Autenticação com Laravel Sanctum: guia prático de implementação

laravel sanctum
Compartilhar:

O Laravel Sanctum é um sistema de autenticação leve para SPAs (Single Page Applications) e APIs simples. Ele oferece autenticação baseada em tokens e suporte a autenticação stateful via cookies para aplicações no mesmo domínio.

Neste artigo, veremos uma implementação prática de autenticação com Sanctum em um sistema Laravel, cobrindo configuração, criação de tokens, proteção de rotas e boas práticas de segurança.

Leia também: Como Instalar e Usar Quasar em uma Aplicação Laravel

Configuração Inicial

Instalação e Configuração

Após instalar o Sanctum via Composer, é necessário publicar o arquivo de configuração:

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

O arquivo de configuração config/sanctum.php permite personalizar o comportamento do Sanctum:

<?php

use Laravel\Sanctum\Sanctum;

return [

    /*
    |--------------------------------------------------------------------------
    | Stateful Domains
    |--------------------------------------------------------------------------
    |
    | Requests from the following domains / hosts will receive stateful API
    | authentication cookies. Typically, these should include your local
    | and production domains which access your API via a frontend SPA.
    |
    */

    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
        '%s%s',
        'localhost,localhost:9000,127.0.0.1,127.0.0.1:9000,::1',
        Sanctum::currentApplicationUrlWithPort()
    ))),

    /*
    |--------------------------------------------------------------------------
    | Sanctum Guards
    |--------------------------------------------------------------------------
    |
    | This array contains the authentication guards that will be checked when
    | Sanctum is trying to authenticate a request. If none of these guards
    | are able to authenticate the request, Sanctum will use the bearer
    | token that's present on an incoming request for authentication.
    |
    */

    'guard' => ['web'],

    /*
    |--------------------------------------------------------------------------
    | Expiration Minutes
    |--------------------------------------------------------------------------
    |
    | This value controls the number of minutes until an issued token will be
    | considered expired. If this value is null, personal access tokens do
    | not expire. This won't tweak the lifetime of first-party sessions.
    |
    */

    'expiration' => null,

    /*
    |--------------------------------------------------------------------------
    | Sanctum Middleware
    |--------------------------------------------------------------------------
    |
    | When authenticating your first-party SPA with Sanctum you may need to
    | customize some of the middleware Sanctum uses while processing the
    | request. You may change the middleware listed below as required.
    |
    */

    'middleware' => [
        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
        'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
    ],

];

Principais configurações:

  • stateful: domínios que recebem autenticação via cookies
  • guard: guards de autenticação verificados
  • expiration: tempo de expiração dos tokens (null = sem expiração)

Middleware no Kernel

O middleware do Sanctum deve ser adicionado ao grupo api no Kernel.php:

        'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

Modelo User com HasApiTokens

O modelo User deve usar o trait HasApiTokens do Sanctum:

use Laravel\Sanctum\HasApiTokens;
use OwenIt\Auditing\Contracts\Auditable;
use Illuminate\Support\Facades\DB;

class User extends Authenticatable implements Auditable
{
    use \OwenIt\Auditing\Auditable;
    use HasApiTokens, HasFactory, Notifiable;

O trait HasApiTokens fornece métodos para gerenciar tokens:

  • createToken(): cria um novo token
  • tokens(): relacionamento com os tokens
  • currentAccessToken(): token atual da requisição

Controller de Autenticação

Login

O método login valida credenciais, autentica o usuário e gera um token:

    public function login(AuthRequest $request) {

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

        if (!$user || !Hash::check($request->password, $user->password)) {
            return response(
                ['message' => 'Nome/Login ou senha inválidos.'],
                401
            );
        } else {

            $credentials = $request->validate([
                'email' => ['required', 'email'],
                'password' => ['required'],
            ]);
            Auth::attempt($credentials);

            $user->tokens()->delete();
            $token = $user->createToken('tkn')->plainTextToken;

            if (request()->path() == 'web/auth') {
                $request->session()->regenerate();
            }

            $response = [
                'user' => $user, 'token' => $token
            ];

            Cache::forget('permissions::of::user::' . $user->id);

            return response($response, 200);
        }
    }

Pontos importantes:

  1. Validação com Form Request (AuthRequest)
  2. Verificação de senha com Hash::check()
  3. Revogação de tokens anteriores ($user->tokens()->delete())
  4. Criação de token com createToken('tkn')->plainTextToken
  5. Retorno do usuário e do token

Logout

O método logout revoga todos os tokens do usuário:

    public function logout() {
        // Session::flush();
        // Auth::logout();
        $id_usuario = auth('sanctum')->user()->id;
        Cache::forget('permissions::of::user::' . $id_usuario);
        auth()->user()->tokens()->delete();
        return response(['message' => 'Usuário saiu com sucesso'], 201);
    }

Validação com Form Request

A validação é feita via Form Request:

class AuthRequest extends FormRequest {
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize() {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules() {
        $regra = array();
        if ((request()->path() == 'api/login') or (request()->path() == 'auth')) {
            $regra['email'] = 'required|string|email';
            $regra['password'] = 'required|string';
        }
        return $regra;
    }

    public function attributes() {
        return [
            'email' => 'E-mail',
            'password' => 'Senha',
        ];
    }

    // Sempre persiste email em lowercase e sem espaços
    protected function prepareForValidation()
    {
        // has e merge são métodos do FormRequest que são usados para verificar se o email existe e para mesclar os dados da requisição
        if ($this->has('email')) {
            $this->merge(['email' => strtolower(trim((string)$this->input('email')))]);
        }
    }
}

Rotas Protegidas

Rotas Públicas

Rotas de login e recuperação de senha ficam públicas:

Route::post('/login', [AuthController::class, 'login']);
Route::post('/sendgrid', [MailController::class, 'sendGridMail']);

Route::post('/forgetPassword', [AuthController::class, 'forgetPassword']);
Route::post('/recoverPassword', [AuthController::class, 'recoverPassword']);

// Fluxo EXTERNO de recuperação de senha (neutro)
Route::group(['prefix' => 'auth'], function () {
    Route::post('/forgot-password', [ExternalPasswordResetController::class, 'forgot'])->middleware('throttle:5,1');
    Route::post('/reset-password', [ExternalPasswordResetController::class, 'reset'])->middleware('throttle:20,1');
});

Rotas Protegidas

Rotas protegidas usam o middleware auth:sanctum:

Route::group(['middleware' => ['auth:sanctum', 'force.password.change']], function () {

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

    // Alterar senha (primeiro acesso)
    Route::post('/auth/change-password', [AuthController::class, 'changePassword']);
    Route::get('/rota', [AuthController::class, 'rota']);

Acessando o Usuário Autenticado

Em controllers protegidos, use auth('sanctum')->user():

    public function changePassword(ChangePasswordRequest $request)
    {
        $user = auth('sanctum')->user();
        if (!$user || !$user->first_login) {
            return response(['message' => 'Operação não permitida'], 403);
        }

        $password = (string) $request->input('password');

        $errors = PasswordPolicyService::validate($password, $user->email, $user->password);
        if (!empty($errors)) {
            return response(['message' => 'A senha não atende aos requisitos mínimos de segurança.', 'errors' => $errors], 400);
        }

        $user->password = Hash::make($password);
        $user->first_login = false;
        $user->save();

        // Invalida tokens existentes por segurança
        $user->tokens()->delete();

        return response(['message' => 'Senha salva com sucesso.'], 200);
    }

Boas Práticas e Segurança

Revogação de Tokens Antigos

No login, revogue tokens anteriores para evitar múltiplas sessões:

$user->tokens()->delete();
$token = $user->tokens()->createToken('tkn')->plainTextToken;

Invalidação de Tokens em Operações Sensíveis

Ao alterar a senha, invalide todos os tokens:

// Invalida tokens existentes por segurança
$user->tokens()->delete();

Validação com Form Request

Use Form Requests para centralizar validação e manter controllers enxutos.

Rate Limiting

Aplique rate limiting em rotas sensíveis:

Route::post('/forgot-password', [ExternalPasswordResetController::class, 'forgot'])->middleware('throttle:5,1');
Route::post('/reset-password', [ExternalPasswordResetController::class, 'reset'])->middleware('throttle:20,1');

Uso de Cache para Permissões

Cacheie permissões para melhorar performance:

Cache::forget('permissions::of::user::' . $user->id);

Uso no Frontend

No frontend, envie o token no header Authorization:

// Exemplo com Axios
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;

Para SPAs no mesmo domínio, o Sanctum pode usar cookies automaticamente.

Conclusão

O Laravel Sanctum oferece uma solução simples e segura para autenticação em APIs e SPAs. Seguindo as práticas acima, você terá:

  • Autenticação baseada em tokens
  • Proteção de rotas com middleware
  • Gerenciamento de tokens
  • Segurança com validação e rate limiting
  • Boa separação de responsabilidades

A implementação apresentada mostra um padrão real e funcional, pronto para uso em produção, com atenção à segurança e manutenibilidade do código.

Referências:

fullstack laravel e php

Postagens Relacionadas

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *