Skip to main content

Emitir, renovar e inspecionar tokens — API

POST /oauth/token é o endpoint OAuth2 de tokens: o campo grant_type decide a operação — trocar um authorization_code (PKCE) por tokens, autenticar máquina-a-máquina (client_credentials) ou rotacionar um refresh_token. POST /oauth/introspect permite a um client OAuth consultar se um access token ainda está ativo (RFC 7662).

Funcionamento​

POST /oauth/token: valida o corpo (grant_type restrito a três valores + client_id) e roteia por grant_type:

  • authorization_code — confere o código pelo hash (não consumido, não expirado), o client_id e o redirect_uri, e o PKCE (SHA-256(code_verifier) em base64url deve igualar o code_challenge, comparado com timingSafeEqual). Emite access_token sempre; id_token apenas se o escopo openid foi pedido; refresh_token apenas se o client suporta o grant refresh_token. O código é marcado como consumido (uso único).
  • refresh_token — confere o refresh token pelo hash e pelo client_id; se já foi usado antes (reuso detectado) ou a sessão associada não está mais ativa, revoga toda a família de refresh tokens daquela sessão. Em sucesso, emite novo access_token e rotaciona o refresh_token (mesma família); não reemite id_token.
  • client_credentials — confere client_id + client_secret (hash comparado com timingSafeEqual) e que o client tem um vínculo organizacional; emite um access_token de máquina — nunca emite refresh_token para este grant.

POST /oauth/introspect: autentica o client chamador com client_id/client_secret e só depois avalia o token informado, pelo mesmo caminho usado para autenticar qualquer requisição (verificação de assinatura RS256 + status da sessão). Qualquer falha nessa verificação — token inválido, expirado, sessão revogada — responde 200 { active: false }, sem erro HTTP. Só funciona para tokens do tipo access token (JWT); um refresh token apresentado aqui sempre volta active: false, porque não é um JWT verificável por assinatura.

Endpoints​

MétodoRotaDescrição
POST/v1/oauth/tokenEmite ou renova tokens (authorization_code, refresh_token, client_credentials)
POST/v1/oauth/introspectConsulta se um access token está ativo

Versão: v1

Swagger:

Rota (Dev): http://localhost:3000/v1/oauth/token

Lógica de decisão por grant_type e da introspecção:

Permissões​

RotaGuardsAcesso
POST /oauth/tokennenhumPúblico (a segurança vem do PKCE, do client_secret ou do refresh token)
POST /oauth/introspectnenhum (autenticação de client dentro do use-case)Público, mediante client_id/client_secret válidos

Não existe rota dedicada de revogação (/oauth/token/revoke, RFC 7009). O que existe é DELETE /oauth/client-sessions/current, que revoga a sessão OAuth associada ao access token do bearer atual — não recebe um token arbitrário no corpo, então não é equivalente ao RFC 7009. Essa rota não está documentada em detalhe aqui (fora do escopo desta página).

Headers​

HeaderObrigatórioDescrição
Content-TypeSimapplication/json

Path parameters​

Nenhum.

Query parameters​

Nenhum.

Body​

authorization_code — IdentityOAuthTokenRequest:

json
{
"grant_type": "authorization_code",
"client_id": "mobile-app",
"code": "opaque-authorization-code",
"code_verifier": "<verificador original em texto>",
"redirect_uri": "https://app.exemplo.com/callback"
}

client_credentials:

json
{
"grant_type": "client_credentials",
"client_id": "ai-service",
"client_secret": "segredo-do-client",
"scope": "reports:read"
}

refresh_token:

json
{ "grant_type": "refresh_token", "client_id": "mobile-app", "refresh_token": "opaque-refresh-token" }
CampoObrigatórioValidação
grant_typeSim@IsIn(['authorization_code', 'client_credentials', 'refresh_token']) — só estes três valores; qualquer outro é rejeitado com 400 pelo ValidationPipe, antes de chegar ao controller
client_idSim@IsString (sem formato fixo)
code, redirect_uriExigidos via @ValidateIf quando grant_type=authorization_code—
code_verifierExigido pelo caso de uso quando grant_type=authorization_coderegex ^[A-Za-z0-9\-._~]{43,128}$
client_secretExigido via @ValidateIf quando grant_type=client_credentials—
refresh_tokenExigido via @ValidateIf quando grant_type=refresh_token—
scopeNãoopcional em qualquer grant

POST /oauth/introspect — IdentityOAuthIntrospectionRequest:

json
{ "client_id": "ai-service", "client_secret": "segredo-do-client", "token": "access-token-jwt" }
CampoObrigatórioValidação
client_idSim@IsString
client_secretSim@IsString
tokenSim@IsString
token_type_hintNão@IsIn(['access_token', 'refresh_token']) — aceito no DTO mas ignorado pela implementação; qualquer token que não seja um JWT de access token válido responde active: false

Response​

200 — authorization_code / refresh_token (IdentityOAuthTokenResponse):

json
{ "access_token": "eyJ...", "id_token": "eyJ...", "refresh_token": "opaque...", "token_type": "Bearer", "expires_in": 3600 }
  • client_credentials: só access_token + token_type + expires_in — sem refresh_token e sem id_token.
  • refresh_token: access_token + refresh_token novos — sem id_token.

200 — POST /oauth/introspect, token ativo:

json
{ "active": true, "sub": "8f2a...", "client_id": "web-portal", "scope": "openid profile", "iss": "http://localhost:3000", "aud": "identity-api", "iat": 1750000000, "exp": 1750003600, "token_type": "Bearer" }

200 — token inativo/inválido: { "active": false }

Erros​

RotaClasse de erroerrorCodeStatusQuando ocorre
/token(validação de payload)BAD_REQUEST400grant_type fora da lista fechada, ou campos do grant faltando
/token (authorization_code)(Value Objects)IDENTITY_OAUTH_AUTHORIZATION_CODE_INVALID / IDENTITY_OAUTH_PKCE_VERIFIER_INVALID400code/code_verifier mal formados
/token (authorization_code)IdentityOAuthAuthorizationCodeRejectedErrorIDENTITY_OAUTH_AUTHORIZATION_CODE_REJECTED400código inexistente/expirado/já usado, client_id/redirect_uri não batem, ou PKCE falhou
/token (client_credentials)IdentityOAuthClientCredentialsRejectedErrorIDENTITY_OAUTH_CLIENT_CREDENTIALS_REJECTED401client_secret errado, client sem vínculo organizacional, ou escopo não permitido
/token (refresh_token)(repositório de refresh token)IDENTITY_INVALID_REFRESH_TOKEN401token inexistente/expirado/reusado/sessão inativa (reuso e sessão inativa também revogam a família inteira)
/introspectIdentityInvalidOAuthClientCredentialsErrorIDENTITY_INVALID_OAUTH_CLIENT_CREDENTIALS401client_id/client_secret do chamador não conferem

Um grant_type fora da lista (invalid_grant, por exemplo) é barrado pelo ValidationPipe com 400 BAD_REQUEST genérico — não chega a existir um branch de "grant desconhecido" alcançável via HTTP nesta rota.

Regras de negócio​

IDRegraComportamento
RN-01Só 3 grants são aceitos pelo DTOauthorization_code, client_credentials, refresh_token — não há mais mobile_credentials/verify_qrcode_login neste endpoint (o QR-code tem fluxo próprio, ver Login por QR-code)
RN-02PKCE obrigatório no authorization_codecomparação timingSafeEqual do SHA-256(code_verifier) contra o code_challenge salvo
RN-03authorization_code é de uso únicoconsumido na troca; reuso → 400 IDENTITY_OAUTH_AUTHORIZATION_CODE_REJECTED
RN-04refresh_token é de uso único, com rotaçãoreuso do mesmo token revoga toda a família (proteção contra roubo de token)
RN-05client_credentials nunca emite refresh_tokenpor design — um client máquina não deve manter uma credencial de longa duração renovável
RN-06id_token só sai com escopo openidtanto na troca de código quanto — não se aplica ao refresh, que nunca reemite id_token
RN-07Access/ID token têm TTL fixo de 3600sconstante no código, não é variável de ambiente
RN-08Introspecção nunca propaga erro de token inválidoqualquer motivo de invalidade vira {active:false} com HTTP 200; só a autenticação do client chamador pode gerar 401
RN-09Introspecção só reconhece access tokenstoken_type_hint é aceito mas ignorado; um refresh token sempre volta active:false

Compliance​

Órgão / normaExigênciaComo a rota atende
OAuth2 / OIDC (RFC 6749, 7636, 7662)PKCE, código de uso único, rotação de refresh, introspecçãoimplementados conforme descrito acima
LGPDnão logar segredosclient_secret, code_verifier e tokens não aparecem em log
HIPAAtrilha de emissão/revogação de credenciaisreuso de refresh token gera evento de revogação de família

Variáveis de ambiente​

VariávelUsoDefault
IDENTITY_OAUTH_AUTHORIZATION_CODE_LIFETIME_SECONDSTTL do authorization_code600s (máx. 3600)
IDENTITY_REFRESH_TOKEN_LIFETIME_DAYSvalidade do refresh_token30 dias (máx. 365)
IDENTITY_SESSION_LIFETIME_DAYSvalidade da sessão de client M2M (reaproveitada em client_credentials)30 dias
IDENTITY_OAUTH_ISSUER / IDENTITY_OAUTH_AUDIENCEclaims iss/aud assinadas e verificadashttp://localhost:3000 / identity-api

O TTL do access/id token (3600s) não é configurável — é uma constante no código (identity-oauth-token-signer.service.ts).

Serviços consumidores​

Reportado, não verificado neste levantamento (origem: card Bitrix 296, F-S01): o serviço de IA da plataforma (mm-pacs-mobilemed-ai-api, repositório não disponível neste workspace) consumiria este endpoint via client_credentials/refresh_token, com cache Redis (TTL 280s), padrão single-flight e retry com backoff (até 4 tentativas, 1s/2s/4s). Não foi possível confirmar esse comportamento no código deste repositório — registrado apenas como contexto reportado pela origem citada.

Tempo médio de resposta​

A confirmar — responsável: time de Identity; data: 24/09/2026. Sem medição publicada.

Requisitos não funcionais​

RequisitoDefinição
IdempotênciaNão — authorization_code e refresh_token são consumidos a cada troca
Rate limitNão identificado @Throttle específico nestas duas rotas neste levantamento
CacheNão, no código deste repositório (ver nota de serviços consumidores acima para um cache externo reportado)
AuditoriaRevogação de família de refresh token é auditada; não identificado evento de auditoria dedicado à emissão comum

Divergências e lacunas confirmadas​

  • Não existe /oauth/token/revoke (RFC 7009); a revogação disponível (DELETE /oauth/client-sessions/current) tem semântica diferente (revoga a sessão do bearer atual, não um token arbitrário informado no corpo).
  • Não existem specs unitários dedicados para os casos de uso de troca de código, refresh, client_credentials ou introspecção — a cobertura vem de dois e2e (identity-oauth-discovery.e2e.spec.ts, identity-oauth-token-validation.e2e.spec.ts).

Relacionado​