Nuestro conocimiento compartido. Nuestro tesoro compartido. Wikipedia.
TreeWeb::Artículos::PHP::Login with Google
Permalink: http://www.treeweb.es/u/1281/ 01/02/2015

Login with Google

Hace unos minutos he acabado la integración de www.wikaan.com con el sistema de autenticación de Google. Delegar la autenticación en terceros es una buena opción si la base de usuarios que manejamos no es colosal y no tenemos al menos un motivo de peso como la seguridad o la imagen institucional.

Se podrían enumerar las ventajas de la delegación de autenticación en la siguiente lista:
  • Es fácil y rápido de integrar con nuestro sistema
  • Ahorra tiempo de desarrollo
  • Es más seguro y fiable que cualquier cosa que podamos desarrollar por nosotros mismos
  • Facilita la vida al usuario al no tener que gestionar otra contraseña
Si no queremos depender de un único proveedor de autenticación, siempre podemos integrar varios y que el propio usuario elija con cuál accede.

Para evitar una dependencia ciega con estos proveedores, debemos asegurarnos de que en la autenticación nos envían al menos el email del usuario. De esta forma, en caso de que el proveedor desapareciese, podríamos implementar nuestro propio sistema de autenticación sin perder la base de usuarios.

Conseguir las keys

Antes de empezar, debemos dirigirnos a https://code.google.com/apis/console/, dar de alta una aplicación y generar unas claves en el apartado 'Client ID for web applications' con lo que obtendremos un Client ID y un Client secret.

Además, hay que indicar la url de nuestra web a la que nos redirigirá Google (lo veremos más adelante).

El proceso

Lo que en principio debería haber sido un proceso sencillo, se ha empezado a complicar innecesariamente. Al buscar 'google oauth php' encontramos un SDK para integrar en nuestro código, con algunos ejemplos. Este SDK no sólo autentica, sino que permite autorizar e interactuar con infinidad de APIs de Google. Pensando que sería poco código, me encuentro con esto:
------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- PHP 153 10467 30218 157837 XML 1 28 41 88 CSS 1 16 14 83 YAML 1 6 2 22 ------------------------------------------------------------------------------- SUM: 156 10517 30275 158030 -------------------------------------------------------------------------------
Nada más y nada menos que 158K líneas de PHP, !casi 5 veces más que lo que tiene el propio Wikaan! Así que he decidido hacer una muy pequeña librería para utilizar la autenticación y sólo acceder a la información básica del usuario (nombre, email y foto).

Los chicos de Google tienen explicado cómo funciona en detalle https://developers.google.com/accounts/docs/OAuth2WebServer.

El flujo es uno de los que se especifica en el estándar de OAuth2:
En concreto estos pasos:
  1. El servidor genera una página html con un enlace que apunta a Google Auth (para generar este enlace, es necesario el Client ID e indicar los scopes, entre otras cosas).
  2. Al hacer clic, el navegador carga una página de Google pidiendo autenticación y permisos. Al aceptar, Google redirige al usuario a nuestra web con un código en la url (Authorization code).
  3. Nuestro servidor envía ese código a Google Auth, junto con el Client secret para intercambiarlo por un Access token. Con este token, podemos acceder a toda la información a la que nos han autorizado en los scopes.
  4. Con el Access token en nuestro poder, volvemos a llamar a Google para que nos devuelva la información que finalmente buscamos (en nuestro caso nombre, email y foto).
  5. Google nos devuelve la info codificada en JSON.
La info que nos devuelve tiene esta pinta:
{ "id": 100000000000000000000, "email": "ejemplo@gmail.com", "verified_email": 1, "name": "Fulanito Fulanitez", "given_name": "Fulanito", "family_name": "Fulanitez", "link": "https://plus.google.com/+FulanitoFulanitez", "picture": "https://lh5.googleusercontent.com/AAAAA/BBBBB/CCCCC/DDDDD/photo.jpg", "gender": "male" }

El resultado

El código generado está colgado en un gist en: https://gist.github.com/GerardoOscarJT/9c8e3b19e87bcac67342.

A continuación pongo un ejemplo con todos los fragmentos juntos, suponiendo que tenemos el archivo en nuestraweb.com/login.php :
<?php $client_id = ' < your client id > '; $client_secret = ' < your client secret > '; $redirect_uri = 'http://nuestraweb.com/login.php'; // Example: http://yourweb.com/callback.php $scopes = 'https://www.googleapis.com/auth/userinfo.email'; // Separated by spaces class Lib { public static function doRequest($method, $url, $headers, $body) { $opts = array('http' => array( 'method' => $method, 'header' => implode("\n", $headers), 'content' => $body, ) ); $context = stream_context_create($opts); return file_get_contents($url, false, $context); } public static function doPostForm($url, $headers, $body) { $headers[] = 'Content-type: application/x-www-form-urlencoded'; $headers = array_unique($headers); return Lib::doRequest('POST', $url, $headers, http_build_query($body)); } } class GoogleAuth { private $config = array(); public function __construct($client_id, $client_secret, $redirect_uri, $scopes) { $this->config = array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'scopes' => $scopes, ); } public function getAuthLink() { return "https://accounts.google.com/o/oauth2/auth?". "redirect_uri=".urlencode($this->config['redirect_uri']). "&response_type=code". "&client_id=".urlencode($this->config['client_id']). "&scope=".urlencode($this->config['scopes']). "&approval_prompt=force". "&access_type=offline"; } public function getUserInfo($code) { // Exchange token $result = Lib::doPostForm( 'https://accounts.google.com/o/oauth2/token', array(), array( 'code' => $_GET['code'], 'redirect_uri' => $this->config['redirect_uri'], 'client_id' => $this->config['client_id'], 'scope' => $scopes, 'client_secret' => $this->config['client_secret'], 'grant_type' => 'authorization_code', ) ); if (null == $result) { return null; } $tokens = json_decode($result, true); // Get User Info $result = Lib::doRequest( 'GET', 'https://www.googleapis.com/oauth2/v2/userinfo', array( 'Authorization: '.$tokens['token_type'].' '.$tokens['access_token'], ), array() ); if (null == $result) { return null; } return json_decode($result, true); } } $auth = new GoogleAuth($client_id, $client_secret, $redirect_uri, $scopes); ?> <h1>Login with Google</h1> <?php if (array_key_exists('code', $_GET)) { ?> <pre> <?php print_r($auth->getUserInfo($_GET['code'])); ?> </pre> <?php } else { ?> <a href="<? echo $auth->getAuthLink(); ?>">Login with Google</a> <?php } ?>