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
- Instalação e Configuração do Laravel Sanctum: Autenticação SPA + Autenticação por Token
- Processo de Registro/Login/Logout: Abordagem de Duplo Canal (Web Session + API Token)
- Capacidades e Permissões de Token: Controle Granular via campo "abilities"
- Isolamento de autenticação multitenant: Middleware e scope com reconhecimento de tenant
- Processo de Redefinição de Senha e Verificação de Email
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.
// 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
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# Cria: tabela personal_access_tokens
(2) Configuração
// 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
// 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
# .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:
# Comando executado com sucesso
4. Processo de Registro/Login/Logout
(1) Registro
// 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
// 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
// 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
// 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:
// Execução bem-sucedida
5. Capacidades e Permissões de Token
(1) Criar um Token com abilities
// 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
// 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
// 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:
// Execução bem-sucedida
6. Isolamento de Autenticação Multitenant
(1) Diagrama de Sequência de Autenticação por Token do Sanctum
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
// 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
// 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
// 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:
// Execução bem-sucedida
7. Redefinição de Senha e Verificação de Email
(1) Redefinição de Senha
// 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
// 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
// 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:
// Execução bem-sucedida
8. Exemplo Abrangente: Sistema de Autenticação Completo do ShopMetrics
// ============================================
// 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
withCredentials: true, e o Sanctum gerencia automaticamente o CSRF e as sessões.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.$user->tokens()->delete() revoga todos os tokens do usuário; $user->currentAccessToken()->delete() revoga apenas o token atual.📖 Resumo
- Sanctum unifica os dois mecanismos de autenticação: Web Session e API Token
- O modo SPA usa cookies/sessões para autenticação, eliminando a necessidade de gerenciar tokens
- O modo Token usa
plainTextTokenpara fornecer acesso à API para clientes de terceiros - Token abilities: Ajuste refinado de permissões para evitar conceder acesso excessivo
- Middleware TenantResolve + TenantScope para implementar isolamento de autenticação multitenant
- Verificação de email e redefinição de senha estão prontas para uso imediato
📝 Exercícios
-
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.
-
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. -
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.



