Autenticación con JWT en Laravel

Json Web Token (JWT) nos permite crear un método de auteticación en servicios API, para que nuestra conexión entre el cliente y nuestro backend sea segura. Funciona de una forma sencilla, el cliente envía su usuario y contraseña, la API le retorna un token que enviará en todas las peticiones, para que ésta compruebe que tiene acceso a las acciones que se quieran realizar.

Instalando JWT en Laravel

Para comenzar, mediante la consola de nuestro sistema, instalaremos un paquete en nuestra aplicación para hacer uso de JWT:

composer require tymon/jwt-auth:dev-develop --prefer-source

Una vez instalado, nos dirigimos como siempre al archivo config/app.php y añadiremos en providers:

 Tymon\JWTAuth\Providers\LaravelServiceProvider::class,

Y más abajo, en este archivo, en el facades, añadiremos estas líneas para ponerle nombre a las clases del paquete instalado:

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class, 
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,

Para terminar con la instalación, en consola ejecutaremos este comando para publicar el archivo de configuración:

$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Para comprobar que la instalación de nuestro paquete ha ido bien, generaremos un token manualmente por consola, con el comando:

php artisan jwt:secret

Nos devolverá la generación de un token similar a:

jwt-auth secret [0mrOWC8Xc6prYU148JZiJlRFotMuOrFsjNzr7mOEgZPFrA3LbItpb4FoYFaAc6Iz] set successfully.

Implementando la auteticación JWT en Laravel

Ahora que ya tenemos correctamente instalado el paquete JWT en laravel, vamos a comenzar con su configuración para obtener mediante el email y contraseña de un usuario, su token.

Para comenzar, iremos al modelo User, que se encuentra en app/User.php:

<?php

    namespace App;

    use Illuminate\Notifications\Notifiable;
    use Illuminate\Foundation\Auth\User as Authenticatable;

    //Añadimos la clase JWTSubject 
    use Tymon\JWTAuth\Contracts\JWTSubject;

    //Añadimos la implementación de JWT en nuestro modelo
    class User extends Authenticatable implements JWTSubject
    {
        use Notifiable;

        /**
         * The attributes that are mass assignable.
         *
         * @var array
         */
        protected $fillable = [
            'name', 'email', 'password',
        ];

        /**
         * The attributes that should be hidden for arrays.
         *
         * @var array
         */
        protected $hidden = [
            'password', 'remember_token',
        ];

        /*
            Añadiremos estos dos métodos
        */
        public function getJWTIdentifier()
        {
            return $this->getKey();
        }
        public function getJWTCustomClaims()
        {
            return [];
        }
    }

Como vemos, añadimos dos métodos nuevos en la clase usuario, es muy importante añadir la implementación JWTSubject en la clase y el uso de su clase, como vemos en el fragmento de código.

Una vez hecho esto, ejecutaremos una migración en nuestra base de datos, que creará un campo nuevo para guardar el token generado de nuestro usuario en la tabla users:

php artisan migrate

Creando el controlador

Crearemos un controlador que llamaremos UserController.php, que utilizaremos para iniciar sesión con el uso de JWT y la generación del token.

<?php   
namespace App\Http\Controllers;

    use App\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Support\Facades\Validator;
    use JWTAuth;
    use Tymon\JWTAuth\Exceptions\JWTException;

class UserController extends Controller
{
    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');
        try {
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 400);
            }
        } catch (JWTException $e) {
            return response()->json(['error' => 'could_not_create_token'], 500);
        }
        return response()->json(compact('token'));
    }
    public function getAuthenticatedUser()
    {
        try {
            if (!$user = JWTAuth::parseToken()->authenticate()) {
                    return response()->json(['user_not_found'], 404);
            }
            } catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
                    return response()->json(['token_expired'], $e->getStatusCode());
            } catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
                    return response()->json(['token_invalid'], $e->getStatusCode());
            } catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
                    return response()->json(['token_absent'], $e->getStatusCode());
            }
            return response()->json(compact('user'));
    }
}
        

Con estos métodos, haremos el inicio de sesión, si el usuario y contraseña coincide, generará un token. En el caso que también quieras utilizar este modo como registro, deberías añadir este método de registro:

public function register(Request $request)
        {
                $validator = Validator::make($request->all(), [
                'name' => 'required|string|max:255',
                'email' => 'required|string|email|max:255|unique:users',
                'password' => 'required|string|min:6|confirmed',
            ]);

            if($validator->fails()){
                    return response()->json($validator->errors()->toJson(), 400);
            }

            $user = User::create([
                'name' => $request->get('name'),
                'email' => $request->get('email'),
                'password' => Hash::make($request->get('password')),
            ]);

            $token = JWTAuth::fromUser($user);

            return response()->json(compact('user','token'),201);
        }

Creando el middlware JWT

Ahora, crearemos un middleware para proteger las rutas de nuestra aplicación con esta nueva auteticación, con este comando:

php artisan make:middleware JwtMiddleware

Y añadiremos el siguiente código:

<?php

namespace App\Http\Middleware;

use Closure;
use JWTAuth;
use Exception;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class JwtMiddleware extends BaseMiddleware
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        try {
            $user = JWTAuth::parseToken()->authenticate();
        } catch (Exception $e) {
            if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException){
                return response()->json(['status' => 'Token is Invalid']);
            }else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException){
                return response()->json(['status' => 'Token is Expired']);
            }else{
                return response()->json(['status' => 'Authorization Token not found']);
            }
        }
        return $next($request);
    }
}

Y para registrarlo, nos dirigimos a app/http/Kernel.php, y agregamos nuestro middleware añadiendo esta línea:

protected $routeMiddleware = [
        ...
        'jwt.verify' => \App\Http\Middleware\JwtMiddleware::class,
        ...
];

Creación de rutas JWT

De ésta forma, ya tendríamos listo nuestro JWT, solo deberíamos crear las rutas para el inicio de sesión y registro:

Route::post('register', 'UserController@register');
Route::post('login', 'UserController@authenticate');

Y por último, en las rutas dónde queramos que sean privadas y solo accesible para usuarios autorizados mediante JWT, añadiremos nuestro nuevo middleware:

 Route::group(['middleware' => ['jwt.verify']], function() {
       /*AÑADE AQUI LAS RUTAS QUE QUIERAS PROTEGER CON JWT*/
 });