id_token via JWKS, respostas ilustrativas de cada endpoint, 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_id(YOUR_CLIENT_ID) e oclient_secret(YOUR_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).O fluxo em uma imagem
O documento de descoberta é a fonte de verdade dos endpoints. Um
GET no .well-known/openid-configuration do tenant devolve algo assim (resposta ilustrativa; o schema completo fica na API Reference):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 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
Guarde o
verifier e o state antes de sair da página (você vai precisar deles no callback).3
Troque o code por tokens
A Cativa redireciona o usuário pra O
https://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 anterior./token responde 200 OK com (resposta ilustrativa; schema completo na API Reference):4
Pegue o perfil e faça login local
/userinfo responde 200 OK com (resposta ilustrativa):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:
id_token tem esta forma (ilustrativa):
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 (ilustrativa) 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, redirect_uri não autorizada
error: invalid_request, 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. A resposta de erro tem a forma:error: invalid_grant, Código de autorização expirado
error: invalid_grant, 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, Código de autorização já utilizado
error: invalid_grant, 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, client_secret inválido
error: invalid_client, 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, Verificação PKCE falhou
error: invalid_grant, 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.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.Login federado (IdP da empresa)
O fluxo inverso: a Cativa como Relying Party, autenticando membros pelo IdP da própria empresa.
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.