Entra ID + API Management — Finance (MVP)¶
Autenticación de usuarios del portal con Microsoft Entra ID, llamadas al Finance API preferentemente vía Azure API Management con JWT, rate limit y correlation id.
1. Visión: autenticación vs autorización¶
flowchart TB
subgraph AuthN["Autenticación — ¿quién eres?"]
U[Usuario] --> ENTRA[Entra ID login]
ENTRA --> JWT[Access token<br/>aud = API app<br/>scp = access_as_user]
end
subgraph Gate["Quién puede entrar a la app"]
JWT --> EA{Enterprise application<br/>Assignment required?}
EA -->|No| ANY[Cualquier usuario del tenant]
EA -->|Yes| GRP[Solo Users and groups<br/>Finance.Reviewer / Finance.User]
end
subgraph AuthZ["Autorización — ¿qué puedes hacer?"]
ANY --> APIM[APIM validate-jwt]
GRP --> APIM
APIM -->|token OK| API[finance-api]
API --> COARSE[MVP hoy: casi todo-o-nada]
API -.-> FINE[Futuro: Reviewer-only en approve/reject]
end
| Capa | Responsable | Qué controla hoy |
|---|---|---|
| Login + token | Entra | Identidad |
| Assignment required | Enterprise app SPA | Quién puede autenticarse en el portal |
| validate-jwt | APIM | Token válido (issuer/audience) |
| Roles por endpoint | finance-api | Pendiente (grupos creados, no enforcement fino) |
2. Arquitectura de llamadas¶
sequenceDiagram
actor U as Usuario
participant Entra as Entra ID
participant Web as finance-web
participant Proxy as Next /api/finance/*
participant APIM as APIM
participant API as finance-api ACA
U->>Web: Abrir portal
Web->>Entra: MSAL loginRedirect
Entra-->>Web: id_token + access_token
U->>Web: Acción (lista, PDF, chat, HITL)
Web->>Proxy: fetch + Authorization Bearer
Proxy->>APIM: APIM_GATEWAY_URL + Bearer + x-correlation-id
Note over Proxy,API: Si APIM_GATEWAY_URL vacío → FINANCE_API_BASE_URL (ACA directo)
APIM->>APIM: rate-limit IP · validate-jwt · rate-limit sub · correlation
APIM->>API: forward
API-->>Web: JSON / PDF
3. Apps y grupos (DEV creado)¶
| Artifact | Valor DEV |
|---|---|
| Tenant | a1d4709b-8867-4715-bfb6-a60ac27ce3c3 |
SPA eai-finance-web-dev |
Client ID 57e764e2-e373-4d10-a17d-22112e6d7d27 |
API eai-finance-api-dev |
Client ID fedbcc05-e352-4a17-abbe-c84f0dd3d443 |
| Audience / App ID URI | api://fedbcc05-e352-4a17-abbe-c84f0dd3d443 |
| Scope | api://fedbcc05-…/access_as_user |
| Redirects SPA | AFD ep-finance-dev-….azurefd.net/ (+ ACA + localhost) |
| Grupos | Finance.Reviewer, Finance.User |
La política del tenant exige App ID URI
api://{appId}(noapi://eai-finance-api-dev).
Authority: https://login.microsoftonline.com/{tenant_id}
Scopes SPA: openid, profile, api://{api-app-id}/access_as_user
4. Cómo restringir quién se conecta¶
- Entra ID → Enterprise applications →
eai-finance-web-dev - Properties →
Assignment required?= Yes - Users and groups → asignar
Finance.Reviewery/oFinance.User(y usuarios puntuales)
Solo esos podrán completar el login. Ver miembros en Groups → Members.
5. Crear / actualizar apps (script)¶
Terraform SP suele no poder crear app registrations. Usar:
./scripts/create_entra_finance_apps.ps1 `
-TenantId "a1d4709b-8867-4715-bfb6-a60ac27ce3c3" `
-Environment dev `
-WebRedirectUri "https://ep-finance-dev-gfayg5axbhd7h5hf.z02.azurefd.net/" `
-SkipLogin
Añadir también en el SPA (Graph / Portal) el FQDN ACA y http://localhost:3000/ si hace falta debug local. Ver edge-front-door.md.
Salida local (gitignored): infra/environments/dev/entra-apps.dev.env
Admin consent (Global Admin / Privileged Role Admin):
Entra → App registrations → eai-finance-web-dev → API permissions → Grant admin consent
(access_as_user + User.Read).
Validación Graph: grants AllPrincipals para ambos scopes.
6. Terraform APIM¶
Archivos: infra/environments/dev/apim.tf, entra.tf, policies/apim-finance-api-policy.xml
| Variable | Rol |
|---|---|
enable_apim |
true en pipeline para crear APIM Developer |
entra_finance_spa_client_id |
SPA client id |
entra_finance_api_audience |
Audience JWT |
enable_entra_app_registrations |
false por defecto (usar script) |
Pipeline eai-terraform-dev pasa enable_apim=true + IDs Entra. Apply: ManualValidation + environment (APIM ~30–60 min).
Policy inbound: rate-limit-by-key 120/min por IP → validate-jwt → groups User|Reviewer (403) → HITL Reviewer → rate-limit-by-key 300/min por JWT sub → x-correlation-id + backend = FQDN finance-api.
7. Variables finance-web (ACA)¶
| Variable | Comportamiento |
|---|---|
FINANCE_API_BASE_URL |
Fallback directo al ACA API |
APIM_GATEWAY_URL |
Si está set → proxy usa gateway |
NEXT_PUBLIC_ENTRA_* / runtime /api/auth/config |
Vacío = modo demo; con client id = login MSAL |
FRONT_DOOR_ID |
Si set → solo tráfico con X-Azure-FDID (ver edge-front-door.md) |
Config Entra se lee en runtime (/api/auth/config) para no depender solo del bake de Next.
Entrada pública del portal: Front Door (no el FQDN ACA).
8. Hardening posterior¶
Assignment requireden Enterprise app- Exigir grupo
Finance.ReviewerenPOST …/approve|reject|return— implementado (API + APIM + UI). Token debe incluir claimgroups(app API:groupMembershipClaims=SecurityGroup). - Exigir
Finance.UseroFinance.Reviewerpara entrar al portal / llamar al API — implementado (pantalla AccessDenied + APIM 403 + middleware API). - Borde ACA (DEV): con
enable_apim=true, Terraform poneip_security_restrictionAllow solo aazurerm_api_management.public_ip_addresses(hoy68.155.193.213/32). Sigueexternal_enabled=trueporque CAE y APIM Developer no tienen VNet —external_enabled=falsedejaría APIM sin ruta al backend. - Smoke:
GETFQDN ACA → 403; vía APIM + JWT → backend (sin JWT → 401 en gateway). - Futuro: VNet + APIM en modo red → entonces sí
external_enabled=false. - Validación JWT defensiva dentro de finance-api (refuerzo)
9. Relacionado¶
- Front Door y WAF
- APIM Developer Portal
- Instrucciones agente:
agents/finance-agent/instructions.md - Actualizar Foundry:
python scripts/create_finance_foundry_agent.py - Getting started: getting-started.md