404 Not Found

404 Not Found


nginx

Sistema de Autenticação do Laravel—Sanctum

Sanctum é o "guardião" do Laravel—ele gerencia autenticação baseada em sessão para páginas web e autenticação baseada em token para APIs, fornecendo dois sistemas de autenticação em um único framework.

1. O Que Você Vai Aprender


2. Uma História Real de um Engenheiro de Segurança

(1) Problema: Incidentes de Segurança Causados por Esquemas de Autenticação API Confusos

Bob usou JWT para a API do ShopMetrics e sessões para a web—dois sistemas de autenticação separados, com estados de login do usuário não sincronizados. Depois que Alice fez login pelo navegador, ela ainda tinha que obter um token separado para chamadas de API, que frequentemente expirava, causando interrupções nas operações. Ainda mais seriamente, Charlie descobriu que um único token de API podia acessar dados de todos os tenants—não havia isolamento de tenant, representando um risco significativo de vazamento de dados entre tenants.

(2) Solução com "Sanctum"

Sanctum unifica a autenticação Web e API—no modo SPA, o navegador e a API compartilham uma sessão; no modo token, ele fornece tokens de API para clientes de terceiros; e o campo abilities permite controle refinado de permissões.

PHP
// Autenticação SPA — compartilha sessão entre web e API
// Basta garantir que o middleware do Sanctum esteja nas rotas da API
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

// Autenticação por token — para clientes de terceiros
$token = $user->createToken('api-token', ['read-orders', 'write-products']);
// O token só pode fazer o que suas abilities permitem

(3) Resultado

Após o single sign-on de Bob, a experiência SPA de Alice é transparente e sem interrupções, as permissões de token de Charlie são precisamente restritas a "somente leitura de pedidos," e o risco de vazamento de dados entre tenants é reduzido a zero.


3. Instalação e Configuração do Sanctum

(1) Instalação

BASH
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# Cria: tabela personal_access_tokens

(2) Configuração

PHP
// config/sanctum.php
'middleware' => [
    'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
    'encrypt_cookies'   => App\Http\Middleware\EncryptCookies::class,
],

'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
    '%s%s',
    'localhost,localhost:3000,localhost:5173,127.0.0.1,127.0.0.1:8000',
    env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : '',
))),

'expiration' => null, // Token nunca expira (ou defina em minutos)

(3) Configuração do Front-End SPA

JAVASCRIPT
// resources/js/bootstrap.js
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.withCredentials = true; // Envia cookies com requisições da API
Opção de Configuração Função Valor Padrão
stateful Domínio certificado para SPA localhost:3000, etc.
expiration Tempo de expiração do token null (nunca expira)
guard Guardião de verificação web

(1) ▶ Exemplo: Configuração de Autenticação SPA do Sanctum

BASH
# .env — Configurar domínios SPA
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000,localhost:5173
SESSION_DRIVER=redis

# Instalar Sanctum e configurar
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Saída:

TEXT
# Comando executado com sucesso

4. Processo de Registro/Login/Logout

(1) Registro

PHP
// app/Http/Controllers/Auth/RegisterController.php
class RegisterController extends Controller
{
    public function register(RegisterRequest $request): JsonResponse
    {
        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
            'tenant_id' => $request->tenant_id,
            'role' => 'tenant_owner',
        ]);

        event(new Registered($user));

        Auth::login($user);

        return response()->json([
            'user' => $user,
            'message' => 'Registration successful.',
        ], 201);
    }
}

(2) Login

PHP
// app/Http/Controllers/Auth/LoginController.php
class LoginController extends Controller
{
    public function login(LoginRequest $request): JsonResponse
    {
        if (!Auth::attempt($request->only('email', 'password'))) {
            throw ValidationException::withMessages([
                'email' => ['The provided credentials are incorrect.'],
            ]);
        }

        $request->session()->regenerate();

        return response()->json([
            'user' => Auth::user(),
            'message' => 'Login successful.',
        ]);
    }

    public function logout(Request $request): JsonResponse
    {
        Auth::guard('web')->logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return response()->json(['message' => 'Logged out.']);
    }
}

(3) Autenticação e Login Baseados em Token

PHP
// app/Http/Controllers/Auth/ApiTokenController.php
class ApiTokenController extends Controller
{
    public function login(LoginRequest $request): JsonResponse
    {
        $user = User::where('email', $request->email)->first();

        if (!$user || !Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['Invalid credentials.'],
            ]);
        }

        $token = $user->createToken(
            $request->device_name ?? 'api-token',
            $request->abilities ?? ['*'],
        );

        return response()->json([
            'user' => $user,
            'token' => $token->plainTextToken,
        ]);
    }

    public function logout(Request $request): JsonResponse
    {
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Token revoked.']);
    }
}
Modo Método de Login Persistência de Estado Cenários Adequados
SPA Session Login por formulário Cookies/Sessions Front-end SPA
Token Obter plainTextToken String de token Mobile/Terceiros

(1) ▶ Exemplo: Roteamento de Autenticação de Duplo Modo do ShopMetrics

PHP
// routes/web.php — Autenticação por sessão para SPA
Route::post('/login', [LoginController::class, 'login']);
Route::post('/logout', [LogoutController::class, 'logout'])->middleware('auth');
Route::post('/register', [RegisterController::class, 'register']);

// routes/api.php — Autenticação por token para API
Route::post('/tokens', [ApiTokenController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
    Route::delete('/tokens/current', [ApiTokenController::class, 'logout']);
    Route::get('/user', fn (Request $request) => $request->user());
});

Saída:

TEXT
// Execução bem-sucedida

5. Capacidades e Permissões de Token

(1) Criar um Token com abilities

PHP
// Criar token com abilities específicas
$token = $user->createToken('analytics-read', [
    'read-analytics',
    'read-orders',
]);

// Token de acesso total
$token = $user->createToken('admin-token', ['*']);

// Token com expiração
$token = $user->createToken('temp-token', ['read-orders'], now()->addDays(7));

(2) Verificar Capacidades do Token

PHP
// No controller ou middleware
if ($request->user()->tokenCan('read-analytics')) {
    return AnalyticsResource::collection($analytics);
}

// No middleware
class CheckAbilities
{
    public function handle($request, $next, ...$abilities)
    {
        foreach ($abilities as $ability) {
            if (!$request->user()->tokenCan($ability)) {
                abort(403, 'Insufficient permissions.');
            }
        }
        return $next($request);
    }
}

(3) Matriz de Permissões de Abilities

Papel Abilities Descrição
Super Admin * Permissões totais
Tenant Owner read-*, write-* Permissões totais dentro do tenant
Analyst read-analytics, read-orders Somente leitura
API Client read-products, write-orders Apenas terceiros

(1) ▶ Exemplo: Controle de Acesso por Token do ShopMetrics

PHP
// app/Http/Controllers/Api/TokenController.php
class TokenController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'abilities' => 'sometimes|array',
            'abilities.*' => 'in:read-analytics,read-orders,write-orders,read-products,write-products',
            'expires_in_days' => 'sometimes|integer|min:1|max:365',
        ]);

        $abilities = $validated['abilities'] ?? match ($request->user()->role) {
            'super_admin' => ['*'],
            'tenant_owner' => ['read-analytics', 'read-orders', 'write-orders', 'read-products', 'write-products'],
            'analyst' => ['read-analytics', 'read-orders', 'read-products'],
            default => [],
        };

        $expiresAt = isset($validated['expires_in_days'])
            ? now()->addDays($validated['expires_in_days'])
            : null;

        $token = $request->user()->createToken(
            $validated['name'],
            $abilities,
            $expiresAt,
        );

        return response()->json([
            'token' => $token->plainTextToken,
            'abilities' => $abilities,
        ], 201);
    }
}

Saída:

TEXT
// Execução bem-sucedida

6. Isolamento de Autenticação Multitenant

(1) Diagrama de Sequência de Autenticação por Token do Sanctum

100%
sequenceDiagram
    participant C as Cliente
    participant S as Sanctum
    participant DB as Banco de Dados
    participant M as Middleware

    C->>S: POST /api/tokens (email + senha)
    S->>DB: Buscar usuário + verificar senha
    DB-->>S: Usuário encontrado
    S->>DB: Criar personal_access_token
    DB-->>S: Token criado
    S-->>C: Retornar plainTextToken

    C->>M: GET /api/shops (Bearer token)
    M->>DB: Buscar token em personal_access_tokens
    DB-->>M: Token + usuário + abilities
    M->>M: Verificar habilidade tokenCan()
    M->>M: Verificar escopo do tenant
    M-->>C: Retornar dados no escopo do tenant

(2) Middleware de Autenticação do Tenant

PHP
// app/Http/Middleware/TenantResolve.php
class TenantResolve
{
    public function handle(Request $request, Closure $next)
    {
        $user = $request->user();
        if (!$user || !$user->tenant_id) {
            abort(403, 'No tenant associated.');
        }

        $tenant = $user->tenant;
        if ($tenant->status !== 'active') {
            abort(403, 'Tenant account is suspended.');
        }

        // Definir contexto de tenant para todas as queries
        app()->instance(Tenant::class, $tenant);

        return $next($request);
    }
}

(3) Scope Multitenant

PHP
// app/Models/TenantScope.php
class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        if ($tenant = app()->make(Tenant::class)) {
            $builder->where($model->getTable() . '.tenant_id', $tenant->id);
        }
    }
}

// Aplicar aos modelos
class Shop extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new TenantScope);
    }
}

(1) ▶ Exemplo: Roteamento de Autenticação Multitenant do ShopMetrics

PHP
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
    // Todas as rotas da API exigem resolução de tenant
    Route::middleware('tenant.resolve')->prefix('v1')->group(function () {
        Route::apiResource('shops', Api\ShopController::class);
        Route::apiResource('products', Api\ProductController::class);
        Route::apiResource('orders', Api\OrderController::class)
            ->only(['index', 'show', 'update']);

        // Analytics — requer ability específica
        Route::get('analytics', [Api\AnalyticsController::class, 'index'])
            ->middleware('ability:read-analytics');

        // Rotas somente para admin
        Route::middleware('ability:*')->prefix('admin')->group(function () {
            Route::apiResource('plans', Api\Admin\PlanController::class);
            Route::apiResource('tenants', Api\Admin\TenantController::class);
        });
    });
});

Saída:

TEXT
// Execução bem-sucedida

7. Redefinição de Senha e Verificação de Email

(1) Redefinição de Senha

PHP
// routes/web.php
Route::post('/forgot-password', [PasswordResetController::class, 'sendResetLink']);
Route::post('/reset-password', [PasswordResetController::class, 'reset']);

// app/Http/Controllers/PasswordResetController.php
class PasswordResetController extends Controller
{
    public function sendResetLink(Request $request): JsonResponse
    {
        $request->validate(['email' => 'required|email|exists:users']);
        $status = Password::sendResetLink($request->only('email'));
        return response()->json([
            'message' => $status === Password::RESET_LINK_SENT
                ? 'Reset link sent to your email.'
                : 'Unable to send reset link.',
        ]);
    }
}

(2) Verificação de Email

PHP
// O modelo deve implementar MustVerifyEmail
class User extends Model implements MustVerifyEmail
{
    use Notifiable, VerifiesEmails;
}

// Rotas protegidas — somente usuários verificados
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

// Verificação de email por API
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/email/verification-notification', function (Request $request) {
        $request->user()->sendEmailVerificationNotification();
        return response()->json(['message' => 'Verification link sent.']);
    });
});

(1) ▶ Exemplo: Configuração Completa do Processo de Autenticação do ShopMetrics

PHP
// bootstrap/app.php — Configurar middleware de autenticação
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'tenant.resolve' => TenantResolve::class,
        'ability' => CheckAbilities::class,
    ]);
})

// Modelo User com verificação
class User extends Authenticatable implements MustVerifyEmail
{
    use HasApiTokens, Notifiable;

    protected $fillable = [
        'tenant_id', 'name', 'email', 'password', 'role', 'email_verified_at',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];

    public function tenant(): BelongsTo
    {
        return $this->belongsTo(Tenant::class);
    }
}

Saída:

TEXT
// Execução bem-sucedida

8. Exemplo Abrangente: Sistema de Autenticação Completo do ShopMetrics

PHP
// ============================================
// Abrangente: Sistema de Autenticação do ShopMetrics
// Abrange: autenticação SPA, autenticação por token, abilities, isolamento de tenant
// ============================================

// routes/api.php — Rotas completas de autenticação da API
Route::prefix('auth')->group(function () {
    Route::post('/register', [Auth\RegisterController::class, 'register']);
    Route::post('/login', [Auth\ApiTokenController::class, 'login']);
    Route::post('/forgot-password', [Auth\PasswordResetController::class, 'sendResetLink']);
    Route::post('/reset-password', [Auth\PasswordResetController::class, 'reset']);

    Route::middleware('auth:sanctum')->group(function () {
        Route::get('/user', fn (Request $r) => $r->user()->load('tenant'));
        Route::post('/logout', [Auth\ApiTokenController::class, 'logout']);
        Route::post('/email/verification', function (Request $request) {
            $request->user()->sendEmailVerificationNotification();
            return response()->json(['message' => 'Verification email sent.']);
        });
        Route::post('/tokens', [Auth\TokenController::class, 'store']);
        Route::get('/tokens', fn (Request $r) => $r->user()->tokens);
        Route::delete('/tokens/{id}', fn (Request $r, $id) => $r->user()->tokens()->where('id', $id)->delete());
    });
});

// Rotas protegidas da API
Route::middleware(['auth:sanctum', 'verified', 'tenant.resolve'])
    ->prefix('v1')->group(function () {
        Route::apiResource('shops', Api\ShopController::class);
        Route::apiResource('products', Api\ProductController::class);
        Route::apiResource('orders', Api\OrderController::class)->only(['index', 'show', 'update']);
        Route::get('analytics/overview', [Api\AnalyticsController::class, 'overview'])
            ->middleware('ability:read-analytics');
        Route::post('reports/generate', [Api\ReportController::class, 'generate'])
            ->middleware('ability:write-reports');
    });

❓ Perguntas Frequentes

P Qual é a diferença entre Sanctum e Passport?
R Sanctum é leve e suporta sessões SPA e autenticação simples por token; Passport fornece uma implementação completa de OAuth2 (código de autorização, implícito, credenciais do cliente, etc.). Sanctum é suficiente para a maioria das aplicações; use Passport apenas se precisar de OAuth 2.0.
P Onde os tokens são armazenados no modo SPA?
R O modo SPA não usa tokens! Ele usa cookies/sessões para autenticação, assim como aplicações web tradicionais. O front-end só precisa definir withCredentials: true, e o Sanctum gerencia automaticamente o CSRF e as sessões.
P Onde os tokens devem ser armazenados no front-end?
R Em dispositivos móveis, armazene-os no Keychain ou Keystore; para SPAs usando o modo token, armazene-os no localStorage (que carrega risco de XSS) ou em um cookie HttpOnly. Melhor prática: Use o modo de sessão para SPAs para evitar armazenar tokens.
P Como defino um token para expirar?
R Defina expiration (em minutos) em config/sanctum.php, ou passe o terceiro parâmetro, $user->createToken('name', ['*'], now()->addDays(30)), ao criar o token. Tokens expirados são automaticamente invalidados.
P Como um sistema multitenant impede que tokens sejam usados entre tenants?
R No middleware TenantResolve, verifique se o tenant_id do usuário ao qual o token pertence corresponde ao tenant da requisição. Alternativamente, informações do tenant podem ser codificadas nas abilities do token.
P Como revogo todos os tokens?
R $user->tokens()->delete() revoga todos os tokens do usuário; $user->currentAccessToken()->delete() revoga apenas o token atual.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Configure a autenticação SPA do Sanctum, implemente três endpoints de API para registro, login e logout, e use o Postman para testar o processo de autenticação por sessão.

  2. Exercício Avançado (⭐⭐): Implemente o modelo de autenticação baseada em token, crie um token com abilities e escreva um middleware para verificar permissões tokenCan() garantindo que o papel "analyst" possa apenas ler dados.

  3. Desafio (⭐⭐⭐): Implemente isolamento multitenant usando o TenantScope e o middleware TenantResolve para garantir que tokens de API possam acessar apenas dados de seu próprio tenant, e que requisições entre tenants retornem um erro 403.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%