Autorizar um client OAuth — API
Endpoint de autorização OAuth2/OIDC. O GET redireciona o navegador para a experiência de
login/consentimento externa, preservando os parâmetros OAuth recebidos. O POST (com um access
token de usuário) e o POST /oauth/authorize/session (chamado por um serviço interno com
X-Service-Token) emitem o authorization_code de fato, via PKCE. O código gerado é trocado
depois em POST /oauth/token.
Funcionamento
GET /oauth/authorize: valida o pedido (client, redirect_uri, escopos) sem autenticar
ninguém, resolve o nome do client e redireciona (303) para a URL de início da experiência de
autorização do navegador (IDENTITY_OAUTH_BROWSER_AUTHORIZATION_START_URL), repassando todos os
parâmetros originais mais client_name. Não lê cookie nem sessão — é apenas o ponto de entrada.
POST /oauth/authorize: exige um access token de usuário válido (Authorization: Bearer).
Valida o client (ativo, com grant authorization_code), o redirect_uri (contra as URLs
cadastradas do client) e os escopos pedidos; confirma que o usuário do token está ativo e que a
sessão associada ainda está ativa; então emite o authorization_code (PKCE, S256 obrigatório).
POST /oauth/authorize/session: variante para uso interno (BFF), autenticada por
X-Service-Token em vez de um access token de usuário. Recebe uma decision (approve ou
deny) e, quando approve, os IDs de sessão/usuário diretamente no corpo — não precisa de um
access token de usuário. deny responde com error: access_denied sem emitir nenhum código.
SSO/
promptcontinuam sem implementação. Não há leitura de sessão de navegador nem parâmetropromptem nenhum DTO desta rota — a pendência de SSO "sessão válida → autoriza sem pedir login de novo" segue em aberto.
Endpoints
| Método | Rota | Descrição |
|---|---|---|
| GET | /v1/oauth/authorize | Redireciona para a experiência de login/consentimento |
| POST | /v1/oauth/authorize | Emite o authorization_code usando o access token do usuário |
| POST | /v1/oauth/authorize/session | Emite (ou nega) o authorization_code a partir de uma sessão, via serviço interno |
Versão: v1
Swagger: Identity — OAuth authorization · Rota (Dev): http://localhost:3000/v1/oauth/authorize
Lógica de decisão das três rotas:
Permissões
| Rota | Guards | Acesso |
|---|---|---|
GET /oauth/authorize | nenhum | Público |
POST /oauth/authorize | IdentityOAuthAccessTokenGuard | Usuário autenticado (usa o próprio access token) |
POST /oauth/authorize/session | IdentityOAuthBffServiceTokenGuard | Serviço interno com X-Service-Token válido |
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
Authorization | Sim (POST /oauth/authorize) | Bearer <access_token> |
X-Service-Token | Sim (POST /oauth/authorize/session) | Token de serviço configurado (identity.internalApi.bffServiceToken) |
Path parameters
Nenhum.
Query parameters
GET /oauth/authorize (todos repassados ao redirect):
| Nome | Tipo | Obrigatório | Validação |
|---|---|---|---|
response_type | string | Sim | @IsIn(['code']) |
client_id | string | Sim | @IsUUID |
redirect_uri | string | Sim | @IsUrl({ require_tld: false }) |
code_challenge | string | Sim | @Length(43, 128) |
code_challenge_method | string | Sim | @IsIn(['S256']) |
scope | string | Não | @Length(1, 1024) |
state | string | Não | @Length(1, 512) |
nonce | string | Não | @Length(1, 255) |
Body
POST /oauth/authorize — IdentityOAuthAuthorizationRequest:
json{"client_id": "mobile-app","redirect_uri": "https://app.exemplo.com/callback","code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM","code_challenge_method": "S256","scope": "openid profile","state": "random-state","nonce": "random-nonce"}
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
client_id | string | Sim | @Length(1, 64) — não exige UUID aqui (diferente da query do GET) |
redirect_uri | string | Sim | @IsUrl |
code_challenge | string | Sim | @Length(43, 128) |
code_challenge_method | string | Sim | @IsIn(['S256']) |
scope | string | Não | @Length(1, 1024) |
state | string | Não | @Length(1, 512) |
nonce | string | Não | @Length(1, 255) |
POST /oauth/authorize/session: os mesmos campos acima, mais:
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
decision | "approve" | "deny" | Sim | @IsIn |
identity_session_id | string | Só quando decision: "approve" | @ValidateIf + @IsUUID |
user_id | string | Só quando decision: "approve" | @ValidateIf + @IsUUID |
Response
GET — 303 redirect:
textLocation: {IDENTITY_OAUTH_BROWSER_AUTHORIZATION_START_URL}?client_id=...&client_name=...&redirect_uri=...&code_challenge=...&response_type=...&state=...
POST /oauth/authorize e POST /oauth/authorize/session (approve) — 200:
json{ "code": "opaque-authorization-code", "state": "random-state", "redirect_uri": "https://app.exemplo.com/callback" }
POST /oauth/authorize/session (deny) — 200:
json{ "error": "access_denied", "redirect_uri": "https://app.exemplo.com/callback", "state": "random-state" }
O authorization_code é opaco (32 bytes aleatórios em base64url), tem uso único e TTL
configurável (IDENTITY_OAUTH_AUTHORIZATION_CODE_LIFETIME_SECONDS); só o hash SHA-256 dele é
persistido.
Erros
| Classe de erro | errorCode | Status | Quando ocorre |
|---|---|---|---|
| (validação de payload) | BAD_REQUEST | 400 | corpo/query inválidos |
IdentityOAuthAuthorizationDeniedError | IDENTITY_OAUTH_AUTHORIZATION_DENIED | 400 | client inexistente/inativo, redirect_uri não cadastrada, escopo não permitido, usuário/sessão inativos |
IdentityInvalidOAuthAccessTokenError | IDENTITY_INVALID_OAUTH_ACCESS_TOKEN | 401 | access token ausente/inválido/expirado (POST /oauth/authorize) |
| (guard de serviço) | IDENTITY_OAUTH_BFF_UNAUTHENTICATED | 401 | X-Service-Token ausente/inválido (POST /oauth/authorize/session) |
O Swagger do
GETtambém lista404 IDENTITY_OAUTH_CLIENT_NOT_FOUNDcomo exemplo, mas o comportamento real para client inexistente é sempre 400IDENTITY_OAUTH_AUTHORIZATION_DENIED— divergência de documentação, não de comportamento.
Regras de negócio
| ID | Regra | Comportamento |
|---|---|---|
| RN-01 | Apenas PKCE S256 é aceito | outro método → rejeitado na validação do DTO (@IsIn(['S256'])) |
| RN-02 | client_id deve existir, estar ativo e suportar authorization_code | senão → 400 IDENTITY_OAUTH_AUTHORIZATION_DENIED |
| RN-03 | redirect_uri deve casar com as URLs cadastradas do client | divergência → mesmo erro acima |
| RN-04 | scope pedido deve estar entre os escopos permitidos do client | inválido → mesmo erro acima |
| RN-05 | POST /oauth/authorize exige usuário e sessão ativos no momento da emissão | qualquer um inativo → 400 |
| RN-06 | authorization_code é de uso único | TTL configurável; consumido na troca em /oauth/token |
| RN-07 | deny em /authorize/session não emite código | resposta access_denied, sem efeito colateral |
| RN-08 | SSO e prompt não implementados | toda autorização passa pela experiência externa de login; pendência confirmada, não resolvida nesta branch |
Compliance
| Órgão / norma | Exigência | Como a rota atende |
|---|---|---|
| OAuth2 / OIDC | PKCE obrigatório (S256), redirect_uri exato | validação de code_challenge_method e de redirect_uri contra cadastro |
| LGPD | minimização | apenas client_id/redirect_uri/escopos são processados; nenhum dado clínico envolvido |
| HIPAA | autorização controlada e rastreável | client e sessão validados antes da emissão do código |
Variáveis de ambiente
| Variável | Uso | Default |
|---|---|---|
IDENTITY_OAUTH_AUTHORIZATION_CODE_LIFETIME_SECONDS | TTL do authorization_code | 600s (máx. 3600) |
IDENTITY_OAUTH_BROWSER_AUTHORIZATION_START_URL | destino do redirect do GET | http://localhost:3000/v1/auth/oauth/start |
IDENTITY_OAUTH_ISSUER | usado na montagem de URLs relacionadas | http://localhost:3000 |
Tempo médio de resposta
A confirmar — responsável: time de Identity; data: 24/09/2026. Sem medição publicada.
Requisitos não funcionais
| Requisito | Definição |
|---|---|
| Idempotência | Não — cada POST aprovado gera um novo authorization_code |
| Rate limit | Não identificado @Throttle específico nestas rotas neste levantamento |
| Cache | Não |
| Auditoria | Não identificado evento de auditoria dedicado nesta rota neste levantamento |
Divergências e lacunas confirmadas
- Não foi possível confirmar, neste levantamento, qual dos dois caminhos (
POST /oauth/authorizecom access token de usuário, ouPOST /oauth/authorize/sessioncom token de serviço) é o usado pelo front hoje para obter o primeiroauthorization_codelogo após o login — ver nota em Visão geral do módulo. - O
GET /oauth/authorizenão recebeu, neste levantamento, confirmação de teste automatizado cobrindo o redirect em si (o spec do controller cobre principalmente oPOST).
Relacionado
- 📂 Módulo: Authentication
- 🔁 Próximo passo: Token (troca do authorization_code)
- 🔑 Rotas relacionadas que reaproveitam a emissão de código: Sessões (trocar de conta), Login por QR-code