Construindo uma API Laravel Segura com Autenticação JWT
A autenticação JWT (JSON Web Token) no Laravel fornece um mecanismo criptograficamente assinado e sem estado para verificar consumidores de API sem armazenamento de sessão no lado do servidor. Um JWT codifica um payload — tipicamente identidade do utilizador e claims — numa string compacta e segura para URL, assinada com uma chave secreta ou RSA, permitindo que qualquer serviço que possua a chave de verificação valide o token de forma independente.
Este guia abrange a implementação completa da autenticação JWT numa API Laravel usando o pacote `tymon/jwt-auth`, incluindo configuração, configuração do modelo, lógica do controlador, proteção de rotas, estratégia de atualização de tokens e reforço para produção. Cada passo inclui contexto técnico que vai além dos tutoriais superficiais.
Pré-requisitos e Pressupostos do Ambiente
Antes de começar, confirme o seguinte:
- PHP 8.1 ou superior (PHP 8.2 recomendado para Laravel 11)
- Laravel 10 ou 11 instalado via Composer
- Composer 2.x
- MySQL 8.0, PostgreSQL 15, ou qualquer base de dados compatível com PDO
- Um ficheiro `.env` configurado com credenciais `DB_*` válidas
- Familiaridade básica com o contentor de serviços, middleware e Eloquent ORM do Laravel
Se estiver a implementar esta API num ambiente de produção, um plano de Alojamento VPS com acesso root completo dá-lhe o controlo necessário para configurar PHP-FPM, gerir variáveis de ambiente de forma segura e definir permissões de ficheiros corretamente — tudo crítico para a gestão de segredos JWT.
Passo 1: Criar um Novo Projeto Laravel
“`bash
composer create-project laravel/laravel laravel-jwt-api
cd laravel-jwt-api
“`
Verifique a instalação:
“`bash
php artisan –version
“`
Defina a chave da sua aplicação se não foi gerada automaticamente:
“`bash
php artisan key:generate
“`
O `APP_KEY` em `.env` é separado do segredo JWT. Ambos são necessários e servem diferentes propósitos criptográficos — `APP_KEY` protege cookies encriptados e dados de sessão, enquanto `JWT_SECRET` assina tokens.
Passo 2: Instalar o Pacote de Autenticação JWT
O pacote `tymon/jwt-auth` é o padrão de facto para JWT no Laravel. Instale-o:
“`bash
composer require tymon/jwt-auth
“`
Publique a configuração do pacote:
“`bash
php artisan vendor:publish –provider="TymonJWTAuthProvidersLaravelServiceProvider"
“`
Isto cria `config/jwt.php`, que controla o TTL do token, TTL de atualização, algoritmo, comportamento da lista negra e claims obrigatórios. Reveja este ficheiro cuidadosamente — os valores predefinidos são razoáveis para desenvolvimento, mas requerem ajuste para produção.
Parâmetros de configuração chave em `config/jwt.php`:
| Parâmetro | Predefinição | Recomendação para Produção |
|---|
| — | — | — |
|---|
| `ttl` | 60 minutos | 15–30 minutos |
|---|
| `refresh_ttl` | 20160 minutos (2 semanas) | 1440–10080 minutos |
|---|
| `algo` | `HS256` | `RS256` para sistemas distribuídos |
|---|
| `blacklist_enabled` | `true` | Deve ser `true` |
|---|
| `blacklist_grace_period` | 0 segundos | 10–30 segundos para pedidos simultâneos |
|---|
| `required_claims` | `['iss','iat','exp','nbf','sub','jti']` | Manter todos; adicionar `aud` para APIs multi-tenant |
|---|
Passo 3: Gerar a Chave Secreta JWT
“`bash
php artisan jwt:secret
“`
Isto acrescenta `JWT_SECRET` ao seu ficheiro `.env`. Este valor é usado como chave de assinatura HMAC-SHA256 para todos os tokens quando se utiliza o algoritmo `HS256` predefinido.
Notas críticas de segurança:
- Nunca confirme `.env` no controlo de versões. Adicione-o imediatamente ao `.gitignore`.
- Num pipeline de implementação partilhado, injete `JWT_SECRET` como variável de ambiente através do seu sistema CI/CD em vez de o armazenar num ficheiro.
- Se rodar o segredo, todos os tokens existentes são imediatamente invalidados. Planeie as janelas de rotação adequadamente e comunique-as aos consumidores da API.
- Para arquiteturas de microsserviços onde múltiplos serviços devem verificar tokens, mude para `RS256`. Gere um par de chaves RSA, armazene a chave privada no serviço de autenticação e distribua apenas a chave pública aos serviços consumidores.
Passo 4: Configurar o Guard de Autenticação
Abra `config/auth.php` e atualize as secções de predefinições e guards:
“`php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
“`
Isto instrui o sistema de autenticação do Laravel a usar o driver JWT ao resolver o guard `api`. O middleware `auth:api` irá agora delegar a validação de tokens ao `tymon/jwt-auth` em vez do Laravel Passport ou do driver de token predefinido.
Não remova o guard `web`. Muitos componentes internos do Laravel dependem dele, e removê-lo causa falhas inesperadas em comandos de consola e workers de fila que interagem com o sistema de autenticação.
Passo 5: Criar o Modelo de Utilizador e a Migração
Se o modelo `User` predefinido e a migração já existirem (existem numa instalação nova do Laravel), pode modificá-los diretamente. Se começar do zero:
“`bash
php artisan make:model User -m
“`
Abra o ficheiro de migração em `database/migrations/` e defina o esquema:
“`php
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
“`
Execute a migração:
“`bash
php artisan migrate
“`
Nota de produção: Execute sempre as migrações com a flag `–force` em ambientes de produção onde `APP_ENV=production`, pois o Laravel solicitará confirmação caso contrário:
“`bash
php artisan migrate –force
“`
Passo 6: Implementar a Interface JWTSubject no Modelo de Utilizador
O pacote `tymon/jwt-auth` requer que o seu modelo autenticável implemente o contrato `JWTSubject`. Abra `app/Models/User.php`:
“`php
<?php
namespace AppModels;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateFoundationAuthUser as Authenticatable;
use IlluminateNotificationsNotifiable;
use TymonJWTAuthContractsJWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
/**
- Get the identifier that will be stored in the JWT subject claim.
*/
public function getJWTIdentifier(): mixed
{
return $this->getKey();
}
/**
- Return a key-value array of arbitrary claims to add to the JWT payload.
*/
public function getJWTCustomClaims(): array
{
return [];
}
}
“`
Sobre claims personalizados: O método `getJWTCustomClaims()` é onde incorpora dados específicos da aplicação diretamente no payload do token. Os casos de uso comuns incluem incorporar `role`, `tenant_id` ou `permissions` para que os serviços downstream possam tomar decisões de autorização sem uma consulta à base de dados. Seja deliberado sobre o que incorpora — cada claim aumenta o tamanho do token e é legível por qualquer pessoa que descodifique o payload em base64. Nunca incorpore dados sensíveis como palavras-passe ou PII.
Passo 7: Construir o Controlador de Autenticação
“`bash
php artisan make:controller AuthController
“`
Preencha `app/Http/Controllers/AuthController.php` com lógica de autenticação completa:
“`php
<?php
namespace AppHttpControllers;
use AppModelsUser;
use IlluminateHttpJsonResponse;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use IlluminateSupportFacadesHash;
use IlluminateValidationValidationException;
use TymonJWTAuthExceptionsJWTException;
use TymonJWTAuthFacadesJWTAuth;
class AuthController extends Controller
{
/**
- Register a new user and return a JWT.
*/
public function register(Request $request): JsonResponse
{
$validated = $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' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = JWTAuth::fromUser($user);
return response()->json([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60,
], 201);
}
/**
- Authenticate a user and return a JWT.
*/
public function login(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
]);
if (!$token = Auth::guard('api')->attempt($credentials)) {
return response()->json(['error' => 'Invalid credentials'], 401);
}
return $this->respondWithToken($token);
}
/**
- Invalidate the current token (logout).
*/
public function logout(): JsonResponse
{
try {
Auth::guard('api')->logout();
} catch (JWTException $e) {
return response()->json(['error' => 'Failed to invalidate token'], 500);
}
return response()->json(['message' => 'Successfully logged out']);
}
/**
- Refresh the current token.
*/
public function refresh(): JsonResponse
{
try {
$token = Auth::guard('api')->refresh();
} catch (JWTException $e) {
return response()->json(['error' => 'Token cannot be refreshed'], 401);
}
return $this->respondWithToken($token);
}
/**
- Return the authenticated user's profile.
*/
public function me(): JsonResponse
{
return response()->json(Auth::guard('api')->user());
}
/**
- Format the token response consistently.
*/
protected function respondWithToken(string $token): JsonResponse
{
return response()->json([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60,
]);
}
}
“`
Por que a especificação explícita do guard é importante: Chamar `Auth::attempt()` sem especificar o guard recorre ao guard predefinido. Se alterou o predefinido para `api`, isto funciona — mas é frágil. Chame sempre `Auth::guard('api')->attempt()` explicitamente para evitar erros subtis quando o guard predefinido muda durante a refatoração.
Passo 8: Definir Rotas de API
Abra `routes/api.php` e defina as rotas de autenticação e protegidas:
“`php
<?php
use AppHttpControllersAuthController;
use IlluminateSupportFacadesRoute;
// Public authentication routes
Route::prefix('auth')->group(function () {
Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
});
// Protected routes — require a valid JWT
Route::middleware('auth:api')->prefix('auth')->group(function () {
Route::post('logout', [AuthController::class, 'logout']);
Route::post('refresh', [AuthController::class, 'refresh']);
Route::get('me', [AuthController::class, 'me']);
});
// Example protected resource routes
Route::middleware('auth:api')->group(function () {
Route::apiResource('posts', AppHttpControllersPostController::class);
});
“`
Estratégia de prefixo de rota: Agrupar endpoints de autenticação sob `/api/auth/` é uma convenção amplamente adotada que torna a documentação da API mais limpa e simplifica as regras de limitação de taxa — pode aplicar limitação mais rigorosa a `/api/auth/login` independentemente dos endpoints de recursos.
Passo 9: Proteger Rotas e Gerir Falhas de Middleware
O middleware `auth:api` do Laravel retornará uma resposta `401 Unauthenticated` quando um token estiver em falta, expirado ou inválido. Para retornar uma resposta JSON consistente em vez de um redirecionamento HTML, substitua o método `unauthenticated` em `app/Exceptions/Handler.php`:
“`php
use IlluminateAuthAuthenticationException;
use IlluminateHttpRequest;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson() || $request->is('api/*')) {
return response()->json(['error' => 'Unauthenticated'], 401);
}
return redirect()->guest(route('login'));
}
“`
Sem esta substituição, os clientes de API recebem um redirecionamento HTML 302 para uma página de login que não existe numa aplicação puramente de API — uma fonte comum de confusão durante os testes de integração.
Passo 10: Estratégia de Atualização de Tokens e Lista Negra
A natureza sem estado do JWT cria uma tensão: os tokens são autossuficientes e válidos até à expiração, mas precisa de uma forma de os revogar no logout. O pacote `tymon/jwt-auth` resolve isto com uma lista negra de tokens suportada pelo driver de cache do Laravel.
Como funciona a lista negra:
- No logout, o claim `jti` (JWT ID) do token é armazenado na cache com um TTL correspondente ao tempo de vida restante do token.
- Em cada pedido autenticado, o middleware verifica a lista negra antes de aceitar o token.
- A definição `blacklist_grace_period` permite uma breve janela onde um token a ser atualizado ainda pode ser usado, prevenindo condições de corrida em clientes que fazem pedidos simultâneos.
Certifique-se de que o seu driver de cache suporta isto. O driver `file` predefinido funciona para implementações de servidor único. Para APIs escaladas horizontalmente a correr em múltiplos nós — comum quando se usam Servidores Dedicados numa configuração com balanceamento de carga — mude para Redis ou Memcached:
“`env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
“`
Fluxo de atualização de token:
“`
Client API Server
| — POST /api/auth/refresh ——> |
|---|
| Authorization: Bearer <old> |
|---|
| — Validate old token |
|---|
| — Blacklist old token's jti |
|---|
| — Issue new token |
|---|
| <– 200 { token: <new> } ——– |
|---|
“`
O token antigo é imediatamente colocado na lista negra após a atualização. Os clientes devem armazenar o novo token e descartar o antigo. Implemente isto como um interceptor Axios ou equivalente no seu frontend para gerir a atualização de tokens de forma transparente.
Passo 11: Testar a API
Use `curl` ou Postman para verificar cada endpoint.
Registar um utilizador:
“`bash
curl -X POST https://your-domain.com/api/auth/register
-H "Content-Type: application/json"
-d '{
"name": "Jane Smith",
"email": "jane@example.com",
"password": "SecurePass123!",
"password_confirmation": "SecurePass123!"
}'
“`
Resposta esperada (`201 Created`):
“`json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…",
"token_type": "bearer",
"expires_in": 3600
}
“`
Iniciar sessão:
“`bash
curl -X POST https://your-domain.com/api/auth/login
-H "Content-Type: application/json"
-d '{"email": "jane@example.com", "password": "SecurePass123!"}'
“`
Aceder a uma rota protegida:
“`bash
curl -X GET https://your-domain.com/api/auth/me
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…"
“`
Atualizar o token:
“`bash
curl -X POST https://your-domain.com/api/auth/refresh
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…"
“`
Terminar sessão:
“`bash
curl -X POST https://your-domain.com/api/auth/logout
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…"
“`
JWT vs. Sessão vs. Laravel Sanctum vs. Passport
Compreender quando o JWT é a escolha certa requer compará-lo com as alternativas disponíveis no ecossistema Laravel.
| Critério | JWT (`tymon/jwt-auth`) | Laravel Sanctum | Laravel Passport | Baseado em Sessão |
|---|
| — | — | — | — | — |
|---|
| **Estado** | Sem estado | Sem estado (tokens SPA) / Com estado (cookies) | Com estado (tokens em BD) | Com estado |
|---|
| **Armazenamento de token** | Lado do cliente | Lado do cliente ou cookie | Base de dados | Sessão no servidor |
|---|
| **Revogação** | Lista negra (cache) | Imediata (eliminação em BD) | Imediata (eliminação em BD) | Imediata |
|---|
| **Escalabilidade** | Excelente (sem BD por pedido) | Boa | Moderada (consulta à BD por pedido) | Fraca (sincronização de sessão necessária) |
|---|
| **Suporte OAuth2** | Não | Não | Sim (servidor OAuth2 completo) | Não |
|---|
| **Complexidade** | Média | Baixa | Alta | Baixa |
|---|
| **Melhor para** | APIs sem estado, microsserviços | SPAs, aplicações móveis | Clientes OAuth de terceiros | Aplicações web tradicionais |
|---|
| **Introspecção de token** | Descodificar payload no lado do cliente | Requer chamada à API | Requer chamada à API | N/A |
|---|
Quando escolher JWT em vez de Sanctum: Se a sua API é consumida por clientes de terceiros, aplicações móveis ou microsserviços que não podem partilhar um cookie de sessão ou ligação à base de dados com a sua aplicação Laravel, a natureza autossuficiente do JWT é uma vantagem significativa. Se estiver a construir uma SPA de primeira parte no mesmo domínio, o Sanctum com autenticação baseada em cookies é mais simples e evita completamente as preocupações de segurança no armazenamento de tokens.
Reforço de Segurança para Produção
Uma implementação JWT funcional não é segura por predefinição. Aplique estas medidas de reforço antes de entrar em produção:
1. Impor HTTPS incondicionalmente
Os tokens JWT transmitidos via HTTP são facilmente intercetáveis. Imponha TLS ao nível do servidor web e redirecione todo o tráfego HTTP. Combine isto com um Certificado SSL para garantir transporte encriptado em cada pedido à API.
2. Definir TTLs de token agressivos
Tokens de acesso de curta duração (15–30 minutos) combinados com tokens de atualização de maior duração (7–14 dias) limitam o impacto de um token roubado. Atualize `config/jwt.php`:
“`php
'ttl' => 15,
'refresh_ttl' => 10080,
“`
3. Aplicar limitação de taxa aos endpoints de autenticação
O middleware throttle integrado do Laravel previne ataques de força bruta:
“`php
Route::middleware(['throttle:10,1'])->group(function () {
Route::post('auth/login', [AuthController::class, 'login']);
Route::post('auth/register', [AuthController::class, 'register']);
});
“`
Isto limita cada IP a 10 pedidos por minuto nos endpoints de autenticação.
4. Validar o claim `aud` para APIs multi-tenant
Se a sua API serve múltiplas aplicações cliente, incorpore e valide um claim `audience` para prevenir a reutilização de tokens entre serviços:
“`php
// In getJWTCustomClaims()
return [
'aud' => config('app.jwt_audience'),
];
“`
5. Proteger o JWT_SECRET ao nível do SO
Defina permissões de ficheiro restritivas em `.env`:
“`bash
chmod 640 .env
chown www-data:www-data .env
“`
Num VPS com cPanel devidamente configurado, pode gerir a propriedade e permissões de ficheiros através do Gestor de Ficheiros ou SSH sem risco de expor segredos a outros utilizadores no sistema.
6. Registar eventos de autenticação
Integre o sistema de eventos do Laravel para registar tentativas de login falhadas, atualizações de tokens e logouts num serviço de registo centralizado. Isto é essencial para a deteção de anomalias.
Adicionar Controlo de Acesso Baseado em Funções
Os claims personalizados tornam simples incorporar funções diretamente no token:
“`php
// In User model
public function getJWTCustomClaims(): array
{
return [
'role' => $this->role, // e.g., 'admin', 'editor', 'viewer'
];
}
“`
Crie um middleware para impor requisitos de função:
“`php
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use TymonJWTAuthFacadesJWTAuth;
class RoleMiddleware
{
public function handle(Request $request, Closure $next, string $role): mixed
{
$payload = JWTAuth::parseToken()->getPayload();
if ($payload->get('role') !== $role) {
return response()->json(['error' => 'Forbidden'], 403);
}
return $next($request);
}
}
“`
Registe-o em `app/Http/Kernel.php` (Laravel 10) ou `bootstrap/app.php` (Laravel 11) e aplique-o às rotas:
“`php
Route::middleware(['auth:api', 'role:admin'])->group(function () {
Route::apiResource('admin/users', AdminUserController::class);
});
“`
Advertência: Os dados de função incorporados no token são apenas tão atuais quanto o próprio token. Se a função de um utilizador mudar, o token antigo continua a conceder a função antiga até expirar ou ser atualizado. Para alterações de função de alta segurança (por exemplo, revogar acesso de administrador imediatamente), combine a lista negra de tokens com um TTL curto ou realize uma verificação de função na base de dados no middleware juntamente com a verificação de claim.
Considerações de Implementação para APIs Laravel JWT
Ao passar do desenvolvimento local para um servidor de produção, vários fatores específicos do ambiente afetam o comportamento do JWT:
- Consistência de fuso horário: Os claims `iat`, `nbf` e `exp` do JWT são timestamps Unix. Certifique-se de que o fuso horário do seu servidor está definido para UTC (`date.timezone = UTC` em `php.ini`) para evitar rejeição de tokens por desvio de relógio.
- OPcache: Ative o OPcache do PHP para reduzir a sobrecarga de carregar ficheiros da biblioteca JWT em cada pedido. Isto é especialmente impactante em APIs de alto tráfego.
- Workers de fila para limpeza de tokens: Se implementar tarefas de limpeza de lista negra de tokens personalizadas, certifique-se de que o worker de fila está a correr como um processo supervisionado (Supervisor ou systemd).
- Gestão de variáveis de ambiente: Em Painéis de Controlo VPS, use o gestor de variáveis de ambiente do painel em vez de editar `.env` diretamente em produção, para evitar substituições acidentais durante as implementações.
Lista de Verificação de Decisão Antes de Entrar em Produção
Use esta lista de verificação para confirmar que a sua implementação está pronta para produção:
- `JWT_SECRET` tem pelo menos 32 caracteres, gerado aleatoriamente e não confirmado no controlo de versões
- `blacklist_enabled` está definido como `true` em `config/jwt.php`
- O TTL do token é de 30 minutos ou menos para tokens de acesso
- O TTL de atualização está definido para um valor adequado à sua política de sessão
- Todos os endpoints da API são servidos exclusivamente via HTTPS
- A limitação de taxa é aplicada aos endpoints `/login` e `/register`
- O handler de exceções `unauthenticated` retorna JSON, não um redirecionamento HTML
- Os claims personalizados não contêm palavras-passe, segredos ou PII sensível
- O driver de cache é Redis ou Memcached (não `file`) em implementações multi-servidor
- Os eventos de autenticação são registados e monitorizados
- As alterações de função que requerem efeito imediato são tratadas via lista negra, não apenas pela expiração de claims
- As permissões do ficheiro `.env` estão restritas ao utilizador do servidor web
FAQ
Qual é a diferença entre `JWT_SECRET` e `APP_KEY` no Laravel?
`APP_KEY` é usado pelo serviço de encriptação do Laravel para encriptar cookies, dados de sessão e valores passados através de `Crypt::encrypt()`. `JWT_SECRET` é usado exclusivamente pelo `tymon/jwt-auth` para assinar e verificar JSON Web Tokens. São criptograficamente independentes e servem propósitos completamente diferentes. Ambos devem ser mantidos em segredo.
Por que o meu token JWT continua a retornar 401 mesmo que não tenha expirado?
As causas mais comuns são: o token foi colocado na lista negra (por exemplo, após um logout ou atualização), o `JWT_SECRET` foi rodado após a emissão do token, o driver de cache que armazena a lista negra está indisponível, ou existe um desvio de relógio entre o servidor emissor e o servidor de validação que excede a definição `leeway` em `config/jwt.php`. Verifique cada um destes por ordem.
Posso usar autenticação JWT com filas ou comandos de consola do Laravel?
O JWT foi concebido para autenticação de pedidos HTTP. Dentro de tarefas de fila ou comandos Artisan, não existe contexto de pedido HTTP, pelo que não pode resolver um utilizador a partir de um token via middleware. Em vez disso, passe a chave primária do utilizador como parâmetro da tarefa e carregue o modelo diretamente com `User::find($userId)`.
Como gerir pedidos simultâneos durante a atualização de tokens sem obter erros 401?
Defina `blacklist_grace_period` em `config/jwt.php` para um valor entre 10 e 30 segundos. Durante esta janela, um token que acabou de ser atualizado (e tecnicamente colocado na lista negra) ainda será aceite. Isto previne condições de corrida em clientes que enviam múltiplos pedidos simultâneos enquanto uma atualização está em curso.
O `tymon/jwt-auth` é compatível com Laravel 11?
A partir do ciclo de lançamento atual, a versão `tymon/jwt-auth` `2.x` suporta Laravel 10 e 11 via branch `dev-develop` ou lançamentos com tag que declaram compatibilidade. Verifique sempre as restrições `composer.json` do pacote e o rastreador de problemas do GitHub antes de atualizar versões do Laravel num projeto que depende deste pacote. Considere fixar a versão do pacote em `composer.json` para evitar alterações inesperadas durante `composer update`.
