Non è una novità: ogni volta che scopriamo un servizio interessante, eccoci a dover nuovamente cliccare sul bottone “Crea nuovo account“, per poi compilare lunghi form, verificare l’indirizzo email, ricordarsi la password.
Quanto è più comodo poter usare, per l’autenticazione, alcuni dei più famosi social network mondiali ? Beh, se non si tratta di servizi critici, poter chiedere la garanzia di un account ad un attore di terze parti come Google o Facebook è molto comodo: basta password da memorizzare, procedure di recupero da inventare, captcha…
C’è però un problemino: quale terza parte scegliamo di usare ? Beh, non è banale ma prendendo anche solo i 3 maggiori social network:
vediamo che ognuno ha la sua libreria ed i suoi metodi per l’autenticazione attraverso il protocollo OAuth.
A tal proposito ci viene in aiuto Janrain, un comodo servizio -gratuito per i piccoli siti- che funge da meta-autenticatore: sarà Janrain a presentare all’utente il servizio social con cui intende autenticarsi, dopodichè provvederà lui ad inviare al nostro server il token ed altre informazioni utili sull’utente.
Come si implementa sul proprio sito web ?
Innanzitutto si crea un account su Janrain.com e poi si crea una nuova “Applicazione”. A questo punto si decide se si preferisce il widget modale (apre un popup) oppure embedded (si tratta di un <div> da inserire nelle proprie pagine).
Il secondo passaggio, un pò più oneroso in termini di tempo, è la scelta dei providers di autenticazione e la creazione, per ognuno di essi, di una applicazione. Il percorso è guidato ed è comunque molto semplice da realizzare.
Dal lato client, è sufficente inserire il loro snipet javascript nelle pagine web dove l’utente può accedere all’autenticazione. Personalmente ho preferito copiare il codice in un nuovo file:
<script type='text/javascript' src='[path_to_js]/janrain.js'></script>
Importante ricordarsi di cambiare l’URL della pagina per il callback dell’autenticazione:
janrain.settings.tokenUrl = 'http://www.openitaly.net/janrain.php';
A questo punto passiamo alla parte più interessante del processo di single sign-on: la pagina di callback. Sul loro sito ci sono esempi i tutti i linguaggi di programmazione più comuni, compreso il PHP. Io ho preferito fare alcune prove e posso dare qualche informazione in più, con il var_dump di quanto restituiscono i tre provider succitati:
<?php
$rpx_api_key = '[API KEY DELL'APPLICAZIONE SU JANRAIN]'; $engage_pro = false; /* STEP 1: Extract token POST parameter */ $token = $_POST['token']; if(strlen($token) == 40) {//test the length of the token; it should be 40 characters /* STEP 2: Use the token to make the auth_info API call */ $post_data = array('token' => $token, 'apiKey' => $rpx_api_key, 'format' => 'json', 'extended' => 'true'); //Extended is not available to Basic. $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_URL, 'https://rpxnow.com/api/v2/auth_info'); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_FAILONERROR, true); $result = curl_exec($curl); if ($result == false) { $mySession->sendMessage("cURL error: ".curl_error($curl)."(".curl_errno($curl).")",T_ERROR); } curl_close($curl); $authInfoArray = json_decode($result, true); if($authInfoArray['stat'] == 'ok') { $profileArray = $authInfoArray["profile"]; // var_dump($profileArray); $identifier = CleanInput($profileArray["identifier"]); $result = dbQuery("SELECT Username FROM Users WHERE providerId='$identifier';"); if(mysql_num_rows($result) == 1) { $row = mysql_fetch_array($result, MYSQL_ASSOC); $userName = stripslashes($row["Username"]); if($userName) { // Ok, questo utente e' registrato. Autentica la sessione e vai alla sua pagina principale .... } } // Se l'utente non e' presente nel DB, richiedi autenticazione oppure l'iscrizione if($profileArray["providerName"] == "Facebook") { /* FACEBOOK array(10) { ["name"]=> array(3) { ["givenName"]=> string(9) "Openitaly" ["familyName"]=> string(3) "Net" ["formatted"]=> string(13) "Openitaly Net" } ["photo"]=> string(60) "http://graph.facebook.com/100000185893913/picture?type=large" ["address"]=> array(1) { ["formatted"]=> string(12) "Siena, Italy" } ["displayName"]=> string(13) "Openitaly Net" ["preferredUsername"]=> string(12) "OpenitalyNet" ["url"]=> string(33) "http://www.facebook.com/openitaly" ["gender"]=> string(4) "" ["utcOffset"]=> string(5) "01:00" ["providerName"]=> string(8) "Facebook" ["identifier"]=> string(54) "http://www.facebook.com/profile.php?id=[ID]" } */ .... } else if($profileArray["providerName"] == "Twitter") { /* TWITTER array(7) { ["photo"]=> string(74) "http://a3.twimg.com/profile_images/1665796327/logoSquare128x128_normal.png" ["name"]=> array(1) { ["formatted"]=> string(9) "OpenItaly" } ["displayName"]=> string(9) "OpenItaly" ["preferredUsername"]=> string(9) "OpenItaly" ["url"]=> string(28) "http://twitter.com/OpenItaly" ["providerName"]=> string(7) "Twitter" ["identifier"]=> string(51) "http://twitter.com/account/profile?user_id=[ID]" } */ .... } else if($profileArray["providerName"] == "Google") { /* GOOGLE array(9) { ["googleUserId"]=> string(21) "[userId]" ["name"]=> array(3) { ["givenName"]=> string(7) "Michele" ["familyName"]=> string(7) "Pinassi" ["formatted"]=> string(15) "Michele Pinassi" } ["verifiedEmail"]=> string(25) "michele.pinassi@gmail.com" ["displayName"]=> string(15) "michele.pinassi" ["preferredUsername"]=> string(15) "michele.pinassi" ["url"]=> string(53) "https://www.google.com/profiles/[ID]" ["providerName"]=> string(6) "Google" ["identifier"]=> string(53) "https://www.google.com/profiles/[ID]" ["email"]=> string(25) "michele.pinassi@gmail.com" } */ .... } else { // Provider non riconosciuto. Che faccio ? } } else { // Gracefully handle auth_info error. Hook this into your native error handling system. $mySession->sendMessage("Error while signing on: ".$authInfoArray['err']['msg'],T_ERROR); } } else { // Gracefully handle the missing or malformed token. Hook this into your native error handling system. $mySession->sendMessage("Abort received while signing on. Wanna try again ?",T_ERROR); }
Come potete vedere, non tutti i provider restituiscono gli stessi dati. Tuttavia Janrain suggerisce di utilizzare identifier come identificatore univoco dell’utente.
Spero di esservi stato di aiuto e …buona autenticazione !
2 comments
Ciao Michele, provo a porti il mio quesito qui dato che in giro per il web non ho trovato questa soluzione. L’implementazione tramite i codici che ti forniscono sul loro sito è chiara direi ma.. il mio dubbio esistenziale e come far capire al mio database che deve aprire una sessione per l’utente che fa il login su un’altro sito e sopratutto come sfruttare questa interessante implementazione per popolare il mio database… ho cercato ovunque ma sinceramente personalmente non sono stato in grado di capire come adeguare il database MySql già esistente. Grazie in anticipo!
Ciao Davide,
nell’array che ritorna l’autenticazione Janrain c’è un identificativo univoco (“identifier”) che puoi usare per “linkare” un tuo utente all’autorità di autenticazione. Ad esempio, se un utente si autentica tramite Google, ecco l’array che ritorna:
["profile"]=> array(9) {
["googleUserId"]=> string(21) "[id]"
["name"]=> array(3) {
["givenName"]=> string(7) "Michele"
["familyName"]=> string(7) "Pinassi"
["formatted"]=> string(15) "Michele Pinassi"
}
["verifiedEmail"]=> string(25) "michele.pinassi[at]gmail.com"
["displayName"]=> string(15) "michele.pinassi"
["preferredUsername"]=> string(15) "michele.pinassi"
["url"]=> string(53) "https://www.google.com/profiles/[id]"
["providerName"]=> string(6) "Google"
["identifier"]=> string(53) "https://www.google.com/profiles/[id]"
["email"]=> string(25) "michele.pinassi[at]gmail.com"
}
["stat"]=> string(2) "ok"
e sempre ad esempio, ecco l’array di Twitter:
["profile"]=> array(7) {
["photo"]=> string(69) "http://a2.twimg.com/profile_images/[url]"
["name"]=> array(1) {
["formatted"]=> string(15) "Michele Pinassi"
}
["displayName"]=> string(15) "Michele Pinassi"
["preferredUsername"]=> string(15) "michele_pinassi"
["url"]=> string(34) "http://twitter.com/michele_pinassi"
["providerName"]=> string(7) "Twitter"
["identifier"]=> string(51) "http://twitter.com/account/profile?user_id=[id]"
}
["stat"]=> string(2) "ok"
come puoi vedere, alcuni campi sono diversi a seconda dell’authority mentre il campo “identifier” è sempre presente ed è univoco per ogni utente: utilizza questo campo (come nuova colonna, ad esempio) nel tuo database per riconoscere un utente locale.
Spero di esserti stato di aiuto, Michele