POST /auth/login
Login com e-mail/senha
/auth/loginAuth: públicoAutentica um usuário com e-mail + senha, retorna um access JWT (curto, em memória do frontend) e seta um cookie HttpOnly com o refresh token (longo, rotacionável).
Use este endpoint apenas para o fluxo de login local. Se a conta foi criada via Google e ainda não tem senha, use OAuth ou primeiro POST /auth/set-password.
Quando chamar
- No formulário de login do app, após o usuário preencher e-mail e senha
- Não chamar em loop após
401— o backend NÃO bloqueia tentativas e o usuário pode causar bloqueio do próprio IP por rate limiting de borda
Request
| Campo | Tipo | Requerido | Descrição |
|---|---|---|---|
| string (email) | sim | ||
| password | string | sim |
Notas:
emailé case-sensitive no nível de DB; normalização em lower-case fica a cargo do frontendpasswordé enviado em plain text via HTTPS — o hash bcrypt acontece no servidor
Response
200
| Campo | Tipo | Requerido | Descrição |
|---|---|---|---|
| accessToken | string | sim | JWT short-lived (15min). Refresh é via cookie HttpOnly. |
| user | object | sim |
401
| Campo | Tipo | Requerido | Descrição |
|---|---|---|---|
| error | string | sim | Mensagem ou código tipado do erro. Códigos canônicos: EMAIL_ALREADY_EXISTS, INVALID_CREDENTIALS, INVALID_RESET_TOKEN, PASSWORD_ALREADY_SET, USER_NOT_FOUND. |
Sobre o accessToken: JWT assinado com JWT_SECRET, claims { userId, email }, validade 15 minutos. Use no header Authorization: Bearer <token> em requests subsequentes. Guarde em memória, não em localStorage.
Sobre o cookie refreshToken:
HttpOnly— JavaScript não consegue ler (proteção contra XSS)Secureem produção — só vai por HTTPSSameSite=strictem produção,laxem dev — proteção contra CSRFPath=/— mandado em todas as requests pro backendExpires=now() + REFRESH_TOKEN_EXPIRES_DAYS(default 7)
Erros
| Status | Códigos possíveis (extraídos do schema) |
|---|---|
| 401 | Mensagem ou código tipado do erro |
Como resolver cada caso:
Código (no campo error) | Causa | Como resolver |
|---|---|---|
INVALID_CREDENTIALS ou mensagem "Credenciais inválidas" | E-mail não existe OU existe mas a senha não bate OU a conta foi criada via Google e não tem password_hash ainda | Verifique a senha. Se a conta foi criada via Google, faça login social ou peça set-password. Não revele ao usuário qual dos casos — o backend retorna o mesmo erro pra evitar enumeration de e-mails. |
⚠️ Mensagem retornada hoje em PT (legacy) — o frontend deve tratar error === 'Credenciais inválidas' como o código tipado equivalente. Refator pra INVALID_CREDENTIALS planejado.
Exemplos
curl -X POST 'http://localhost:3003/auth/login' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer SEU_ACCESS_TOKEN' \
-d '{}'await fetch('http://localhost:3003/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + accessToken,
},
body: JSON.stringify({}),
})Resposta de sucesso (200)
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZjEyLi4uIiwiZW1haWwiOiJqb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTQzMjAwMDAsImV4cCI6MTcxNDMyMDkwMH0.SIGNATURE",
"user": {
"id": "5f12abcd-1234-5678-9abc-def012345678",
"email": "joe@example.com",
"displayName": "Joe Gamer"
}
}E no header Set-Cookie:
refreshToken=8a7bf4...64; Path=/; HttpOnly; SameSite=Lax; Expires=Mon, 06 May 2026 12:00:00 GMTSide effects
- DB:
INSERT INTO refresh_tokens (user_id, token_hash, expires_at, created_at)— uma sessão nova - Cookie: setado no header de resposta
- Sem notificações disparadas
- Sem eventos de socket emitidos
- Sem jobs enfileirados
Relacionados
- Conceito: Autenticação — entenda o esquema de tokens
- Módulo: Auth — visão completa do fluxo de login com e-mail/senha
- Endpoint:
POST /auth/refresh— renovar o access token sem re-login - Endpoint:
POST /auth/logout— encerrar a sessão atual - Tabela:
refresh_tokens— onde a sessão é guardada - Tabela:
users— onde a senha é validada