id_token via JWKS, comportamento esperado em erros e o que fazer com o token quando ele expira.
Prefere aprender por exemplo? O repo
cativa-sso tem apps de referência prontos pra rodar. A pasta login-com-cativa (Node + Express) implementa todos os passos deste guia, ponta a ponta.Cenário
Sua empresa tem um app — pode ser uma SPA React em outro domínio, um portal Rails com sessões server-side, ou um app iOS/Android nativo. Sua comunidade já vive numa Cativa e você quer que o usuário entre nesse app com a mesma conta que ele usa na comunidade, sem criar credencial separada. A Cativa expõe um IdP OIDC standard por tenant. Qualquer biblioteca OIDC client (oidc-client-ts, auth0/spa-js, next-auth, passport-openidconnect, AppAuth-iOS, AppAuth-Android) consegue falar com ele a partir do discovery URL do tenant.
Pré-requisitos
- Um OAuth App registrado no Console — vá em app.cativa.digital/admin/developers, aba OAuth Apps, clique Criar app. Anote o
client_ide oclient_secret(o secret aparece uma única vez). - Um redirect URI registrado no mesmo app. Pode adicionar múltiplos (ex: produção + staging + localhost).
- O
customerName(slug do tenant) — confirme com o admin da comunidade. É o subdomínio público da Cativa daquele tenant.
Os endpoints SSO da Cativa seguem OIDC e são organizados por tenant:
https://apis.cativalab.digital/tenant/api/v2/sso/{customerName}/.... O documento de descoberta fica em https://apis.cativalab.digital/tenant/api/v2/sso/{customerName}/.well-known/openid-configuration e lista todos os endpoints e algoritmos suportados (S256 para PKCE, ES256 para id_token).Decida qual fluxo usar
- SPA com PKCE
- Backend Node/Express
- Mobile com deep link
Quando usar: seu app é um frontend estático (React/Vue/Svelte) servido por CDN, sem backend confiável pra guardar 1. Gere
O 2. Redirecione para o
Não persista o
client_secret. PKCE (Proof Key for Code Exchange) substitui o segredo do client por um par verifier/challenge gerado a cada login.1. Gere code_verifier e code_challenge
O code_verifier é uma string aleatória que fica no navegador. O code_challenge é o SHA-256 do verifier, base64url-encoded — esse vai pro /authorize.2. Redirecione para o /authorize
3. Trate o callback
A Cativa redireciona o usuário prahttps://meuapp.com/callback?code=...&state=.... Valide o state e troque o code por tokens.Como SPA não pode guardar client_secret, o /token aceita PKCE como prova: você manda o code_verifier original (não o challenge) e a Cativa recomputa o SHA-256 e compara com o code_challenge que ela guardou da etapa 2.4. Pegue o perfil e faça login local
access_token em localStorage — ele é vulnerável a XSS. Mantenha em memória (estado da SPA) ou em sessionStorage se aceitar perder a sessão entre abas.Validar o id_token via JWKS
O id_token retornado pelo /token é um JWT assinado em ES256. Você deve validar a assinatura antes de confiar nas claims (sub, email, etc.) — isso evita que um atacante substitua o token por um falsificado.
A chave pública pra validar fica no JWKS do tenant:
Node.js com jose
Python com PyJWT
Sessão e renovação de token
Oaccess_token retornado pelo SSO da Cativa é de curta duração (configurado no OAuth App, default 1h). Escolha a estratégia que se encaixa:
-
Renovar com
refresh_token(recomendado) — toda troca deauthorization_codejá devolve umrefresh_token(opaco). Troque-o por umaccess_tokennovo quando o atual expirar:A resposta traz umaccess_tokennovo e umrefresh_tokennovo — os tokens são rotacionados, então guarde o refresh novo e descarte o antigo (ele é invalidado no uso). -
Re-login silencioso quando expira — quando
expires_inchegar a 0, redirecione o usuário pro/authorizede novo. Se ele ainda tem sessão na Cativa, o IdP devolve um novo code sem pedir senha de novo (single sign-on). -
Trate o token como sessão atrelada ao seu app — guarde apenas a identidade (
sub,email) na sua sessão e gere o seu próprio JWT/cookie. Oaccess_tokenda Cativa é usado só no momento do login.
Logout
Hoje não há um endpoint OIDCend_session dedicado pra apps OAuth externos. O fluxo recomendado é:
- No seu lado: descarte a sessão local (apague cookie/session/Keychain), redirecione o usuário pra um destino “deslogado”.
- Opcional, se quiser deslogar também da comunidade Cativa: redirecione o usuário pra
https://{customerName}.cativa.digital/logout(substituindo{customerName}pelo subdomínio do tenant).
Endpoint OIDC standard
end_session para parceiros externos está em desenvolvimento. Quando disponível, esta página será atualizada com cURL e exemplo de redirect com post_logout_redirect_uri.Erros comuns
`error: invalid_request, error_description: redirect_uri não autorizada`
`error: invalid_request, error_description: redirect_uri não autorizada`
A
redirect_uri enviada pra /authorize (e re-confirmada no /token) precisa ser idêntica a uma das URIs cadastradas no OAuth App do Console. Compare com cuidado: trailing slash, http vs https, e maiúsculas/minúsculas em paths importam.`error: invalid_grant, error_description: Código de autorização expirado`
`error: invalid_grant, error_description: Código de autorização expirado`
O
code retornado pelo /authorize tem TTL curto (alguns minutos). Se sua troca demora — porque o backend redireciona pra outra rota antes, ou porque você está depurando manualmente — o code expira. Sempre faça a troca imediatamente no callback.`error: invalid_grant, error_description: Código de autorização já utilizado`
`error: invalid_grant, error_description: Código de autorização já utilizado`
O
code é single-use. Se seu callback é chamado duas vezes (ex: usuário deu reload na tela de callback), a segunda troca falha com esse erro. Isso é esperado — apenas redirecione pra home da SPA quando você já tiver os tokens em sessão.`error: invalid_client, error_description: client_secret inválido`
`error: invalid_client, error_description: client_secret inválido`
Verifique:
- Você não confundiu
client_ideclient_secret. - O secret não foi rotacionado no Console (gerar novo invalida o anterior).
- Não tem espaço/quebra de linha extra na variável de ambiente (acontece com
cat .envquando o arquivo veio de Windows).
`error: invalid_grant, error_description: Verificação PKCE falhou`
`error: invalid_grant, error_description: Verificação PKCE falhou`
O
code_verifier enviado no /token não bate com o code_challenge enviado no /authorize. Causas comuns: você gerou um novo verifier antes do callback (perdeu o original), está fazendo SHA-256 sobre bytes diferentes (UTF-8 vs ASCII), ou está aplicando base64 padrão em vez de base64url.`401 Unauthorized` no /userinfo
`401 Unauthorized` no /userinfo
O usuário consegue logar mas o `id_token` não passa na minha validação JWKS
O usuário consegue logar mas o `id_token` não passa na minha validação JWKS
Confirme:
algorithms: ['ES256'](a Cativa assina com ES256, não RS256).issuerexatamentehttps://apis.cativalab.digital/tenant/api/v2/sso/{customerName}(sem trailing slash).audienceé o seuclient_id.- Sua biblioteca está buscando o JWKS do issuer correto (e cacheando, pra não bater no endpoint a cada login).
Próximos passos
Badges como permissão
Como o
sub do id_token aparece na comunidade e como badges controlam o que esse usuário pode acessar.Quick Start: API Key
Para chamadas server-to-server (sem usuário interativo), use uma API Key em vez de OAuth.
App de exemplo (GitHub)
Implementações de referência prontas pra rodar (Node + Express). A pasta
login-com-cativa implementa exatamente este fluxo: discovery, PKCE, troca de token e validação via JWKS.