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 |
Sí | Siempre code |
client_id |
Sí | ID del cliente registrado en Keycloak |
redirect_uri |
Sí | URI de callback registrada en el cliente |
scope |
Sí | openid mínimo; agregar profile, email, scopes personalizados |
state |
Sí | 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.