Saltar a contenido

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:

  1. Genera code_verifier (aleatorio, 128 bytes) y code_challenge = BASE64URL(SHA256(code_verifier)).
  2. Redirige a Keycloak (realm-partners) con response_type=code, client_id=partner-portal-app, code_challenge y code_challenge_method=S256.
  3. El usuario se autentica. Keycloak emite un authorization_code (válido 60 s).
  4. La SPA intercambia el código enviando también code_verifier. Keycloak verifica SHA256(code_verifier) == code_challenge y devuelve access_token + refresh_token.
  5. La SPA almacena el access_token en memoria y el refresh_token en 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.