Saltar a contenido

Authorization Code + PKCE

El flujo Authorization Code con PKCE (Proof Key for Code Exchange) es el estándar recomendado para aplicaciones con usuario interactivo: portales web, SPAs y aplicaciones móviles. PKCE elimina la necesidad de un client secret en clientes públicos previniendo el robo del authorization code.

Cuándo usarlo

  • Aplicaciones web con usuario humano (browser).
  • SPAs (Single Page Applications).
  • Aplicaciones móviles nativas.
  • Cualquier cliente donde el secret no puede mantenerse confidencial.

No usar para: comunicación M2M sin usuario → usar Client Credentials.

Diagrama del flujo

sequenceDiagram
    autonumber
    actor U as Usuario
    participant B as Navegador / App
    participant T as Traefik
    participant K as Keycloak
    participant API as Resource Server

    U->>B: Clic en "Iniciar sesión"
    B->>B: Generar code_verifier (random 43-128 chars)\ncode_challenge = BASE64URL(SHA256(code_verifier))
    B->>K: GET /realms/realm-internal/protocol/openid-connect/auth\n?response_type=code\n&client_id=partner-portal-app\n&redirect_uri=https://app.example.com/callback\n&scope=openid profile email\n&state=<random>\n&code_challenge=<hash>\n&code_challenge_method=S256
    K->>U: Mostrar pantalla de login
    U->>K: Introducir credenciales
    K->>K: Autenticar usuario
    K->>B: 302 → redirect_uri?code=<auth_code>&state=<state>
    B->>B: Verificar state recibido == state enviado
    B->>K: POST /realms/realm-internal/protocol/openid-connect/token\ncode=<auth_code>\n&code_verifier=<original>\n&client_id=partner-portal-app\n&grant_type=authorization_code\n&redirect_uri=https://app.example.com/callback
    K->>K: Verificar code + code_verifier (SHA256 match)
    K-->>B: access_token + id_token + refresh_token
    B->>T: GET /api/recurso\nAuthorization: Bearer <access_token>
    T->>API: Forward
    API-->>B: 200 OK datos

Parámetros de la request de autorización

Parámetro Obligatorio Descripción
response_type Siempre code
client_id ID del cliente registrado en Keycloak
redirect_uri URI de callback registrada en el cliente
scope openid mínimo; agregar profile, email, scopes personalizados
state Valor aleatorio criptográfico para prevenir CSRF
code_challenge Sí (PKCE) BASE64URL(SHA256(code_verifier))
code_challenge_method Sí (PKCE) Siempre S256 (nunca plain)
nonce Recomendado Previene replay del ID Token

Parámetros del token endpoint

Parámetro Valor
grant_type authorization_code
code Código recibido en el callback
code_verifier El valor original generado antes del login
client_id ID del cliente
redirect_uri Mismo valor que en la autorización

Configuración del cliente en Keycloak

Cliente: partner-portal-app
Tipo de acceso: Public
Standard Flow: ✅ Enabled
Implicit Flow: ❌ Disabled
Direct Access Grants: ❌ Disabled (para producción)

Valid Redirect URIs:
  https://app.example.com/callback
  http://localhost:3000/callback  (solo dev)

Web Origins:
  https://app.example.com

PKCE Challenge Method: S256

Ejemplo con curl (para pruebas)

# 1. Generar code_verifier y code_challenge
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | sha256sum | cut -d' ' -f1 | xxd -r -p | base64 | tr -d '=\n' | tr '+/' '-_')

echo "code_verifier: $CODE_VERIFIER"
echo "code_challenge: $CODE_CHALLENGE"

# 2. Construir URL de autorización (abrir en navegador)
KC_URL="https://auth.example.com"
REALM="realm-internal"
CLIENT_ID="partner-portal-app"
REDIRECT_URI="https://app.example.com/callback"

echo "${KC_URL}/realms/${REALM}/protocol/openid-connect/auth\
?response_type=code\
&client_id=${CLIENT_ID}\
&redirect_uri=${REDIRECT_URI}\
&scope=openid+profile+email\
&state=$(openssl rand -hex 16)\
&code_challenge=${CODE_CHALLENGE}\
&code_challenge_method=S256"

# 3. Después de obtener el code del callback, intercambiarlo por tokens
curl -s -X POST "${KC_URL}/realms/${REALM}/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=${CLIENT_ID}" \
  -d "code=<AUTH_CODE_AQUI>" \
  -d "redirect_uri=${REDIRECT_URI}" \
  -d "code_verifier=${CODE_VERIFIER}" | jq .

Respuesta del token endpoint

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "session_state": "a1b2c3d4-...",
  "scope": "openid profile email"
}

Nunca usar el flujo Implicit

El flujo Implicit (response_type=token) está deprecado en OAuth 2.1 y deshabilitado por defecto en Keycloak 26. Expone el access token en la URL, vulnerable a leaks vía Referer y logs. Usar siempre Authorization Code + PKCE.

Validar el state siempre

El parámetro state protege contra ataques CSRF. El cliente debe generar un valor aleatorio antes de redirigir, almacenarlo en la sesión (no en localStorage), y compararlo con el recibido en el callback antes de intercambiar el código.