JWT para REST API con Laravel 8
By: Eric Buitrón López
Tags: Jwt Auth, Laravel, Rest Api

¿Qué vamos a realizar?

En este tutorial vamos a demostrar cómo configurar la librería jwt-auth en un proyecto de Laravel 8 para hacer funciones básicas de manejo de usuarios. Vamos a enfocarnos en específico en utilizar un modelo que no sea el de usuario default de Laravel.

Creación del proyecto

Configuración de JWT

Crear y preparar el modelo para autenticación

Configurar el Auth Guard

Definir rutas

Definir lógica del controlador

Pruebas con Postman

Creación del proyecto

Para empezar, es necesario instalar un nuevo proyecto de Laravel con el comando create-project de composer. Es importante mencionar que para realizar esto, composer debe estar instalado globalmente o en el directorio en dónde estamos creando el proyecto.

Para más información sobre cómo instalar composer visita el siguiente enlace: https://getcomposer.org

composer create-project - prefer-dist laravel/laravel laravel8-api-jwt

El comando generará un nuevo proyecto de Laravel 8 junto con todas sus dependencias.

Después, es necesario configurar la base de datos que se va a utilizar y agregar los detalles de esta en el archivo .env . En este ejemplo se creó una base de datos MySQL llamada laravel8-api-jwt.

Una vez que realizamos este paso, tenemos que crear una migración para la tabla que usaremos para manejar las cuentas del proyecto. En nuestro caso, la tabla se va a llamar Cuenta.

php artisan make:migration create_cuenta_table

Toma en consideración que tu tabla tiene que tener como mínimo un id, un string único que puede ser nombre de usuario o correo, un string para la contraseña llamado “password”, un campo tipo rememberToken y los timestamps. Nosotros decidimos incluir también un campo para el nombre de la persona que registra una cuenta.

<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema;

class CreateCuentaTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('cuenta', function (Blueprint $table) { $table->id(); $table->string('nombre'); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('cuenta'); } }

Finalmente se deben de correr las migraciones con el siguiente comando:

php artisan migrate

Para asegurarnos de que todo funcione correctamente, levantamos el servidor con el siguiente comando:

php artisan serve

Deberíamos de ver una pantalla similar a la siguiente:

Configurar JWT

Primero hay que instalar el paquete utilizando el siguiente comando:

composer require tymon/jwt-auth

En cuanto se termine de instalar el paquete, hay que agregarlo al proveedor de servicios en el archivo app.php que se encuentra en el directorio “config”.

/* * Package Service Providers... */ 'Tymon\JWTAuth\Providers\LaravelServiceProvider', /* * Application Service Providers... */

También hay que agregar JWTAuth y JWTFactory en los “aliases” del mismo archivo.

'aliases' => [ ... 'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth', 'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory' ],

Después, se deben publicar los paquetes usando el siguiente comando:

php artisan vendor:publish — provider=”Tymon\JWTAuth\Providers\LaravelServiceProvider”

Para finalizar este paso hay que generar una llave secreta que va a utilizar el paquete para encriptar los tokens. El siguiente comando genera la llave de encripción y la pone en el archivo .env con algo parecido a JWT_SECRET=llave.

php artisan jwt:secret

Crear y preparar el modelo para autenticación

Primero se debe de crear el modelo de nuestra tabla. Esto se logra utilizando el siguiente comando:

php artisan make:model Cuenta

Para integrar el modelo es necesario implementar el contrato Tymon\JWTAuth\Contracts\JWTSubject y definir los métodos requeridos getJWTIdentifier() y getJWTCustomClaims().

<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Tymon\JWTAuth\Contracts\JWTSubject; use Illuminate\Notifications\Notifiable; class Cuenta extends Authenticatable implements JWTSubject { protected $table = 'cuenta'; use HasFactory; use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'nombre', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the identifier that will be stored in the subject claim of * the JWT. * @return mixed */ public function getJWTIdentifier() { return $this->getKey(); } /** * Return a key value array, containing any custom claims to be * added to the JWT. * @return array */ public function getJWTCustomClaims()

{ return []; } }

Configurar Auth Guard

Lo que sigue es configurar Laravel para que use jwt-auth como medio de autenticación. Para esto es necesario hacer las siguientes modificaciones en el archivo auth.config que se encuentra en el directorio config.

Nota: Todo lo que ponemos cómo cuenta debe ser el nombre de la tabla que hayas creado.

<?php return [ /* |-------------------------------------------------------------------------- | Authentication Defaults |-------------------------------------------------------------------------- | | This option controls the default authentication "guard" and password | reset options for your application. You may change these defaults | as required, but they're a perfect start for most applications. | */ 'defaults' => [ 'guard' => 'api', 'passwords' => 'cuenta', ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | here which uses session storage and the Eloquent user provider. | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | Supported: "session", "token" | */ 'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'cuenta', 'hash' => false, ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | If you have multiple user tables or models you may configure multiple | sources which represent each model / table. These sources may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'cuenta' => [ 'driver' => 'eloquent', 'model' => App\Models\Cuenta::class, ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | You may specify multiple password reset configurations if you have more | than one user table or model in the application and you want to have | separate password reset settings based on the specific user types. | | The expire time is the number of minutes that the reset token should be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | */ 'passwords' => [ 'cuenta' => [ 'provider' => 'cuenta', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], ], /* |-------------------------------------------------------------------------- | Password Confirmation Timeout |-------------------------------------------------------------------------- | | Here you may define the amount of seconds before a password confirmation | times out and the user is prompted to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | */ 'password_timeout' => 10800, ];

Definir rutas

Ahora vamos a definir las rutas de nuestra aplicación REST API en el archivo api.php que se encuentra en el directorio routes.

<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use App\Http\Controllers\JWTController; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); Route::group([ 'middleware' => 'api', 'prefix' => 'auth' ], function ($router) { Route::post('register', [JWTController::class, 'register']); Route::post('login', [JWTController::class, 'login']); Route::post('logout', [JWTController::class, 'logout']); Route::post('refresh', [JWTController::class, 'refresh']); Route::get('profile', [JWTController::class, 'profile']); });

Nota: todas las rutas definidas en api.php van a tener cómo prefijo api/ y las rutas de autenticación el prefijo /auth. Por lo que nuestras rutas son realmente /api/auth/register por ejemplo.

Definir lógica del controlador

Ya que tenemos las rutas definidas, tenemos que crear el controlador para manejarlas usando el siguiente comando:

php artisan make:controller JWTController

Después definimos cada método.

<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Auth; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Validator; use Illuminate\Http\Request; use App\Models\Cuenta; class JWTController extends Controller { /** * Create a new AuthController instance. * * @return void */ public function __construct() { $this->middleware('auth:api', ['except' => ['login', 'register']]); } /** * Register a User. * * @return \Illuminate\Http\JsonResponse */ public function register(Request $request) { $validator = Validator::make($request->all(), [ 'nombre' => 'required|between:2,100', 'email' => 'required|email|unique:users|max:50', 'password' => 'required|confirmed|string|min:6', ]); $cuenta = Cuenta::create(array_merge($validator->validated(),['password' => bcrypt($request->password)])); return response()->json([ 'message' => 'Registrado con éxito', 'cuenta' => $cuenta ], 201); } /** * Get a JWT via given credentials. * * @return \Illuminate\Http\JsonResponse */ public function login(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|email', 'password' => 'required|string|min:6', ]); if ($validator->fails()) { return response()->json($validator->errors(), 422); } if (! $token = auth()->attempt($validator->validated())) { return response()->json(['error' => 'Unauthorized'], 401); } return $this->createNewToken($token); } /** * Get the authenticated User. * * @return \Illuminate\Http\JsonResponse */ public function profile() { return response()->json(auth()->user()); } /** * Log the user out (Invalidate the token). * * @return \Illuminate\Http\JsonResponse */ public function logout() { auth()->logout(); return response()->json(['message' => 'Sesión cerrada con éxito']); } /** * Refresh a token. * * @return \Illuminate\Http\JsonResponse */ public function refresh() { return $this->createNewToken(auth()->refresh()); } /** * Get the token array structure. * * @param string $token * * @return \Illuminate\Http\JsonResponse */ protected function createNewToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth()->factory()->getTTL() * 60 ]); } }

Register

Este método se va a encargar de registrar una nueva cuenta. Es importante mencionar que debe mandarse también un campo llamado “password_confirmation” que contenga lo mismo que el campo “password”. Esto es para validar que el usuario está ingresando la contraseña deseada.

public function register(Request $request) { $validator = Validator::make($request->all(), [ ‘nombre’ => ‘required|between:2,100’, ‘email’ => ‘required|email|unique:users|max:50’, ‘password’ => ‘required|confirmed|string|min:6’, ]); $cuenta = Cuenta::create(array_merge($validator->validated(),[‘password’ => bcrypt($request->password)]));

return response()->json([ ‘message’ => ‘Registrado con éxito’, ‘cuenta’ => $cuenta ], 201); }

Login

Este método permitirá iniciar sesión haciendo una validación con la contraseña y el email, estos valores se autentican con la tabla de Cuenta en nuestra base de datos, si la autenticación es correcta nos regresará nuestro token de acceso, de lo contrario nos devolverá un mensaje de error.

public function login(Request $request) { $validator = Validator::make($request->all(), [ ‘email’ => ‘required|email’, ‘password’ => ‘required|string|min:6’, ]); if ($validator->fails()) { return response()->json($validator->errors(), 422); } if (! $token = auth()->attempt($validator->validated())) { return response()->json([‘error’ => ‘Unauthorized’], 401); } return $this->createNewToken($token); }

Profile

Este método regresa todos los detalles del usuario. Es importante mencionar que se debe de pasar el token de autenticación en el header.

public function profile() { return response()->json(auth()->user()); }

Logout

Esta función cierra la sesión del usuario invalidando el token de acceso que se le dió para iniciar sesión. Al igual que la función profile es necesario pasar dicho token de autenticación en el header.

public function logout() { auth()->logout(); return response()->json([‘message’ => ‘Sesión cerrada con éxito’]); }

Refresh

Esta función puede ser utilizada para mandarle un nuevo token al usuario. Esto es útil para prevenir que el usuario se quede sin token si su sesión tiene una duración larga.

public function refresh() { return $this->createNewToken(auth()->refresh()); }

Create New Token

Este método nos permitirá crear un nuevo token de acceso. Este método solamente va a ser utilizada por otros métodos dentro del controlador por lo que es un método protegido.

protected function createNewToken($token) { return response()->json([ ‘access_token’ => $token, ‘token_type’ => ‘bearer’, ‘expires_in’ => auth()->factory()->getTTL() * 60 ]); }

Pruebas con Postman

A continuación vamos a probar nuestro REST API utilizando Postman. Toma en cuenta que cómo mencionamos anteriormente, se pasa el token en el header de la siguiente manera para los métodos Profile, Refresh Token, y Logout.

Register

Cómo se mencionó anteriormente, es necesario mandar el campo de “password_confirmation” con el mismo valor que tiene “password”.

Login

Profile

Refresh Token

Logout

Con esto finaliza el tutorial para configurar jwt-auth usando un modelo personalizado en Laravel 8 al construir un API REST con autenticación básica. Esperamos que les haya sido de utilidad.