Skip to content

Como empezar a usar sanctum en Laravel

Posted on:15 de octubre de 2023 at 02:00 p. m.

Sanctum es un paquete que nos ayuda a manejar la auntenticación para una API o una aplicación SPA

A continuación vamos a ver un ejemplo de uso del paquete

Para empezar

Instalar el paquete con composer

composer require laravel/sanctum

Publicar la configuración y crear las tablas para su funcionamiento

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Agregar trait al modelo USER

use Laravel\Sanctum\HasApiTokens;
 
class User extends Authenticatable
{
  use HasApiTokens;
}
Registrar usuario

A continuación se especifica cómo se puede registrar un usuario

Agregar Test
<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class RegisterControllerTest extends TestCase
{
    use RefreshDatabase;
    use WithFaker;

    /** @test */
    public function itCanRegisterAnUser(): void
    {
        $data = [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'password' => $this->faker->password()
        ];

        $response = $this->post(route('api.register'), $data, [
            'Accept' => 'application/json'
        ]);

        $response->assertJsonMissingValidationErrors();
        $response->assertStatus(201);
        $response->assertJsonFragment([
            'message' => trans('auth.user_created_message'),
            'user' => [
                'name' => $data['name'],
                'email' => $data['email'],
            ]
        ]);

        $this->assertDatabaseCount('users', 1);
        $this->assertDatabaseHas('users', [
            'name' => $data['name'],
            'email' => $data['email'],
        ]);
    }

    /** @test */
    public function itCanValidatedWhenRegisterAnUser(): void
    {
        $user = User::factory()->create();

        $data = [
            'name' => '',
            'email' => $user->email,
            'password' => ''
        ];

        $response = $this->post(route('api.register'), $data, [
            'Accept' => 'application/json'
        ]);

        $response->assertJsonValidationErrors(['name', 'email', 'password']);
        $response->assertStatus(422);
    }
}
Agregar Form Request
<?php

namespace App\Domain\Auth\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    /**
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required',
        ];
    }
}
Agregar Controller
<?php

namespace App\Domain\Auth\Controllers;

use App\Domain\Auth\Requests\RegisterRequest;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;

class RegisterController extends Controller
{
    public function __invoke(RegisterRequest $request): JsonResponse
    {
        $user = User::query()->create([
            'name' => $request->get('name'),
            'email' => $request->get('email'),
            'password' => bcrypt($request->get('password')),
        ]);

        return response()->json([
            'message' => trans('auth.user_created_message'),
            'user' => $user->only(['name', 'email']),
        ], 201);
    }
}
Agregar Ruta
Route::post('register', RegisterController::class)->name('api.register');

Realizar el Login

A continuación se especifica cómo se usa sanctum para realizar la autenticación

Agregar Test
<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class LoginControllerTest extends TestCase
{
  use RefreshDatabase;

  /** @test */
  public function itCanLogin(): void
  {
      $user = User::factory()->create();

      $response = $this->post(route('api.login'), [
          'email' => $user->email,
          'password' => 'password'
      ], ['accept' => 'application/json']);

      $response->assertStatus(200);
      $response->assertJsonMissingValidationErrors(['email', 'password']);
      $response->assertJsonStructure([
          'access_token'
      ]);
  }

  /** @test */
  public function itCanValidatedPasswordWrongWhenLogin(): void
  {
      $user = User::factory()->create();

      $response = $this->post(route('api.login'), [
          'email' => $user->email,
          'password' => 'passwordwrong'
      ], ['accept' => 'application/json']);

      $response->assertStatus(422);
      $response->assertJsonFragment([
          'message' => trans('auth.failed'),
      ]);
  }

  /** @test */
  public function itCanValidatedWhenLogin(): void
  {
      $response = $this->post(route('api.login'), [
          'email' => 'email@',
          'password' => ''
      ], ['accept' => 'application/json']);

      $response->assertStatus(422);
      $response->assertJsonValidationErrors(['email', 'password']);
  }
}
Agregar Form Request
<?php

namespace App\Domain\Auth\Requests;

use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    /**
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'email' => 'required|email',
            'password' => 'required',
        ];
    }
}
Agregar Controller
<?php

namespace App\Domain\Auth\Controllers;

use App\Domain\Auth\Requests\LoginRequest;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;

class LoginController extends Controller
{
    public function __invoke(LoginRequest $request): JsonResponse
    {
        if (!Auth::attempt($request->only('email', 'password'))) {
            return response()->json([
                'message' => trans('auth.failed'),
            ], 422);
        }

        $user = User::query()->where('email', $request->get('email'))->first();
        $token = $user->createToken(Str::random())->plainTextToken;

        return response()->json([
            'access_token' => $token,
        ]);
    }
}
Agregar Ruta
Route::post('login', LoginController::class)->name('api.login');

Proteger una ruta

El paquete nos brinda el middleware auth:sanctum para restringir el acceso a partes del sistema

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

Usar el token que se obtiene al realizar el login

Para realizar peticiones usando el access token es necesario enviar una cabecera de Authorization como un Bearer token

curl -H "Authorization: Bearer access_token" https://api.tusitio.com/resource-private

Para más información

Repositorio
Documentación oficial