FundamentosLección 09
OAuth 2 y OpenID Connect
Autorización delegada sin compartir contraseñas. Los cuatro roles, los tipos de token, qué flujo usar en cada caso y por qué PKCE es obligatorio.
Al terminar sabrás
- Identificar los cuatro roles de OAuth 2 en un sistema real.
- Distinguir access token, refresh token e id token, y OAuth 2 de OpenID Connect.
- Elegir el grant correcto para cada tipo de cliente.
- Seguir un flujo Authorization Code + PKCE paso a paso y calcular un code challenge.
Antes de OAuth, si una app quería leer tus contactos de Gmail, te pedía tu contraseña de Gmail. La app podía entonces hacer cualquier cosa con tu cuenta, para siempre, y la única forma de quitarle el acceso era cambiar la contraseña. OAuth 2 resuelve eso: das a la app un permiso limitado, revocable y sin compartir tu contraseña.
Los cuatro roles
Sección titulada «Los cuatro roles»| Rol | Qué hace | Ejemplos |
|---|---|---|
| Resource owner | Dueño de los datos; da su consentimiento | Tú, en tu cuenta |
| Client | La aplicación que pide acceso | Una SPA, una app móvil, un backend, un job |
| Authorization server | Autentica al usuario y emite tokens | Keycloak, Auth0, Okta, Entra ID, Google, Spring Authorization Server |
| Resource server | La API que acepta tokens y protege los datos | Tu API de notas |
Tres tokens distintos
Sección titulada «Tres tokens distintos»| Token | Para quién | Para qué | Formato típico |
|---|---|---|---|
| Access token | La API (resource server) | Autorizar llamadas: “este cliente puede notes:read en nombre de ana” | JWT u opaco, vida corta |
| Refresh token | El authorization server | Obtener access tokens nuevos sin volver a pedir login | Opaco, vida larga, revocable |
| ID token (OIDC) | El cliente | Saber quién inició sesión | Siempre JWT |
OAuth 2 es un protocolo de autorización (delegar acceso a una API). No dice nada sobre quién es el usuario. OpenID Connect (OIDC) es una capa encima que añade autenticación: el id_token, el endpoint /userinfo y los scopes estándar openid, profile y email. “Iniciar sesión con Google” es OIDC.
Qué flujo (grant) usar
Sección titulada «Qué flujo (grant) usar»| Situación | Grant |
|---|---|
| Un usuario inicia sesión en una web, SPA o app móvil | Authorization Code + PKCE |
| Un servicio llama a otro sin usuario (jobs, microservicios) | Client Credentials |
| Renovar un access token sin pedir login otra vez | Refresh Token |
| Dispositivos sin teclado cómodo (TV, CLI) | Device Authorization |
| Obsoleto: el cliente vería la contraseña. Eliminado en OAuth 2.1 | |
| Obsoleto: el token viajaba en la URL |
OAuth 2.1, que consolida las buenas prácticas de los últimos años, se resume en: Authorization Code con PKCE para todo lo que tenga usuario, redirect_uri comparados exactamente, y nada de implicit ni password.
Authorization Code + PKCE, paso a paso
Sección titulada «Authorization Code + PKCE, paso a paso»Por qué PKCE
Sección titulada «Por qué PKCE»El code del paso 3 viaja por el navegador: puede quedar en el historial, en un log, o ser interceptado por otra app registrada con el mismo esquema de URL en un móvil. PKCE (Proof Key for Code Exchange) lo vuelve inútil para quien lo robe:
code_verifiersecreto aleatorio que nunca sale de la appcode_challengeBASE64URL(SHA-256(verifier)), viaja en el paso 1- Canje del codela app envía el verifier; el servidor recalcula el hash y compara
Quien roba el code no tiene el code_verifier, y el hash no se puede invertir. Por eso PKCE se exige hoy a todos los clientes, incluidos los confidenciales.
El parámetro state
Sección titulada «El parámetro state»state es un valor aleatorio que la app genera, guarda y comprueba al volver. Evita que un atacante inyecte en tu sesión un code de su cuenta (un CSRF sobre el callback). OIDC añade nonce, que va dentro del id_token y evita reutilizar tokens.
Clientes públicos y confidenciales
Sección titulada «Clientes públicos y confidenciales»- Confidencial: puede guardar un secreto (un backend). Se autentica ante el authorization server con
client_secret,private_key_jwto mTLS. - Público: no puede guardar secretos porque su código está en manos del usuario (SPA, móvil, CLI). Depende por completo de PKCE y de
redirect_uriexactas.
Por eso el patrón BFF convierte una SPA (cliente público) en un cliente confidencial: el backend hace el flujo, guarda los tokens y la SPA solo tiene una cookie HttpOnly.
Descubrimiento y claves
Sección titulada «Descubrimiento y claves»Un authorization server OIDC publica su configuración en una URL conocida:
GET /.well-known/openid-configuration HTTP/1.1Host: auth.example.comLa respuesta incluye issuer, authorization_endpoint, token_endpoint, userinfo_endpoint y jwks_uri, de donde las APIs descargan las claves públicas para verificar las firmas de los tokens. Cada clave tiene un kid; el header del JWT dice qué kid se usó, lo que permite rotar claves sin cortar el servicio.
Mini lab · 15 min
Calcula un code challenge
Objetivo: Generar un par PKCE en Java y razonar qué obtiene un atacante en cada punto del flujo.
public static void main(String[] args) throws Exception { byte[] random = new byte[32]; new SecureRandom().nextBytes(random); String verifier = Base64.getUrlEncoder().withoutPadding().encodeToString(random);
byte[] hash = MessageDigest.getInstance("SHA-256") .digest(verifier.getBytes(StandardCharsets.US_ASCII)); String challenge = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
System.out.println("verifier = " + verifier); System.out.println("challenge = " + challenge);}- Sustituye el verifier aleatorio por el del ejemplo de la RFC 7636,
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk. ¿Qué challenge obtienes? - Un atacante intercepta la URL del paso 3 (
?code=…&state=…). ¿Puede obtener tokens? ¿Y si la app no usara PKCE y fuera un cliente público? - ¿Qué grant usarías para: (a) un job nocturno que genera informes desde la API de notas; (b) una app móvil de notas; (c) un panel web con backend propio?
Pista
Para el punto 2, revisa qué envía la app en el paso 6 que el atacante no tiene.
Mostrar respuestaOcultar respuesta
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM, el valor exacto que aparece en la RFC 7636. Si te sale distinto, revisa que uses Base64 URL y sin padding.- Con PKCE, no: para canjear el
codenecesita elcode_verifier, que nunca salió de la app. Sin PKCE y con un cliente público (sin secreto), sí: bastaría con enviar elcodeal endpoint/tokenantes que la app legítima. - (a) Client Credentials, con un scope de solo lectura: no hay usuario. (b) Authorization Code + PKCE como cliente público, con el navegador del sistema (no un WebView). (c) Authorization Code + PKCE como cliente confidencial (BFF): el backend guarda los tokens y el navegador solo recibe una cookie de sesión.