Flujos OAuth 2.0 en la plataforma¶
OAuth 2.0 define múltiples flujos de autorización diseñados para escenarios distintos: aplicaciones públicas en el navegador, servicios backend sin usuario, dispositivos sin teclado y delegación controlada entre servicios. Elegir el flujo incorrecto compromete la seguridad o la usabilidad del sistema. Esta página describe los cinco flujos relevantes para la plataforma, cuándo aplicar cada uno y cómo se mapean a los clientes reales del proyecto.
Tabla comparativa de flujos¶
| Flujo | Estándar | Actores | Cuándo usar | Nivel de seguridad | Soporte Keycloak 26.x |
|---|---|---|---|---|---|
| Authorization Code + PKCE | RFC 6749 + RFC 7636 | Usuario, SPA/Mobile, IdP | SPAs, apps móviles, cualquier cliente público con usuario humano | Alto — PKCE elimina el ataque de intercepción del código | Nativo, configuración por defecto para clientes públicos |
| Client Credentials | RFC 6749 §4.4 | Servicio A, IdP, Servicio B | Comunicación M2M sin usuario, daemons, jobs batch | Alto — requiere client_secret o mTLS en el canal de confianza | Nativo, habilitar en la pestaña "Capability config" del cliente |
| Refresh Token | RFC 6749 §6 | Cliente (con refresh_token), IdP | Renovación silenciosa de access_token sin re-autenticar al usuario | Medio-Alto — el refresh_token debe almacenarse de forma segura (HttpOnly cookie) | Nativo, configurar TTL en realm settings → Tokens |
| Device Authorization (Device Flow) | RFC 8628 | Dispositivo sin navegador, Usuario en otro device, IdP | Smart TVs, CLIs, IoT, dispositivos sin teclado | Medio — el código de usuario expira rápido; usar polling corto | Activar en realm → OpenID Connect → Device Authorization Grant |
| Token Exchange | RFC 8693 | Servicio origen, IdP, Servicio destino | Delegación de identidad entre microservicios (impersonación controlada, actor tokens) | Alto — requiere política explícita en Keycloak por cliente | Activar feature token-exchange en Keycloak; definir políticas en admin |
Notas de seguridad por flujo¶
Flujos obsoletos — nunca usar
Los flujos Implicit (RFC 6749 §4.2) y Resource Owner Password Credentials (ROPC) están depreciados por el OAuth 2.0 Security BCP (RFC 9700). Keycloak 26.x los desactiva por defecto. No habilitarlos.
Refresh Token en SPAs
Si el cliente es una SPA (JavaScript en el navegador), el refresh_token debe estar en una HttpOnly cookie gestionada por un BFF (Backend for Frontend), nunca en localStorage. Un access_token de vida corta (60-300 s) mitiga el riesgo si el refresh_token es expuesto.
mTLS como alternativa a client_secret
Para Client Credentials en entornos de alta seguridad, configura mTLS Certificate-Bound Access Tokens (RFC 8705). Keycloak 26.x lo soporta en la pestaña "Credentials" del cliente → "X.509 Client Certificate".
Árbol de decisión para elegir flujo¶
flowchart TD
START([¿Qué tipo de cliente necesita un token?]) --> Q1{¿Hay un usuario\nhumano involucrado?}
Q1 -->|Sí| Q2{¿El cliente puede\nguardar un secret\nde forma segura?}
Q1 -->|No| Q3{¿Es comunicación\nM2M / servicio a servicio?}
Q2 -->|No — SPA o mobile| PKCE[Authorization Code + PKCE\ncliente: public\npkce: S256]
Q2 -->|Sí — app server-side| Q4{¿El dispositivo tiene\nnavegador disponible?}
Q4 -->|Sí| PKCE_CONF[Authorization Code + PKCE\ncliente: confidential\ncon client_secret]
Q4 -->|No — TV, CLI, IoT| DEVICE[Device Authorization Grant\nRFC 8628]
Q3 -->|Sí — daemon/job/API| CC[Client Credentials\nclient_credentials grant]
Q3 -->|Delegación de identidad| TEX[Token Exchange\nRFC 8693\nrequiere policy explícita]
PKCE --> RT[+ Refresh Token\npara renovación silenciosa]
PKCE_CONF --> RT
CC --> NOTE[Sin refresh token\nel token M2M expira\ny se obtiene uno nuevo]
style PKCE fill:#1976d2,color:#fff
style PKCE_CONF fill:#1976d2,color:#fff
style CC fill:#388e3c,color:#fff
style DEVICE fill:#f57c00,color:#fff
style TEX fill:#7b1fa2,color:#fff
style RT fill:#0288d1,color:#fff
Mapeo de casos de uso del proyecto a flujos¶
Clientes de referencia¶
| Cliente | Tipo Keycloak | Flujo principal | Flujo secundario | Realm |
|---|---|---|---|---|
partner-portal-app |
Public | Authorization Code + PKCE | Refresh Token | realm-partners |
partner-service-m2m |
Confidential | Client Credentials | — | realm-partners |
internal-portal |
Confidential (flexible) | Authorization Code + PKCE | Token Exchange (para llamadas internas) | realm-internal |
Escenarios concretos¶
Escenario 1 — Socio externo accede al portal web¶
El usuario navega a https://partners.empresa.com. El partner-portal-app (SPA) detecta que no hay sesión activa e inicia el flujo Authorization Code + PKCE:
- Genera
code_verifier(aleatorio, 128 bytes) ycode_challenge = BASE64URL(SHA256(code_verifier)). - Redirige a Keycloak (
realm-partners) conresponse_type=code,client_id=partner-portal-app,code_challengeycode_challenge_method=S256. - El usuario se autentica. Keycloak emite un
authorization_code(válido 60 s). - La SPA intercambia el código enviando también
code_verifier. Keycloak verificaSHA256(code_verifier) == code_challengey devuelveaccess_token+refresh_token. - La SPA almacena el
access_tokenen memoria y elrefresh_tokenen una HttpOnly cookie gestionada por el BFF.
Escenario 2 — Servicio M2M consulta API interna¶
El partner-service-m2m ejecuta un job nocturno que necesita llamar a la API de facturación:
curl -X POST https://auth.empresa.com/realms/realm-partners/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=partner-service-m2m" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=billing:read"
El token resultante incluye el claim azp: partner-service-m2m y los roles asignados al cliente en Keycloak. No hay usuario; el sub del token es el client_id.
Escenario 3 — Portal interno delega identidad a microservicio downstream¶
El internal-portal (cliente confidencial) recibe una petición de un empleado con su access_token. Para llamar al servicio de RRHH necesita un token con el contexto del usuario pero con los scopes del servicio. Usa Token Exchange:
curl -X POST https://auth.empresa.com/realms/realm-internal/protocol/openid-connect/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "client_id=internal-portal" \
-d "client_secret=${CLIENT_SECRET}" \
-d "subject_token=${USER_ACCESS_TOKEN}" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "audience=hr-service"
Prerequisito Token Exchange
En Keycloak 26.x, Token Exchange requiere habilitar la feature en la imagen Docker y crear una política de autorización en el cliente hr-service que permita a internal-portal realizar el exchange.
Escenario 4 — CLI interna sin navegador (Device Flow)¶
Un desarrollador ejecuta la CLI interna desde una máquina sin interfaz gráfica:
# La CLI solicita el device_code
curl -X POST https://auth.empresa.com/realms/realm-internal/protocol/openid-connect/auth/device \
-d "client_id=internal-cli" \
-d "scope=openid profile"
# Keycloak responde con:
# { "device_code": "...", "user_code": "ABCD-1234",
# "verification_uri": "https://auth.empresa.com/realms/realm-internal/device",
# "expires_in": 300, "interval": 5 }
El CLI muestra Visita https://auth.empresa.com/realms/realm-internal/device y escribe: ABCD-1234. El usuario lo hace desde su laptop. El CLI hace polling cada 5 segundos hasta obtener el token.
Ciclo de vida de los tokens¶
sequenceDiagram
participant C as Cliente
participant KC as Keycloak
participant RS as Resource Server
C->>KC: Solicita token (cualquier flujo)
KC-->>C: access_token (TTL: 300s) + refresh_token (TTL: 1800s)
C->>RS: GET /api/resource<br/>Authorization: Bearer {access_token}
RS->>RS: Valida JWT (firma JWKS + exp + iss + aud)
RS-->>C: 200 OK
Note over C,KC: Cuando access_token expira (exp < now)
C->>KC: POST /token grant_type=refresh_token
KC-->>C: Nuevo access_token + Nuevo refresh_token (rotación)
C->>RS: GET /api/resource<br/>Authorization: Bearer {nuevo_access_token}
RS-->>C: 200 OK
Note over C,KC: Cuando refresh_token expira — re-autenticar
Configuración de TTLs en Keycloak¶
Los tiempos de vida de los tokens se configuran en Realm Settings → Tokens:
| Parámetro | Valor recomendado MVP | Descripción |
|---|---|---|
| Access Token Lifespan | 300 s (5 min) | Ventana de exposición si el token es capturado |
| Client Session Idle | 1800 s (30 min) | Inactividad máxima de sesión de cliente |
| Client Session Max | 36000 s (10 h) | Duración máxima absoluta de sesión |
| SSO Session Idle | 3600 s (1 h) | Para realm-partners; más corto para zero-trust |
| Refresh Token Max Reuse | 0 | Reuso cero: rotación estricta en cada refresh |
Refresh Token Rotation
Con Refresh Token Max Reuse = 0, cada uso del refresh_token invalida el anterior y emite uno nuevo. Si el refresh_token original es robado y usado primero, el cliente legítimo recibirá un error 400 invalid_grant y el sistema puede alertar de sesión comprometida.