Fluxo de Login — Verificação de Dispositivo
Login com verificação de dispositivo/IP: só devolve o token de acesso quando o login parte de um dispositivo já confiável. Um dispositivo novo passa antes por uma verificação de e-mail (código de 6 dígitos).
- Backend:
fastgivr-api(App\Http\Controllers\Access\Auth\AuthControllereTrustedDeviceController) - Guard:
api(JWT) - Persistência: tabela
trusted_devices+password_reset_tokens(código)
Contas de Teste Prontas
Para testar a autenticação com usuários reais populados no banco local, consulte a página Contas de Teste. Todos os usuários usam a senha padrão password.
1. Visão geral do fluxo
Ciclo de vida do dispositivo
Identificação do dispositivo. O cliente gera uma vez um UUID e o guarda localmente (ex.: localStorage), enviando-o em todo login no header X-Device-Id. O backend deriva a chave estável do dispositivo com sha256(X-Device-Id | IP | User-Agent) e usa (user_id, device_key) como identidade. Sem o header, a chave cai para sha256("" | IP | User-Agent) — o que faz o dispositivo ser re-verificado sempre que o IP muda.
2. Endpoints (Etapa 1)
Todas as respostas seguem o envelope { code, success, data|<chave>, message? }. Erros de validação: HTTP 422 com { success:false, message, data: { campo: [erros] } }.
2.1 POST /auth/login
Autentica e decide entre devolver o token ou exigir verificação de dispositivo.
| Auth | pública |
| Headers | X-Device-Id: <uuid> (opcional, recomendado) · Content-Type: application/json |
Request
{
"identifier": "joao@email.com", // e-mail, CPF/CNPJ (com ou sem máscara) ou telefone
"password": "••••••••",
"device_label": "Chrome · macOS", // opcional, apenas rótulo
}Validações — identifier: required|string|max:255; password: required|string; device_label: sometimes|nullable|string|max:120.
Respostas
| Status | Corpo (data) | Quando |
|---|---|---|
200 OK | { access_token, token_type: "bearer", expires_in, user } | dispositivo confiável |
202 Accepted | { requires_email_verification: true, challenge_token, expires_in: 900, email_hint: "jo****@email.com" } | dispositivo novo — código enviado por e-mail |
401 Unauthorized | null + message: "Credenciais inválidas." | usuário não encontrado ou senha errada |
422 | erros de validação | payload incompleto |
Efeitos colaterais
- Sempre cria/atualiza a linha em
trusted_devices(contexto:ip_address,user_agent,label,device_id). - No caso
202: gravachallenge_token(64 hex) +challenge_expires_at = now()+15mine emite um código de 6 dígitos (hash empassword_reset_tokens, TTL 1 h) enviado por e-mail (App\Mail\EmailVerificationMail).
2.2 POST /auth/device/verify
Conclui a verificação do dispositivo novo.
| Auth | pública (o challenge_token é a credencial) |
| Headers | X-Device-Id: <uuid> (o mesmo do login) |
Request
{
"challenge_token": "b1a2...c9", // recebido no 202 do /auth/login
"code": "482913", // 6 dígitos do e-mail
}Validações — challenge_token: required|string; code: required|string|size:6.
Respostas
| Status | Corpo (data) | Quando |
|---|---|---|
200 OK | { access_token, token_type: "bearer", expires_in, user } | código correto |
422 | null + message: "Desafio inválido ou expirado." | challenge_token inexistente, já usado ou vencido |
422 | null + message: "Código de verificação inválido ou expirado." | código errado ou fora do TTL de 1 h |
Efeitos colaterais (sucesso)
trusted_devices:status = trusted,trusted_at,last_used_at; limpachallenge_token/challenge_expires_at.password_reset_tokens: remove o código do usuário.users.email_verified_at: preenchido se ainda nulo.- Emite o JWT do usuário.
2.3 Sessão (já existente)
GET /auth/me · PUT /auth/profile · POST /auth/logout · POST /auth/refresh · POST /auth/send-verification-code — todos com Authorization: Bearer <access_token>.
3. Modelo trusted_devices
| Coluna | Tipo | Nota |
|---|---|---|
user_id | bigint | FK lógica para users |
device_key | string | sha256(device_id | ip | user_agent) — único por user_id |
device_id | string? | header X-Device-Id |
label | string? | rótulo amigável |
ip_address, user_agent | string / text | contexto do último login |
status | pending | trusted | enum TrustedDeviceStatus |
challenge_token | string? | token opaco enquanto pending |
challenge_expires_at | timestamp? | validade do desafio (15 min) |
trusted_at, last_used_at | timestamp? | |
deleted_at | timestamp? | soft delete |
Índice único (user_id, device_key).
4. Frontend
Implementado em fastgivr-internet-bank: LoginPage (form identifier + password, guarda access_token/user em 200 ou o challenge_token em 202) e VerifyDevicePage (código de 6 dígitos), via authApi.login / authApi.verifyDevice (src/services/auth.ts). O header X-Device-Id (UUID persistido em localStorage) é injetado pelo interceptor de src/services/api.ts, que também limpa a sessão e redireciona para /login?session=expired em qualquer 401.
O cadastro (
RegisterPage/OnboardingPage) é um fluxo separado — ver Fluxo de Cadastro.