Autenticar com e-mail e senha — API
Autentica um usuário com e-mail e senha. Em sucesso sem segundo fator, cria uma sessão e devolve
seus dados diretamente (session_id, session_expires_at, user_id) — não existe mais um
authorization_code emitido por este endpoint. Se a conta tem TOTP ou SafeID habilitado, a rota
devolve um desafio de segundo fator em vez de autenticar; o cliente conclui em
/authentication/totp/verify ou /authentication/safe-id/verify.
Funcionamento
- Valida a origem da requisição (
IdentityOriginGuard). - Aplica rate limit (por IP e por e-mail).
- Valida o corpo (
email,password,client_id). - Normaliza o e-mail (
trim+ minúsculas) e busca o usuário ativo. - Se a conta está bloqueada no momento, recusa sem sequer conferir a senha.
- Confere a senha (bcrypt). Errada: registra a falha (pode iniciar ou agravar um bloqueio progressivo) e recusa. Certa: zera o contador de falhas.
- Se a credencial tem TOTP habilitado, emite um desafio TOTP. Senão, se tem SafeID habilitado,
emite um desafio SafeID. Senão, cria a sessão (
assurance: PASSWORD) e autentica.
Endpoints
| Método | Rota | Descrição |
|---|---|---|
| POST | /v1/authentication/login | Autentica por e-mail/senha ou inicia um desafio de MFA |
Versão: v1
Swagger: POST /authentication/login · Rota (Dev): http://localhost:3000/v1/authentication/login
Lógica de decisão da rota (validações, bloqueio e MFA → desfechos):
Permissões
Rota pública (não exige token). O controle de acesso é por origem (IdentityOriginGuard) e por
rate limit.
| Rota | Guards | Acesso |
|---|---|---|
POST /authentication/login | IdentityOriginGuard | Público, apenas de uma origem permitida |
IdentityOriginGuard aceita a requisição quando o header Origin está na lista
IDENTITY_ALLOWED_ORIGINS ou quando é um domínio próprio de uma organização (confirmado por
uma chamada síncrona ao serviço de Organization); qualquer falha nessa confirmação nega o acesso.
Sem header Origin, a requisição é sempre negada.
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
Content-Type | Sim | application/json |
Origin | Sim | Validado pelo IdentityOriginGuard; ausente ou não permitido → 403 |
user-agent | Não | Gravado na sessão e na auditoria (default "unknown") |
x-device-id | Não | Identificador de dispositivo, gravado na sessão/desafio |
Path parameters
Nenhum — não há mais tenant no path desta rota.
Query parameters
Nenhum.
Body
IdentityAuthenticationLoginRequest:
json{"email": "usuario@exemplo.com","password": "SenhaForte123!","client_id": "web-portal"}
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
email | string | Sim | @IsEmail |
password | string | Sim | @IsString @Length(8, 128) |
client_id | string | Sim | @IsString @Length(1, 128) |
Response
200 — autenticado (IdentityAuthenticationResponse):
json{"session_id": "b3f1...-uuid","session_expires_at": "2026-10-24T12:00:00.000Z","user_id": "8f2a...-uuid"}
200 — desafio TOTP:
json{ "mfa_type": "totp", "token": "opaque-challenge-token", "expires_at": "2026-09-24T12:10:00.000Z" }
200 — desafio SafeID:
json{"mfa_type": "safe_id","authorization_url": "https://pscsafeweb.safewebpss.com.br/.../authorize?...","state": "b6b6...-uuid","expires_at": "2026-09-24T12:10:00.000Z"}
Nos dois casos de desafio, o cliente conclui em MFA. Não há mais campo
authorization_code nesta resposta.
Erros
| Classe de erro | errorCode | Status | Quando ocorre |
|---|---|---|---|
| (validação de payload) | BAD_REQUEST | 400 | corpo inválido/incompleto |
ForbiddenAction (IdentityOriginGuard) | IDENTITY_AUTH_ORIGIN_FORBIDDEN | 403 | Origin ausente ou não permitida |
IdentityInvalidCredentialsError | IDENTITY_INVALID_CREDENTIALS | 401 | usuário não encontrado ou senha errada (sem bloqueio) |
IdentityAccountLockedError | IDENTITY_ACCOUNT_LOCKED | 423 | conta com bloqueio temporário ativo |
IdentityAccountLockedError | IDENTITY_ACCOUNT_PERMANENTLY_LOCKED | 423 | conta com bloqueio permanente (5ª falha na janela) |
ThrottlerException | RATE_LIMIT_EXCEEDED | 429 | limite de requisições excedido (por IP ou por e-mail) |
O Swagger do controller documenta apenas o errorCode
IDENTITY_ACCOUNT_LOCKEDno exemplo de 423; o caso permanente (IDENTITY_ACCOUNT_PERMANENTLY_LOCKED) usa o mesmo status mas não está no exemplo — divergência de documentação, não de comportamento.
Regras de negócio
| ID | Regra | Comportamento esperado |
|---|---|---|
| RN-01 | E-mail é normalizado antes da busca | trim() + minúsculas; a busca usa o e-mail já normalizado |
| RN-02 | Usuário inexistente não distingue de senha errada | ambos → 401 IDENTITY_INVALID_CREDENTIALS, sem revelar qual falhou |
| RN-03 | Bloqueio é checado antes da senha | conta já bloqueada → 423 imediato, sem gastar uma tentativa nova |
| RN-04 | Bloqueio de conta é progressivo, não plano | ver tabela de bloqueio abaixo |
| RN-05 | Sucesso zera o contador de falhas | failedLoginAttempts, lockedUntil e lastFailedLoginAt voltam a 0/null |
| RN-06 | TOTP tem prioridade sobre SafeID | se a credencial tiver os dois habilitados, o desafio emitido é o de TOTP |
| RN-07 | Sem MFA, a sessão é criada com assurance: PASSWORD | tokens emitidos depois carregam esse nível até um step-up elevar para MFA |
| RN-08 | Desafios de MFA emitidos aqui não persistem o token em claro | só o hash SHA-256 do token vai para o banco; o valor puro só existe na resposta HTTP |
Bloqueio progressivo (constantes fixas no código, não configuráveis por variável de ambiente):
| Tentativa (dentro da janela de retenção) | Efeito |
|---|---|
| 1ª e 2ª | Sem bloqueio |
| 3ª | Bloqueio temporário de 10 minutos |
| 4ª | Bloqueio temporário de 15 minutos |
| 5ª ou mais | Bloqueio permanente (lockedUntil = 9999-12-31T23:59:59.000Z) |
A janela de retenção das tentativas é de 30 minutos enquanto failedLoginAttempts < 4, e de 60
minutos a partir da 4ª falha; fora da janela, o contador reinicia em zero. Um login correto também
zera o contador imediatamente.
Compliance
| Órgão / norma | Exigência | Como a rota atende |
|---|---|---|
| LGPD | minimização; não expor se a conta existe | resposta idêntica para e-mail inexistente e senha errada; e-mail normalizado, nunca logado em claro fora de auditoria |
| HIPAA | trilha de login (quem/quando/IP/resultado) | eventos de auditoria auth.login.failed, auth.login.succeeded e auth.account.locked, com IP e user-agent |
| ANVISA (indireto) | política de bloqueio por tentativas sob controle | bloqueio progressivo auditado, sem depender de configuração externa |
Variáveis de ambiente
| Variável | Uso | Default |
|---|---|---|
IDENTITY_ALLOWED_ORIGINS | lista (CSV) de origens aceitas pelo IdentityOriginGuard | [] |
IDENTITY_TOTP_CHALLENGE_TTL_MINUTES | validade do desafio TOTP emitido aqui | 10 min |
IDENTITY_SAFE_ID_LIFETIME_SECONDS | validade do desafio SafeID emitido aqui | 600s |
IDENTITY_SESSION_LIFETIME_DAYS | validade da sessão criada em caso de sucesso | 30 dias |
IDENTITY_PASSWORD_HASH_SALT_ROUNDS | custo do bcrypt usado para conferir a senha | 12 |
Os limiares do bloqueio progressivo (3ª/4ª/5ª tentativa, janelas de 30/60 min) e os limites de rate limit (7 requisições / 60s) não são variáveis de ambiente — são constantes no código.
Tempo médio de resposta
A confirmar — responsável: time de Identity; data: 24/09/2026. Não há medição publicada; não
foi executado neste levantamento.
Requisitos não funcionais
| Requisito | Definição |
|---|---|
| Idempotência | Não — cada chamada conta para o rate limit e para o contador de falhas |
| Rate limit | 7 requisições / 60s, em dois baldes simultâneos: por IP e por e-mail normalizado |
| Cache | Não |
| Auditoria | Sim — auth.login.failed, auth.login.succeeded, auth.account.locked |
Relacionado
- 🖥️ Tela:
A confirmar — responsável: time de frontend; data: 24/09/2026.(página de interface ainda não localizada neste levantamento, que cobriu apenas o backend) - 📂 Módulo: Authentication
- 🔑 Próximo passo: Concluir com MFA (quando a resposta trouxer um desafio)