Pular para conteúdo

Spike AUTH-14.1 — claims app-only Entra e origem do 403

PLANID: AUTH-14.1 · Issue: #51
Data: 2026-08-30
Escopo: classificar origem do 403 reportado pelo cliente; contrato de claims; matriz HTTP gateway × backend.


1. Contexto

Cliente em ECOSIF_API_TOKEN_MODE=AZURE_ENTERPRISE_GATEWAY obtém access token via client_credentials e recebe 403 ao chamar APIs eCosif. Gateway provavelmente valida assinatura/iss/aud; o eCosif antes de AUTH-14.3 não resolvia identidade de aplicação.


2. Matriz de origem HTTP (como distinguir)

Camada HTTP típico Corpo Quando
API Gateway (APIM / AWS JWT authorizer) 401 ou 403 JSON/XML da plataforma (não ErrorResponse eCosif) aud/iss inválido, assinatura, policy exige scp, allowlist appid
Backend — falha JWT (decoder) 401 Spring OAuth2 / WWW-Authenticate Token malformado, expirado, assinatura inválida no Java
Backend — sem vínculo (AUTH-14.3+) 401 ErrorResponse JSON: code=AUTH_IDP_NOT_LINKED ou AUTH_UNAUTHORIZED, message contém identity_provider_link Token app-only válido criptograficamente, mas sem registro AZURE_ENTERPRISE_APP
Backend — app-only desligado 401 Idem ECOSIF_ENABLE_AZURE_APP_ONLY=false e token classificado como app-only
Backend — role/client id 401 Idem (BadJwtException na validação) Role ausente ou appid fora da allowlist
Backend — autorização negócio 403 Varia por serviço Usuário técnico autenticado, mas sem ACL empresa/filial no endpoint

Hipótese principal para o 403 do cliente (pré-AUTH-14)

Evidência Conclusão
Modo B humano documentado mapeia “usuário não encontrado” como problema de link, não 403 no starter Falha de identidade no Java → 401, não 403
Gateway APIM pode usar failed-validation-httpcode="403" em policy customizada 403 provável na borda se token não passa policy (ex.: exige scp)
Cliente ainda não enviou payload jwt.ms Classificação definitiva gateway vs backend permanece pendente confirmação cliente (item 1 do plano)

Procedimento ops (sem token completo):

  1. Comparar corpo da resposta: se não contém "code":"AUTH_IDP_NOT_LINKED" → provável gateway.
  2. Chamar API direto no container Java (bypass gateway) com o mesmo Bearer: se 401 + JSON eCosif → confirma backend (identidade).
  3. Log gateway vs log ecosif.auth.m2m outcome=denied (AUTH-14.7): presença do segundo indica request chegou ao backend.

3. Contrato de claims — token Entra v2 app-only

Referência: Microsoft identity platform access tokens · grant client_credentials · scope api://{resource}/.default.

Claims esperadas (obrigatórias / usadas pelo eCosif)

Claim Presente em app-only? Uso eCosif
iss Sim JWKS / issuer validator
aud Sim = ECOSIF_AZURE_API_AUDIENCE (api://…)
exp / nbf Sim Validade
tid Sim Tenant (ECOSIF_AZURE_TENANT_ID)
roles Sim (App role consentida) ECOSIF_AZURE_APP_REQUIRED_ROLES
appid Sim (v1 style) ou via azp Allowlist + fallback lookup
azp Frequentemente Allowlist + fallback lookup
oid Sim (object id do service principal) Lookup preferencial em identity_provider_link
idtyp Frequentemente app Detecção app-only (AzureEntraAppOnlySupport)
scp Não (delegated) Ausência confirma app-only
sub Sim (often = oid do SP) Não usar como chave primária B2B (diferente de humano)

Payload sintético de referência (homolog / testes)

Valores fictícios — não são secrets; usar só em jwt.ms local ou testes unitários.

{
  "aud": "api://00000000-0000-4000-8000-000000000001",
  "iss": "https://login.microsoftonline.com/11111111-1111-4111-8111-111111111111/v2.0",
  "iat": 1756550000,
  "nbf": 1756550000,
  "exp": 1756553600,
  "aio": "XXXX",
  "appid": "22222222-2222-4222-8222-222222222222",
  "appidacr": "1",
  "idtyp": "app",
  "oid": "33333333-3333-4333-8333-333333333333",
  "roles": ["Ecosif.Api.Access"],
  "sub": "33333333-3333-4333-8333-333333333333",
  "tid": "11111111-1111-4111-8111-111111111111",
  "ver": "2.0"
}

Ordem de lookup B2B (implementada AUTH-14.3)

  1. oid do service principal
  2. appid
  3. azp

Provider: AZURE_ENTERPRISE_APP (ou ECOSIF_AZURE_APP_LINK_PROVIDER).

Detecção app-only vs humano (implementada)

Token idtyp scp roles Classificação
App-only app ausente presente App-only
App-only (variante) ausente ausente presente App-only
Humano delegated user ou ausente presente opcional Humano — ramo AZURE_ENTERPRISE

Testes reprodutíveis: AzureEntraAppOnlySupportTest, AzureEntraAppOnlySpikeTest (starter).


4. Cenários pós-AUTH-14.3 (backend)

Config Vínculo BD Token HTTP backend
ECOSIF_ENABLE_AZURE_APP_ONLY=false app-only válido 401 (app_only_disabled no log)
true ausente app-only válido 401 (no_identity_provider_link)
true presente role OK 200 (se ACL endpoint OK)
true presente role ausente 401 (BadJwtException)
true presente appid não allowlisted 401 (BadJwtException)

5. Pendências do cliente (fechar na homolog AUTH-14.9)

  • [ ] Confirmar se 403 vem do gateway (corpo + headers x-ms-* / APIM trace)
  • [ ] Enviar payload jwt.ms (sem secret) do token real
  • [ ] Confirmar App role name = valor em ECOSIF_AZURE_APP_REQUIRED_ROLES
  • [ ] Confirmar oid do service principal para onboarding (POST /api/admin/b2b-app-links)

6. Referências

Artefato Caminho
Plano AUTH-14 hub .internal_docs/tasks-plan/in-progress/20260825_AUTH-14_entra-app-only-b2b.md
Integração B2B integracao_b2b_entra_app_only.md
Checklist ops checklist_b2b_entra_app_only.md
Starter detector AzureEntraAppOnlySupport.java
Logs M2M AzureEntraAppOnlyAuthAudit.java