Pacuna's Blog

Autenticaci贸n mediante token en Laravel 4

En este art铆culo presentar茅 un esquema b谩sico para realizar autenticaci贸n mediante token.

Este enfoque es 煤til para desarrollo de API鈥檚 en las cuales no se deben mantener estados a trav茅s de sesiones. Sirve para desarrollar aplicaciones Single-Page o para otro tipo de API鈥檚.

Flujo b谩sico

  1. El usuario env铆a sus credenciales (email, password) a trav茅s de una petici贸n POST al servidor
  2. El servidor verifica que las credenciales sean correctas, genera un token 煤nico para este usuario y lo devuelve
  3. Todas las peticiones posteriores se har谩n incluyendo este token como parte del Header de la petici贸n
  4. El servidor al recibir una petici贸n para un recurso protegido, verificar谩 que este token existe en la base de datos y corresponde a un usuario que tiene permisos.

Implementaci贸n

Como siempre crearemos una aplicaci贸n usando composer:

composer create-project laravel/laravel laravel-token-auth --prefer-dist

Luego crearemos una migraci贸n para la tabla de usuarios

php artisan migrate:make create_users_table --create=users

Modificaremos esta migraci贸n para agregar los campos necesarios

 public function up()
 {
 Schema::create('users', function(Blueprint $table)
 {
 $table->increments('id');
 $table->string('username');
 $table->string('email');
 $table->string('password');
 $table->string('authentication_token')->nullable();
 $table->timestamps();
 });
 }

Permitiremos que el campo del token pueda ser nulo al crear usuarios.

Tambi茅n agregaremos un usuario de prueba en nuestro archivo de seeders

DatabaseSeeder.php

public function run()
 {
 Eloquent::unguard();
 User::create([
 'username' => 'test',
 'email' => '[email protected]',
 'password' => Hash::make('pass')
 ]);
}

Recuerda configurar tu base de datos antes de ejecutar la migraci贸n.

Para aplicar los cambios en la base de datos ejecutamos:

php artisan migrate --seed

Ahora tenemos nuestra tabla y un usuario de prueba. Recuerda que Laravel 4 tiene un modelo de usuario creato por defecto adem谩s de una cantidad 煤tiles de funciones para trabajar con autenticaci贸n.

Primeras rutas

Vamos a crear 2 rutas para comenzar a hacer pruebas con tu cliente para hacer peticiones favoritos. En este ejemplo utilizar茅 Curl para mostrar los comandos necesarios. Una de las rutas no tendr谩 protecci贸n y la otra si:

<?php

Route::get('/', function(){
 return Response::json(['content' => 'main page']);
});

Route::get('/secret', function(){
 if(authenticated()){
 return Response::json(['content' => 'secret page']);
 }
 else{
 return Response::json(['content' => 'no estas autorizado'], 401);
}
});

function authenticated(){
return false;
}

Para la ruta protegida, estamos llamando a una funci贸n que verificar谩 si el usuario est谩 autorizado para acceder a la p谩gina. Por ahora podemos declarar esta funci贸n en el mismo archivo routes la cual retornar谩 falso para simular un acceso denegado. Tambi茅n puedes ver que al responder dentro del else, devolvemos un status code 401, el cual es est谩ndar para accesos denegados. Otro detalle importante es que las respuestas est谩n siendo enviadas en formato Json, el cual se est谩 convirtiendo en un formato est谩ndar para devolver datos de API鈥檚.

Primeras pruebas

Para probar nuestra ruta no protegida podemos usar Curl en nuestra consola. Ejecuta:

php artisan serve

En el root de tu aplicaci贸n para ejecutar tu aplicaci贸n y luego en otra ventana ejecuta:

curl http://localhost:8000

Curl ejecutar谩 una petici贸n get a la direcci贸n. Deber铆as obtener algo as铆 en tu terminal:

{"content":"main page"}

Luego para probar la ruta protegida, ejecuta curl de manera similar, pero agrega el flag -v (verbose) para analizar m谩s detalladamente la respuesta del servidor:

curl -v http://localhost:8000/secret

Obtendras una respuesta similar a esta:

< HTTP/1.1 401 Unauthorized
< Host: localhost:8000
< Connection: close
< X-Powered-By: PHP/5.5.11
< Cache-Control: no-cache
< Date: Wed, 14 May 2014 18:17:02 GMT
< Content-Type: application/json
< X-Frame-Options: SAMEORIGIN
< Set-Cookie: laravel_session=eyJpdiI6IlNzQXE0SU82cmR1S1JRYUhUSE8zUHNWeG92WEQ2UEtETnZtWlFtSFcxXC8wPSIsInZhbHVlIjoiTk1YeFwvR1REK3cyZk9DbDZ4REZUZUZpTzFYaUp4XC9BaWZmMzl1WDgzVmdPWkxcL0NRS1J5NXpjOVdYZkVLRW02MFBQYUpHSkVuc2ttZ2hOUXR4elFDcFE9PSIsIm1hYyI6IjRhYjgzZDlhMjRlODEyYmZjMjE5MmE1MGMzNDQ2MzRlZmU3YjAwNzJjNjA3ZTNhNTE0NDk2MDE3Mjk2YTg3ZWIifQ%3D%3D; expires=Wed, 14-May-2014 20:17:02 GMT; Max-Age=7200; path=/; httponly
< 
* Closing connection 0
{"content":"no estas autorizado"}

Puedes ver que se recibi贸 el status code 401 as铆 como la respuesta 鈥渘o est谩s autorizado鈥. Hasta ahora vamos bien. Comencemos a escribir la parte entretenida.

Login de usuario

Para el login usaremos las funciones t铆picas de seguridad en Laravel, pero una vez que el usuario sea autenticado, vamos a generar un token random y se lo asignaremos al usuario:

<?php

Route::post('/login', function(){
 $email = Input::get('email');
 $password = Input::get('password');

 if (Auth::attempt(array('email' => $email, 'password' => $password)))
 {
   //loop para crear un token unico
   while(true){
   $token = str_random(15); //TODO: usar una manera mas segura para generar el token
   $user = User::where('authentication_token', '=', $token)->get();

   if($user->count() > 0) continue;
   else break;
 }

 //setear el token para el usuario
 Auth::user()->update(['authentication_token' => $token]);
 return Response::json(['auth_token' => $token]);
 }

else{
 return Response::json(['content' => 'datos incorrectos'], 401);
 }
});

Este login se ve un poco complejo pero en realidad es simple. Una vez que realizamos el attempt() con las credenciales del usuario, creamos el token aleatorio dentro un loop, para verificar que ning煤n otro usuario tiene este token asignado. La probabilidad de que esto ocurra es extremadamente baja pero a煤n as铆 es parte del trabajo. Una vez que tenemos este token, usamos la funci贸n update() para actualizar el campo authentication_token del usuario que se logue贸, y le asignamos el token generado. Tambi茅n devolvemos el token para que el cliente que quiera acceder al recurso lo pueda enviar en futuras peticiones. Finalmente si los datos son incorrectos, respondemos con un status code 401.

Funci贸n para autenticar

La funci贸n authenticate() que verifica si un usuario puede acceder a un recurso a煤n no est谩 implementada. Ahora s贸lo estamos devolviendo un valor false. Veamos una implementaci贸n mas 煤til:

<?php

function authenticated($token){
  $user = User::where('authentication_token', '=', $token)->get();
  if($token && $user->count() > 0){
   return true;
  }
  else{
   return false;
  }
}

Esta funci贸n recibe el token que ser谩 enviado a trav茅s de un header http, luego verifica que existe un usuario con este token asignado en la base datos. Si existe retorna true, de lo contrario retorna false. Simple!

Ruta secreta reescrita:

Debemos hacer algunos cambios en nuestra ruta secreta. Como el token ser谩 enviado a trav茅s de un header, usaremos la Facade Request de laravel para acceder a este header, que tendr谩 el nombre 鈥榯oken鈥.

<?php

Route::get('/secret', function(){
 if(authenticated(Request::header('token'))){
   return Response::json(['content' => 'secret page']);
 }
 else{
   return Response::json(['content' => 'no estas autorizado'], 401);
 }
});

Estamos recibiendo el header token y envi谩ndoselo a la funci贸n que escribimos previamente.

Vamos a probar la funcionalidad de login para ver si nos devuelve el token:

curl -X POST -d "[email protected]&password=pass" http://localhost:8000/login

Recuerda que estos son los datos del usuario que agregamos en nuestro seed al migrar la base de datos. Deber铆as ver una respuesta similar a esta:

{"auth_token":"d9xbO7Fy40OzyIp"}

Ahora podemos enviar este token como header a la ruta secreta y deber铆amos poder acceder:

curl -v -H "token: d9xbO7Fy40OzyIp" http://localhost:8000/secret

Recuerda reemplazar el token por el que tu obtuviste al hacer login. Obtendr谩s una respuesta similar a esta:



> GET /secret HTTP/1.1
> User-Agent: curl/7.36.0
> Host: localhost:8000
> Accept: */*
> token: d9xbO7Fy40OzyIp
> 
< HTTP/1.1 200 OK
< Host: localhost:8000
< Connection: close
< X-Powered-By: PHP/5.5.11
< Cache-Control: no-cache
< Date: Wed, 14 May 2014 18:41:59 GMT
< Content-Type: application/json
< X-Frame-Options: SAMEORIGIN
< Set-Cookie: laravel_session=eyJpdiI6IlA0YU1YTHBxdG1SZlB2dWVUcDVTMXA2UFBRWTdRUlZ6NElHUEl5NGNPU3M9IiwidmFsdWUiOiJtcVJtVlBmZDJPRXBQMUFKT3UyWkVWUFJrN2N5U3JKbWFWSmhOemRBbm11eXA3b1R3d0UwSmhvQ1Q2d1wvSUVuVCtZblpHbUpqdUNUZHZkckN1ZUdINUE9PSIsIm1hYyI6IjVkMmY1YjI4ZWM5Yjg0MWQ0ZmY3M2Q0NzZmNTE5MDk1NzkyMWYxZGU2NTljZTE1ZTEyY2FkYWY1ZTAyMzA4MTAifQ%3D%3D; expires=Wed, 14-May-2014 20:41:59 GMT; Max-Age=7200; path=/; httponly
< 
* Closing connection 0
{"content":"secret page"}

Como vez hemos podido acceder y se nos has enviado la respuesta del sitio secreto. Si cambias algo del token y haces la petici贸n nuevamente ver谩s que ser谩 rechadaza:

curl -v -H "token: d9xbO7Fy40OzyI" http://localhost:8000/secret

Obtenemos:

> GET /secret HTTP/1.1
> User-Agent: curl/7.36.0
> Host: localhost:8000
> Accept: */*
> token: d9xbO7Fy40OzyI
> 
< HTTP/1.1 401 Unauthorized
< Host: localhost:8000
< Connection: close
< X-Powered-By: PHP/5.5.11
< Cache-Control: no-cache
< Date: Wed, 14 May 2014 18:43:16 GMT
< Content-Type: application/json
< X-Frame-Options: SAMEORIGIN
< Set-Cookie: laravel_session=eyJpdiI6InlPaXJjU3lXeDhVMHlaNnVtWHNVRHlNZHRkb1I3OXdwXC95VWV4YXp5TXBnPSIsInZhbHVlIjoia3Y5TVwvRkFVN3VPSkc4S2FqSkNRTnd6V05sRlJTUkw3VEhvZ2pDTHhxdnMza1pOZXdrVFFIelhibk9zYlMrV3R3XC9FY3dtbFBrWXhLdlY4cEJNR3VCUT09IiwibWFjIjoiNGUxNTJkYjU2OTNmNDc2OWE2ZmIxNmNmOTVjNzM4NTE0NzVkYTYxYWZhYTc2ODRmZDgzN2JkMWY4MjA4ZjlhMCJ9; expires=Wed, 14-May-2014 20:43:16 GMT; Max-Age=7200; path=/; httponly
< 
* Closing connection 0
{"content":"no estas autorizado"}

Genial! el token est谩 siendo recibido correctamente.

Este es el esquema m谩s b谩sico para comenzar a trabajar con autenticaci贸n v铆a token. Ahora el cliente que desee utilizar tu API debe encargarse de implementar un formulario que env铆e los datos v铆a post a tu ruta de login, recibir el token de seguridad y comenzar a enviarlo como header cuando haga las peticiones hacia tu servidor.

El repositorio con el c贸digo de este ejemplo lo puedes encontrar en https://github.com/pacuna/laravel-auth-token

Mejoras

La principal mejora es encontrar una manera m谩s segura de generar un token aleatorio. Esto se puede hacer usando funciones de encriptaci贸n mas avanzadas de php.

Obviamente otra mejorar es estructurar de mejor manera el proyecto y sus funciones. Este es s贸lo un ejemplo para jugar y para tener una idea de como usar este tipo de autenticaci贸n en Laravel.

Cual duda s贸lo comenta!

Saludos!

View original

#php #laravel

- 1 toasts