OpenID Connect no Node.js adiciona uma camada de identidade ao OAuth 2.0. Enquanto OAuth autoriza acesso a recursos, OpenID Connect permite que uma aplicação autentique o usuário e receba informações verificáveis em um ID Token.
O fluxo recomendado para aplicações web modernas é Authorization Code com PKCE. A aplicação redireciona o usuário ao provedor, recebe um código curto, troca esse código no backchannel e valida rigorosamente o ID Token.
Conceitos principais
- Authorization Server ou OpenID Provider: autentica o usuário;
- Client: aplicação Node.js;
- ID Token: JWT com dados da autenticação;
- Access Token: credencial para APIs;
- UserInfo: endpoint opcional de atributos;
- Discovery: documento com endpoints e capacidades.
Discovery
O provedor publica:
https://issuer.example/.well-known/openid-configurationO documento informa issuer, authorization endpoint, token endpoint, JWKS URI e algoritmos suportados. Valide o issuer esperado e use HTTPS.
Authorization Code com PKCE
Gere valores criptograficamente aleatórios:
import crypto from 'node:crypto';
const state = crypto.randomBytes(32).toString('base64url');
const nonce = crypto.randomBytes(32).toString('base64url');
const codeVerifier = crypto.randomBytes(64).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');Armazene state, nonce e codeVerifier na sessão temporária vinculada ao navegador.
Redirecionamento
const url = new URL(metadata.authorization_endpoint);
url.searchParams.set('client_id', clientId);
url.searchParams.set('response_type', 'code');
url.searchParams.set('redirect_uri', redirectUri);
url.searchParams.set('scope', 'openid profile email');
url.searchParams.set('state', state);
url.searchParams.set('nonce', nonce);
url.searchParams.set('code_challenge', codeChallenge);
url.searchParams.set('code_challenge_method', 'S256');Use redirect URIs fixas e previamente cadastradas. Não aceite uma URL arbitrária do cliente.
Callback
Compare o state em tempo constante quando possível e use-o uma única vez. Depois, troque o código:
const body = new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier
});
const response = await fetch(metadata.token_endpoint, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body,
signal: AbortSignal.timeout(5000)
});Clientes confidenciais também autenticam no token endpoint conforme o método registrado.
Validando o ID Token
Valide:
- assinatura com chave do JWKS;
issexatamente igual ao issuer;audcontendo o client ID;azpquando houver múltiplas audiences;expeiat;nonceigual ao salvo;- algoritmo permitido;
auth_timequando exigido.
Não aceite alg escolhido livremente pelo token.
ID Token não é Access Token
O ID Token é destinado ao cliente e descreve autenticação. Não o use para chamar APIs. O Access Token possui audience e regras próprias.
Subject estável
Identifique o usuário pelo par iss + sub. E-mail pode mudar e não é necessariamente verificado ou único.
const externalIdentity = `${claims.iss}|${claims.sub}`;Claims
Trate claims como dados do provedor. Verifique email_verified antes de usar e-mail em fluxos sensíveis. Não conceda permissões administrativas apenas por domínio de e-mail sem política adicional.
UserInfo
Quando necessário, chame o endpoint com Access Token:
const userInfo = await fetch(metadata.userinfo_endpoint, {
headers: { authorization: `Bearer ${accessToken}` },
signal: AbortSignal.timeout(3000)
});Confirme que o sub retornado corresponde ao ID Token.
Sessão da aplicação
Depois de validar a identidade, crie uma sessão própria com cookie:
Set-Cookie: __Host-session=...; Path=/; Secure; HttpOnly; SameSite=LaxNão armazene tokens grandes diretamente em cookie sem avaliar tamanho e exposição. Um identificador opaco de sessão costuma ser mais simples.
Refresh Tokens
Solicite offline_access somente quando necessário. Armazene refresh token criptografado, faça rotação e revogue em logout ou suspeita de comprometimento.
Consulte Refresh Tokens no Node.js.
Logout
Logout local remove a sessão da aplicação. Logout no provedor pode usar end_session_endpoint, quando suportado. Valide o redirecionamento pós-logout.
Prompt e max_age
prompt=login força nova autenticação. max_age limita idade da sessão no provedor. Use para operações sensíveis que exigem reautenticação.
ACR e AMR
acr pode representar nível de garantia e amr métodos usados, como senha e MFA. Só confie nesses valores se houver contrato claro com o provedor.
Multiple issuers
Em SaaS com vários provedores, selecione issuer a partir de configuração confiável do tenant, não diretamente de URL enviada pelo usuário. Faça discovery apenas em allowlist para evitar SSRF.
Mix-up attacks
Vincule a transação ao issuer e valide o issuer da resposta. Não reutilize o mesmo callback sem saber qual provedor iniciou o fluxo.
Clock skew
Permita pequena tolerância de relógio, mantendo servidores sincronizados. Tolerância grande enfraquece expiração.
Observabilidade
Registre:
- issuer técnico;
- resultado do fluxo;
- erro público categorizado;
- duração de discovery e token exchange;
- falhas de state e nonce;
- algoritmo e kid;
- renovações e logouts.
Nunca registre tokens, códigos ou secrets.
Testes
- state incorreto;
- nonce incorreto;
- issuer e audience inválidos;
- token expirado;
- kid desconhecido e rotação;
- código reutilizado;
- redirect URI inválida;
- UserInfo com sub divergente;
- sessão e logout;
- provedor indisponível.
Erros comuns
- confundir OAuth com autenticação;
- não validar nonce;
- identificar usuário por e-mail;
- usar ID Token como Access Token;
- discovery em URL arbitrária;
- armazenar refresh token sem proteção;
- aceitar redirect dinâmico;
- registrar tokens;
- não fixar algoritmos.
Conclusão
OpenID Connect no Node.js fornece autenticação federada sobre OAuth 2.0. O fluxo seguro depende de PKCE, state, nonce e validação completa do ID Token.
Use iss e sub como identidade, crie sessão própria e proteja refresh tokens. Trate discovery e múltiplos provedores como entradas controladas para evitar SSRF e confusão de issuer.
Consulte a especificação OpenID Connect Core e o documento de Discovery.




