Saltar a contenido

Autenticación web SPA

Como dijimos cuando creamos nuestra API, no tenemos seguridad ninguna. Esto es algo que supone una gran brecha de seguridad si queremos que nuestro proyecto no sea accesible para todo el mundo con todas sus capacidades. La autenticación en la REST API puede ser una necesidad, ya que en un momento dado puede que sea una necesidad para ti al querer proteger ciertos recursos de una REST API, para evitar un acceso público. El esquema común en base a sesiones, que aunque se puede activar, no es una buena práctica al recomendarse que las APIs se pueda consumir sin estado o sesión.

Por lo tanto, existen otros métodos para lograr el objetivo de dotar a la REST API de autenticación requerida en alguno de sus métodos o en todos, basándonos en cookies o tokens. Laravel Sanctum proporciona un sistema de autenticación al proporcionar opciones según nuestras necesidades para webs de tipo SPA (aplicaciones de una sola página) y API simples basadas en tokens. Aparte de estas, Sanctum permite que cada usuario de la aplicación genere múltiples tokens de API para su cuenta. A estos tokens se les pueden otorgar habilidades o permisos que especifican qué acciones pueden realizar los tokens.

Vamos a ver cómo se haría esta autenticación para una SPA:

app.php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->statefulApi();
    })
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->render(function(NotFoundHttpException $e, $request){
            if($request->expectsJson()){
                return response()->json('Not found',404);
            }
        });
    })->create();
Añadimos el middleware de statefulApi() que nos hará la gestión de la autenticación en nuestra SPA. Además, necesitamos modificar el bootstrap.js

import axios from 'axios';
window.axios = axios;

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;

Y nuestro fichero api.php deberá tener la parte en la que le indiquemos el uso de sanctum:

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

En un proyecto reactivo de cero, como es el de este apartado, habrá que crear el módulo de Login o inicio de sesión, si hemos hecho una app híbrida de Laravel y Vue, podríamos usar el creado por Breeze.

Módulo de Login

En busca de un sistema sencillo de autenticación, vamos a generar nuestro sistema. Para ello debemos crear un controlador de Login, y que quede parecido a:

LoginController
<?php

namespace App\Http\Controllers;

use Auth;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    function authenticate(Request $request) {

        $validator = validator()->make($request->all(),
            [
                'email' => 'required', 'email',
                'password' => 'required'
            ]
        );

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

        $credentials = $validator->valid();

        if(Auth::attempt($credentials)){
            $request->session()->regenerate();
            return response()->json('Successful authentication');
        }

        return response()->json('The username and/or password do not match', 422);
    }
}

Crear usuario

Para generar un usuario de prueba, vamos a usar la herramienta Tinker. Debido a que nuestro proyecto parte de cero, y no tenemos instalado Breeze, no tenemos un módulo de creación de usuarios.

Tinker

Tinker es una herramienta que proporciona un espacio interactivo para experimentar con código, interactuar con su base de datos y optimizar la depuración. Es una herramienta de la que debes hacer uso cuando tienes proyectos grandes o no tienes acceso a la base de datos.

php artisan tinker
> $user= new App\Models\User()
> $user->password = Hash::make('123456789')
> $user->email= 'admin@admin.com'
> $user->name='admin'
> $user->save()

Con esto ya tendríamos nuestro usuario creado, faltaría adaptar todo lo realizado a nuestra API.

Adaptar a la API REST

Lo primero, como ya te habrás dado cuenta, las rutas.

api.php
Route::post('user/login',[LoginController::class, 'authenticate']); 
web.php
Route::post('user/login',[LoginController::class, 'authenticate']);

Con esto ya tenemos nuestras rutas registradas tanto en la web como en la API. Si partimos de la definición básica de lo que es una REST API, no deberíamos tener un estado o sesión ya que es un mal concepto de programación, es por ello que debemos trabajar a través de Sanctum y las cookies.

Implementación

Para poder hacer uso de esto debemos implementarlo en nuestro proyecto haciendo uso de las CSRF cookies:

<script>
export default {
    mounted(){
        axios.get('sanctum/csrf-cookie').then(response=>{
            axios.post('/api/user/login',{
                'email': 'admin@admin.com',
                'password':'123456789',
            }).then(response =>{
                console.log(response.data)        
            })
        })
    },
    ...
}
</script>

El login hay que hacerlo una sola vez, y lo veremos más adelante, es por eso que una vez autenticado podemos consumir la API de las rutas que de otro modo no podríamos.

Protección de rutas

La protección de las rutas de nuestra API se hace desde el fichero de api.php, en el que deberemos hacer uso de un middleware para ello:

Route::group(['middleware' => 'auth:sanctum'], function () {
    Route::resource('category', CategoryController::class)->except(['create', 'edit']);
    Route::resource('post', PostController::class)->except(['create', 'edit']); 
});

De este modo para la creación y edición de un post o categoría deberíamos estar autenticados o no nos dejaría consumir la API.

Actividad

  • 🧪 PR 910. (RA9 / CE9e CE9h / IC2 / 5p) - Protege las rutas de la administración para que solo el docente pueda acceder a ellas.