Publicidad

Como crear un API con Laravel y autenticacion Sanctum


Por Alex el 03/03/2025, Comentar el artículo

Comparte este artículo:      




Con Laravel y autenticación Sanctum es muy sencillo crear APIs Rest que se validen por token.
La creación de APIs está a la orden del día y no es de extrañar que toque lidiar con más de una a lo largo de nuestra carrera profesional, por eso no solo es importante saber utilizarlas sino también saber crearlas y cómo funcionan internamente.


En este artículo veremos cómo crear una APIs Rest con Laravel 11 y Sanctum para la validación de usuarios. Crearemos un blog donde podremos:

  • crear, listar, modificar y borrar posts de forma sencilla y rápida.
  • Crear usuarios y hacer login con Sanctum
  • Poner seguridad a la creación de post, borrado y modificación y solo usuarios registrados podran hacerlo


Dejo todo el código del proyecto en el github
https://github.com/depruebas/laravel-api-crud

1) Crear un proyecto en Laravel 11 e instalar Sanctum

Lo primero que hacemos es crear el proyecto, lo voy hacer para la versión 11 aunque he probado el código en la 12 recién salida y funciona igual.

Creamos el proyecto

composer create-project laravel/laravel lara-api '11.*'

Para el ejercicio utilizaré la base de datos de sqlite que incorporar Laravel con el proyecto que está alojada en la ruta databases/database.sqlite

Ejecutamos las migraciones para que cree las tablas que incorpora por defecto el proyecto

php artisan migrate

Y ahora cómo vamos a crear una API.

A partir de Laravel 11 ya no viene configurada por defecto en un proyecto de Laravel y vamos a instalarla junto a Sanctum para todo lo referente a accesos de usuarios, validaciones, registros, autentificaciones, etc …

Para instalar todo lo relacionado con una API ejecutamos el siguiente comando de artisan

php artisan install:api

Este comando configura el fichero bootstrap/app.php con el fichero de rutas de API

Estructura del fichero bootstrap/app.php

Y crea los ficheros:
  • config/sanctum.php
  • database/migrations/2025_02_27_151812_create_personal_access_tokens_table.php
  • routes/api.php
Después de haber hecho esto en la base de datos tendríamos que tener una estructura como esta:

Estructura de base de datos para la API

1) Crear un ejemplo CRUD de un blog

Ya tenemos la estructura de la API configurada en el proyecto Laravel y base de datos configurada, ahora vamos a crear un CRUD para una tabla posts donde utilizaremos los métodos REST para

  • POST: crear un nuevo artículo
  • GET: Leer un artículo o todos los artículos
  • PUT: modificar un artículo
  • DELETE: borrar un artículo
Lo primero creamos la tabla posts con una migración

php artisan make:migration create_posts_table

Que creará el siguiente fichero: database/migrations/2025_02_28_071201_create_posts_table.php

Lo abrimos y dentro ponemos el siguiente código

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;


return new class extends Migration
{
   /**
    * Run the migrations.
    */
   public function up(): void
   {
       Schema::create('posts', function (Blueprint $table) {
           $table->id();
           $table->string('title');
           $table->text('description');
           $table->string('slug');
           $table->integer('enabled');
           $table->timestamps();
       });
   }


   /**
    * Reverse the migrations.
    */
   public function down(): void
   {
       Schema::dropIfExists('posts');
   }
};

ejecutamos la migración para que cree la tabla.

php artisan migrate

Ahora creamos el controlador y el modelo

php artisan make:controller PostsController
php artisan make:model Post

Y dentro del controlador ponemos el siguiente código de pruebas para probar que el controlador y las rutas que ahora crearemos funciona bien

namespace App\Http\Controllers;


use Illuminate\Http\Request;


class PostsController extends Controller
{
   #  Lista todos los posts
   public function index()
   {
       echo "Soy muchos posts";
   }


   # Lista un post en específico
   public function show($id)
   {
       echo "Soy el post $id";
   }


   # Guardar el post
   public function store()
   {
       echo "Guardando el post";
   }


   # Actualizar un post
   public function update($id)
   {
       echo "Actualizando el post $id";
   }


   # Eliminar un post
   public function destroy($id)
   {
       echo "Eliminando el post $id";
   }
}


Y creamos las rutas para acceder a estos métodos del controlador.

En el fichero routes/api.php ponemos lo siguiente (de momento lo que hay lo comentamos)

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostsController;


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


Route::get('/posts', [PostsController::class, 'index']);
Route::get('/posts/{id}', [PostsController::class, 'show']);
Route::post('/posts', [PostsController::class, 'store']);
Route::put('/posts/{id}', [PostsController::class, 'update']);
Route::delete('/posts/{id}', [PostsController::class, 'destroy']);


Para probarlo se puede hacer desde el terminal con curl o desde un cliente API REST como por ejemplo postman o una extensión para Visual Studio Code como Thunder Client yo suelo utilizar esta última.

Antes de poder utilizar la API hay que levantar el servidor web y en esta ocasión utilizare el servidor que incorpora Laravel, ejecutamos desde el terminal:

php artisan serve

Y si sale esto es que esta activo y funcionando

Servidor web de Laravel

Y desde un navegador podemos probar la siguiente url http://127.0.0.1:8000/api/posts/ debería dar como resultado "Soy muchos posts"

Ahora vamos a poner el código para crear, modificar, borrar y listar posts en el controlador app/Http/Controllers/PostsController.php que antes hemos creado.

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;
use App\Http\Resources\PostResource;
use App\Http\Resources\PostCollection;

class PostsController extends Controller
{
    #  Lista todos los posts
    public function index()
    {
        return new PostCollection( Post::paginate(10));
    }

    # Lista un post en específico
    public function show(Post $post)
    {
        return new PostResource( $post);
    }

    # Guardar el post
    public function store( Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'description' => 'required|string',
            'slug' => 'required|string|max:255',
            'enabled' => 'required|integer|min:0',
        ]);

        $post = Post::create( $validated);

        return new PostResource( $post);
    }

    # Actualizar un post
    public function update(Request $request, Post $post)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'description' => 'required|string',
            'slug' => 'required|string|max:255',
            'enabled' => 'required|integer|min:0',
        ]);

        $post->update($validated);

        return new PostResource($post);
    }

    # Eliminar un post
    public function destroy(Post $post)
    {
        $post->delete();

        return response()->json([ "code" => "1", "message" => "Post eliminado correctamente"], 200);
    }
}


Y a modificar el código del modelo app/Models/Post.php y poner lo siguiente:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    //
    protected $fillable = [
        'title',
        'description',
        'slug',
        'enabled'
    ];
}

También creamos dos ficheros mas en un directorio llamado Resources para formatear los datos de salida app/Http/Resources/PostResource.php formatea la salida de un registro

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'description' => $this->description,
            'slug' => $this->slug,
            'enabled' => $this->enabled,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}


Y app/Http/Resources/PostCollection.php formatea la salida de muchos registros

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class PostCollection extends ResourceCollection
{
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'meta' => [
                'total' => $this->resource->total(),
                'count' => $this->resource->count(),
                'per_page' => $this->resource->perPage(),
                'current_page' => $this->resource->currentPage(),
                'total_pages' => $this->resource->lastPage(),
            ],
        ];
    }
}

Ya podemos ponernos a probar la API del blog y lo primero vamos a crear un post utilizando un cliente API Rest, como os he comentado yo utilizo Thunder Client

Antes de probarlo en las rutas hay que cambiar el {id} por el objeto {post} para inyectarlo por dependencias cuando devolvemos los resultados. Si dejáis el id no funcionará

Route::get('/posts', [PostsController::class, 'index']);
Route::get('/posts/{post}', [PostsController::class, 'show']);
Route::post('/posts', [PostsController::class, 'store']);
Route::put('/posts/{post}', [PostsController::class, 'update']);
Route::delete('/posts/{post}', [PostsController::class, 'destroy']);


Para crear un post

Enviamos una petición POST a la url http://127.0.0.1:8000/api/posts/

Y enviamos este json para crear un post

{
  "title": "Artículo número dos",
  "description": "Esta es una descripción detallada del post dos",
  "slug": "articulo-numero-dos",
  "enabled": 1
}

El resultado deben de ser los datos del post recién creado … cualquier otra cosa no habrá ido bien

Crear un nuevo post

A la izquierda lo que se envía, a la derecha los resultados que devuelve la API.

Crear las siguientes entradas de la API es similar a lo realizado con la creación de posts así que os dejo el código para que lo veáis.

Y estas serían las llamadas

Obtener un post (GET): http://127.0.0.1:8000/api/posts/5 (si no existe dará un 404)

Obtener un post en concreto

Obtener el listado de posts (GET): http://127.0.0.1:8000/api/posts

Actualizar un post (PUT): http://127.0.0.1:8000/api/posts/5

Actualizar un post en concreto

Eliminar un post (DELETE): http://127.0.0.1:8000/api/posts/5

Eliminar un post en concreto


1) Autentificar con Sanctum: Registro, login y obtener token

Ahora vamos a crear la parte del registro, el login y los permisos para que un usuario pueda hacer unas cosas y otras no

Lo primero modificamos el modelo app/Models/User.php y añadimos que use el trait HasApiToken de Laravel\Sanctum\HasApiTokens que permite al modelo genere y administre tokens de API cuando se usa Laravel Sanctum para la autenticación de APIs.

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

....

En nuestro ejemplo utilizaremos

$token = $user->createToken('auth_token')->plainTextToken;

para crear tokens de usuario.

Ahora creamos un controlador para la autenticación AuthController

php artisan make:controller AuthController

con el siguiente código

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use App\Http\Resources\UserResource;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ]);

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

        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            "data" => new UserResource( $user), 
            "auth" => [
                "access_token" => $token,
                "token_type" => "Bearer"
            ],
        ], 201);
    }

    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        $user = User::where('email', $request->email)->first();

        if (!$user || !Hash::check($request->password, $user->password)) 
        {
            throw ValidationException::withMessages([
                'email' => ['Las credenciales proporcionadas son incorrectas.'],
            ]);
        }

        // Revocar tokens anteriores si se desea
        // $user->tokens()->delete();

        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            "data" => new UserResource( $user), 
            "auth" => [
                "access_token" => $token,
                "token_type" => "Bearer"
            ],
        ], 201);
    }

    public function logout(Request $request)
    {
        $request->user()->currentAccessToken()->delete();

        return response()->json([
            'message' => 'Sesión cerrada exitosamente'
        ]);
    }

    public function profile(Request $request)
    {
        return response()->json($request->user());
    }
}

Y un fichero resources app/Http/Resources/UserResource.php para devolver los datos de los usuarios formateados

namespace App\Http\Resources;


use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;


class UserResource extends JsonResource
{
   public function toArray( Request $request): array
   {
       return [
           'id' => $this->id,
           'name' => $this->name,
           'email' => $this->email,
           'created_at' => $this->created_at,
           'updated_at' => $this->updated_at,
       ];
   }
}


Por ultimo añadimos dos nuevas rutas en el fichero de rutas routes/api.php para el regitro y el login

use App\Http\Controllers\AuthController;

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

Para probarlo y crear un usuario o varios volvemos a utilizar Thunder Client o el cliente que más guste para trabajar con API Rest

Crear un nuevo usuario (POST) http://127.0.0.1:8000/api/register y en el cuerpo se envía el json

{
  "name": "Usuario Ejemplo 10",
  "email": "usuario10@ejemplo.com",
  "password": "password123",
  "password_confirmation": "password123"
}



Crear un nuevo usuario

Login (POST) http://127.0.0.1:8000/api/login y en el cuerpo se envía el json

{
  "email": "usuario2@ejemplo.com",
  "password": "password123"
}



Login a través de la API

Como se ve a la derecha devuelve los datos del usuario y el token que se tiene que utilizar en sucesivas llamadas cuando pongamos seguridad a los posts

Ahora ya tenemos la gestión de usuarios y vamos a poner las rutas de crear posts, actualizar posts y borrarlos seguras y solo se podrá acceder a esas rutas si el usuario se ha autentificado en el sistema.

Para esto lo primero que haremos es cambiar las rutas en el fichero routes/api.php y tiene que quedar así

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostsController;
use App\Http\Controllers\AuthController;

# rutas de la API publicas
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::get('/posts', [PostsController::class, 'index']);
Route::get('/posts/{post}', [PostsController::class, 'show']);

# rutas de la API protegidas
Route::middleware('auth:sanctum')->group(function () {
   Route::post('/logout', [AuthController::class, 'logout']);
   Route::post('/posts', [PostsController::class, 'store']);
   Route::put('/posts/{post}', [PostsController::class, 'update']);
   Route::delete('/posts/{post}', [PostsController::class, 'destroy']);
});

Y su ahora probáis a crear un nuevo post dará un error porque no estas validado.

Antes de crear un post nuevo se tiene hacer login, el login devuelve un conjunto de datos como estos, como hemos visto antes:

{
  "data": {
	"id": 9,
	"name": "Usuario Ejemplo 10",
	"email": "usuario10@ejemplo.com",
	"created_at": "2025-03-02T16:10:41.000000Z",
	"updated_at": "2025-03-02T16:10:41.000000Z"
  },
  "auth": {
	"access_token": "15|kkdg6vyiAXrOI390SY12TAqRX9ldW920pGn4B7TL1280fad6",
	"token_type": "Bearer"
  }
}



De estos datos nos quedamos con el <access_token y se lo pasamos a la petición de crear posts en el campo auth -> Bearer

Paso del toquen en la petición

En el campo Body los datos como antes para crear un post.

Para las acciones de modificar y borrar posts hay que hacer lo mismo que antes con las mismas urls pero añadiendo el token como al crearlo.

1) Conclusion

Como podéis comprobar, crear una API Rest con Laravel y con autenticación Sanctum es muy sencillo y rápido.

He dejado todo el código en el repositorio de github https://github.com/depruebas/laravel-api-crud para que lo bajéis y probéis

Se han quedado cosas en el tintero como la gestión de errores y permisos con "abilities", permisos para hacer según que cosas por token pero esto lo veremos en un próximo artículo


Y esto es todo, feliz programming!!!
Saludos
Alex
:-)
/


Si te ha gustado el artículo compartelo en:      




Añadir un comentarios:

Nombre:
Email: (no se publica el email)




SIGUENOS EN


ARCHIVO

Publicidad

.