Estágio 02 · 02-13
LockedAuth é o subsistema onde mais erros caros se cometem. "Login funciona" não basta, você precisa entender token leakage, replay, CSRF, XSS reflection of secret, refresh flows, cookie attributes, RBAC vs ABAC, mistura de OAuth2 com OIDC, expiração e revogação, federation. A maioria das brechas em apps modernos vem de auth implementado por intuição.
Este módulo é auth com clareza: distinção entre autenticação e autorização, sessions com cookie, JWT (e suas armadilhas), OAuth2 grants com OIDC pra identidade, MFA, passkeys (WebAuthn), e modelo de autorização. Sem isso, você terceiriza pra Auth0/Clerk e ainda implementa errado em redor.
Erros comuns vêm de misturar: tokens que provam identidade mas servem como "blank check" de permissão.
Nunca armazene plain text. Nunca SHA-256 simples. Use algoritmos de hashing pra senhas (slow, salted):
Cada hash inclui salt. Custo (work factor) ajustável; aumente a cada N anos.
Verificação:
import { hash, verify } from 'argon2';
const h = await hash(password); // store h
const ok = await verify(h, candidate); // compares
Não compare strings com == em código de auth (timing attacks). Use crypto.timingSafeEqual ou bibliotecas que já fazem.
Padrão clássico, ainda viável e em muitos casos melhor que JWT:
{sessionId → userId, expiresAt, ...} em store (Redis, DB).Set-Cookie: sid=....Atributos cruciais:
HttpOnly: JS não acessa (mitiga XSS exfiltrar).Secure: só HTTPS.SameSite=Lax (default moderno) ou Strict (mais restritivo) ou None (cross-site, exige Secure). Mitiga CSRF.Path=/ ou específico.Max-Age / Expires.__Host- prefix força Secure + Path=/ + sem Domain.Vantagens sessions:
Desvantagens:
JWT é spec (RFC 7519). Estrutura: header.payload.signature (base64-url encoded).
{ alg, typ }.HMAC(header + payload, secret) (HS256) ou RSA/ECDSA sign (RS256/ES256).Vantagens:
Desvantagens reais:
alg: none, key confusion (HS vs RS), bad signature comparison, expired token aceito.Quando JWT vence: APIs entre serviços, mobile com token em secure storage, identity provider pra múltiplos serviços. Quando session vence: app web monolítico.
Padrão moderno híbrido: refresh token longo-lived em cookie HttpOnly + access token JWT short-lived em memória do client. Refresh roda quando access expira.
alg: none: algumas libs aceitavam token sem assinatura. Verifique sempre.alg: HS256 mas server usando key pública RSA como secret HMAC. Atacante assina com a public key.exp não verificado: lib mal usada não checa expiração.iss/aud checks: token de outro tenant aceito.Authorization: Bearer ... ou cookie.OAuth2 (RFC 6749) é framework pra autorização delegada: "deixe app B acessar recurso meu em provider A". Não foi desenhado pra autenticação, mas usado errado pra isso por anos.
OIDC (OpenID Connect) é camada sobre OAuth2 que adiciona identidade: ID token (JWT) com claims de quem é o user. Pra "Login com Google", você usa OIDC.
Grants OAuth2:
PKCE (Proof Key for Code Exchange), extensão pra Authorization Code. Cliente gera code_verifier, manda hash (code_challenge) na auth request; troca code por token enviando code_verifier. Mitiga interceptação do code. Sempre use PKCE, mesmo em SPAs e apps mobile.
https://provider/authorize?response_type=code&client_id=X&redirect_uri=Y&scope=openid+email&state=...&code_challenge=...&code_challenge_method=S256.redirect_uri?code=ABC&state=....state (CSRF guard), troca code + code_verifier em /token endpoint → recebe access_token, id_token, refresh_token.id_token (assinatura via JWKS, iss, aud, nonce, exp).OIDC providers comuns: Google, GitHub (GitHub não é OIDC strict, é OAuth2 + API), Apple, Microsoft, Auth0, Clerk, Keycloak, Authentik.
Fatores:
Formas:
otplib no Node.WebAuthn (W3C Level 3 em 2024) + CTAP2 (FIDO Alliance) são o substrato técnico. Passkey é o termo de marketing pra credentials WebAuthn que sincronizam entre devices via iCloud Keychain, Google Password Manager, 1Password, Bitwarden. Em 2025-2026 viraram default em Apple/Google/Microsoft accounts, GitHub, Stripe.
Modelo criptográfico:
challenge, recebe attestation (assinada). Pública é guardada no server, associada ao user.assertion (challenge + clientDataJSON + authenticatorData assinados). Verifica com pública.clientDataJSON, phishing falha porque attacker em domínio errado não consegue produzir assinatura válida pro origin real.Tipos de credential (decisão importante):
| Tipo | Onde reside | Sincroniza? | Backup | Use case |
|---|---|---|---|---|
| Synced passkey (default 2024+) | iCloud / Google PM / 1Password | Sim | Sim (cloud do provider) | Consumer apps. UX prioridade. |
Device-bound (authenticatorAttachment: "platform") | Secure enclave do device | Não | Não | High-assurance: banking, gov |
| Roaming (security key física: YubiKey) | Hardware key | Manual | Não | Enterprise admins, dev signing |
Server-side flow (registration):
// 1. Server gera options
const options = await generateRegistrationOptions({
rpName: 'Logistica',
rpID: 'logistica.com', // domain, ESSENCIAL pra anti-phishing
userID: bytesFromUuid(user.id),
userName: user.email,
attestationType: 'none', // 'direct' se quiser auditar fabricantes (corporate)
excludeCredentials: existingCreds.map((c) => ({ id: c.credentialID, type: 'public-key' })),
authenticatorSelection: {
residentKey: 'preferred', // 'required' pra usernameless login
userVerification: 'preferred',
},
});
// 2. Cliente chama navigator.credentials.create(options)
// 3. Server verifica resposta com verifyRegistrationResponse, persiste credentialID + publicKey + counter
Pegadinhas reais:
rpID precisa ser registrable suffix do origin. logistica.com cobre app.logistica.com mas não acme.io. Subdomain isolation é decisão consciente.counter em authenticatorData: detecta clonagem. Cresce em cada uso. Se vier menor que o último guardado: alerta (possível attacker com cópia). Synced passkeys reportam counter=0 sempre, não dá pra detectar clone via counter em passkey synced. Trade-off conhecido.mediation: 'conditional'): mostra autofill de passkey direto no input. Implementação do navegador. Default em 2025+.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() e degrade pra password+OTP.Server libs (2026):
@simplewebauthn/server (de longe o mais usado e atualizado).github.com/go-webauthn/webauthn.webauthn package.webauthn-rs.Quando NÃO usar passkey:
Estratégia de migração pragmática (2025+):
Apple, Google, Microsoft já fizeram esse caminho. Stripe e GitHub também. Vale seguir.
Login sem senha: user digita email; server manda link com token único; clicar autentica.
Pontos:
Cross-Site Request Forgery: site malicioso induz user logado a enviar request indesejado pro server.
Mitigações:
SameSite=Lax ou Strict em cookies de sessão (default moderno faz quase tudo).X-Requested-With ou similar (browser bloqueia em cross-origin).Em APIs JWT em header Authorization, CSRF é menos relevante (browser não envia automaticamente). Mas se você usa cookie pra JWT, vira problema.
XSS = injeção de JS no app. Se atacante executa JS:
CSP (Content Security Policy) é defesa em camadas: limita origens de scripts, mitiga XSS impact. Em aplicações modernas, configure CSP.
user X is editor of doc Y).Implementação:
roles, role_permissions, user_roles. Middleware checa.OpenFGA, SpiceDB, baseados em Zanzibar.Em apps típicos, comece RBAC. Se permissions ficam complexas (compartilhamento granular tipo Google Drive), ReBAC é o caminho.
Tenant isolation:
tenantId claim.WHERE tenant_id = ?). Postgres RLS (Row Level Security) pode ser última linha.Cuidado com:
Tipo:
Single Sign-On em corporações: SAML 2.0 (legado, ainda predominante em enterprise) ou OIDC (moderno).
samlify, passport-saml).Just-In-Time Provisioning: ao primeiro login via SSO, criar conta automaticamente.
SCIM (System for Cross-domain Identity Management): API pra IdP gerenciar usuários no app.
Decisão: managed (Auth0/Clerk) acelera mas vendor lock e custo escalando. Self-hosted (Keycloak, Authentik) flexível, op heavier. Lib + DB próprio (Lucia, Better-Auth) controle máximo, código maior.
OWASP Top 10 pra auth (Identification and Authentication Failures): credential stuffing, brute force, weak password reset, session fixation, etc.
Defesas:
Você precisa, sem consultar:
alg: none JWT existiu e como evita.Implementar auth completo do Logística, sem libs auth-as-a-service.
argon2, jose (JWT), @simplewebauthn/server (passkeys), otplib (TOTP).POST /auth/signup, email + senha, validação (zxcvbn ou similar pra força).POST /auth/login, verifica senha, emite session.__Host-sid HttpOnly + Secure + SameSite=Lax.POST /auth/logout revoga (delete Redis).POST /auth/logout-all revoga todas sessions do user.otpauth://totp/...)./auth/passkey/register e /auth/passkey/login usando WebAuthn.GET /auth/google redireciona pra Google com PKCE + state.GET /auth/google/callback valida state, troca code, valida ID token via JWKS, cria/upgrade conta./auth/mobile/login retorna access (10 min) + refresh (30 dias) tokens.lojista, courier, admin.requireRole(['admin']).tenantId./auth/login (5 falhas em 15 min, Redis).auth_events (user, type, ip, ua, ts).passport-tudo (você implementa o flow OIDC manualmente; pode usar openid-client pra parsing).oidc-provider.subtle em edge.Destrava
02-13 é prereq dos seguintes módulos: