OpenID Connect: la capa de identidad sobre OAuth

Cómo OIDC convierte OAuth 2.0 en autenticación de verdad: el ID token (un JWT) frente al access token, los claims estándar (sub, iss, aud, exp, email), los endpoints de discovery y UserInfo, la validación con JWKS, los flujos Authorization Code, Hybrid e Implicit, y el logout RP-initiated, front-channel y back-channel.

“Iniciar sesión con Google”

Daniel está probando una herramienta nueva para gestionar proyectos. En vez de inventarse otra contraseña, pulsa “Iniciar sesión con Google”, elige su cuenta y aparece en su panel con su nombre y su foto ya puestos. Por debajo de ese botón, la herramienta usó OAuth tal como lo contó el artículo anterior: una redirección, una pantalla de consentimiento, un código de autorización, un intercambio por tokens. Pero además recibió algo que OAuth por sí solo no da: una afirmación fiable de quién es Daniel.


Esa afirmación es lo que añade OpenID Connect (OIDC). El artículo de OAuth cerraba con una advertencia: OAuth autoriza a un cliente pero no autentica al usuario, y las apps que usaron un access token como si fuera un inicio de sesión abrieron agujeros de seguridad reales. OIDC es la solución en la que se puso de acuerdo la industria, una capa de identidad delgada sobre OAuth 2.0, publicada por la OpenID Foundation en 2014, que aporta autenticación sin tirar nada de la maquinaria de OAuth.


Lo que lo hace funcionar es lo poco que añade. Reutiliza tal cual los flujos, los endpoints y los access tokens de OAuth, y suma justo lo que OAuth dejó fuera a propósito: un token que dice quién es el usuario (el ID token), un sitio donde pedir más datos sobre él (el endpoint UserInfo), un vocabulario común para los atributos del usuario (los claims) y una forma de que el cliente se configure solo (el discovery). Nada más. OAuth pregunta si esta app puede hacer esto; OIDC añade la pregunta de quién es, exactamente, este usuario.


Lo valioso es la estandarización. Antes de OIDC, cada proveedor que ofrecía login social lo hacía con su propia extensión de OAuth, un poco distinta a las demás, y los desarrolladores escribían código a medida para cada uno. OIDC lo sustituyó por un único formato de ID token, un único documento de discovery y un único procedimiento de validación que se comporta igual con Google, Microsoft, Okta, Auth0 o cualquier proveedor que cumpla el estándar. Se aprende una vez y se integra en cualquier parte, y por eso se extendió tan rápido.


ID token y access token

El centro de OIDC es un segundo token que convive con el access token de OAuth, con un propósito y un destinatario completamente distintos. Tener clara la pareja es la mayor parte de entender OIDC.


flowchart TD
  accTitle: ID token frente a access token en OpenID Connect
  accDescr: El servidor de autorización emite dos tokens. El ID token habla de identidad, va dirigido al cliente y es un JWT que el cliente lee y valida para autenticar al usuario. El access token habla de autorización, va dirigido al resource server, y el cliente lo trata como opaco y solo lo reenvía a la API. El cliente usa el ID token para iniciar la sesión del usuario y el access token para llamar a APIs protegidas.
  AS[Servidor de autorización] --> IDT[ID token<br/>identidad, para el CLIENTE]
  AS --> AT[Access token<br/>autorización, para la API]
  IDT --> C[El cliente lo lee y valida<br/>para iniciar la sesión]
  AT --> RS[El cliente lo reenvía<br/>al resource server]
Dos tokens, dos destinatarios: el ID token habla de identidad y lo lee el cliente; el access token habla de autorización y es para la API. No uses uno para el trabajo del otro.

El ID token habla de identidad y está pensado para el cliente. Es un JWT firmado que el cliente abre, valida y lee para saber quién acaba de entrar, y su destinatario (aud) es el propio cliente. El access token habla de autorización y está pensado para el resource server. El cliente debe tratarlo como opaco: no lo lee, solo lo reenvía a las APIs, y su destinatario es el resource server.


Los errores clásicos son dos. Usar el access token para identificar al usuario, algo para lo que no está hecho y que a veces ni siquiera es un JWT legible. Y mandar el ID token a una API como si fuera un access token, cuando la API no es su destinataria. El ID token te dice quién; el access token es con lo que llamas a qué.


El ID token: un JWT lleno de claims

El ID token es un JWT, un JSON Web Token: tres partes en base64url, cabecera, contenido y firma, que el cliente puede decodificar y verificar. Su contenido es un conjunto de claims sobre el inicio de sesión y el usuario. Decodificado se ve así:


{
  "iss": "https://accounts.example.com",
  "sub": "248289761001",
  "aud": "s6BhdRkqt3",
  "exp": 1718000300,
  "iat": 1718000000,
  "auth_time": 1717999990,
  "nonce": "n-0S6_WzA2Mj",
  "email": "sara@example.com",
  "email_verified": true,
  "given_name": "Sara",
  "family_name": "López"
}

Los claims se reparten en dos grupos. Los de protocolo son los que hacen el token fiable. iss es el emisor, el proveedor que creó el token, y el cliente tiene que reconocerlo. sub es el sujeto, un identificador estable y único del usuario dentro de ese emisor; la clave primaria de verdad es él y no el email, porque los emails cambian y el sub no. aud es el destinatario, el client ID para el que se emitió el token, y el cliente debe confirmar que es él. exp e iat son la expiración y la emisión, que acotan la validez. Y auth_time y nonce registran cuándo se autenticó de verdad el usuario y llevan un valor que generó el cliente para atar el token a su propia petición y evitar que alguien lo reutilice.


Los claims de identidad describen al usuario: email, email_verified, given_name, family_name, name, picture y otros, según los scopes que se pidieron. OIDC vincula claims con scopes, así que el cliente recibe solo lo que pide.


ScopeClaims que libera
openidsub (obligatorio; es lo que convierte la petición en OIDC)
profilename, given_name, family_name, picture, locale y otros
emailemail, email_verified
addressaddress
phonephone_number, phone_number_verified

Pide siempre openid, que es obligatorio y es lo que dispara el ID token, y añade profile o email solo si de verdad los necesitas. Pedir pocos claims es la versión en la capa de identidad de los scopes estrechos de OAuth: no solicites atributos que no vas a usar.


Un ID token no vale nada hasta que se valida. Comprueba la firma con la clave pública del proveedor, que iss sea el emisor esperado, que aud sea tu client ID, que exp no haya pasado y que el nonce coincida con el que enviaste. Sacar el email de un JWT sin validar e iniciar sesión con él es la versión OIDC de fiarse de una nota sin firma, y es una vulnerabilidad real que aparece una y otra vez.


Discovery, JWKS y UserInfo

OIDC estandariza cómo un cliente conoce a un proveedor y consigue las claves para validar sus tokens, y eso es lo que convierte “añadir un proveedor de identidad nuevo” en configuración en lugar de en un proyecto de desarrollo.


Todo proveedor que cumple el estándar publica un documento de discovery en /.well-known/openid-configuration con su authorization_endpoint, su token_endpoint, su userinfo_endpoint, su jwks_uri y los scopes, claims y algoritmos que soporta. El cliente apunta a la URL del emisor y se configura solo. La jwks_uri sirve las claves públicas de firma del proveedor como un JSON Web Key Set. El cliente las descarga para verificar la firma de los ID tokens, y como cada clave se elige por su kid, su identificador, el proveedor puede rotar sus claves sin romperle nada a nadie. Y UserInfo es un endpoint protegido por OAuth al que el cliente llama con el access token para obtener más claims del usuario, cuando necesita datos de perfil que no vienen en el ID token.


sequenceDiagram
  accTitle: Discovery de OIDC y validación del ID token
  accDescr: El cliente descarga el documento de discovery desde la URL well-known openid-configuration del proveedor y recibe las URLs de los endpoints y la URI de JWKS. Luego descarga el JSON Web Key Set desde esa URI para obtener las claves públicas de firma. Cuando más tarde recibe un ID token, elige la clave por su identificador y verifica la firma, el emisor, el destinatario y la expiración antes de fiarse de sus claims.
  participant C as Cliente (RP)
  participant P as Proveedor OIDC
  C->>P: GET /.well-known/openid-configuration
  P->>C: Endpoints + jwks_uri
  C->>P: GET jwks_uri
  P->>C: Claves públicas de firma (JWKS)
  C->>C: Más tarde, valida el ID token (kid, iss, aud, exp, nonce)
Con el discovery la integración se configura sola: el cliente lee el documento well-known del proveedor, descarga las claves de firma de la URI de JWKS y las usa para validar el ID token. La rotación de claves funciona sin tocar nada.

Los flujos

OIDC usa los grant types de OAuth, adaptados a la autenticación con el scope openid, que le pide al proveedor que emita también un ID token.


Authorization Code, con PKCE, es el flujo estándar para casi todo: apps web, aplicaciones de una sola página y móviles. El cliente recibe un código por redirección y lo cambia por un ID token y un access token. Es el que hay que usar. Hybrid devuelve algunos tokens directamente desde el endpoint de autorización y otros desde el de token. Existe para escenarios concretos, pero complica las cosas y expone tokens en el canal del navegador, y la mayoría de equipos no lo necesita. Implicit devolvía los tokens directamente en la redirección, como el grant implícito de OAuth, y está obsoleto por los mismos motivos: los tokens acaban en el historial del navegador y en los logs. Authorization Code con PKCE lo sustituye en todas partes, también en las aplicaciones de una sola página.


Authorization Code en OIDC

Es el Authorization Code con PKCE de OAuth con un único añadido, el scope openid, que hace que el proveedor devuelva un ID token.


sequenceDiagram
  accTitle: Flujo Authorization Code de OpenID Connect
  accDescr: El cliente envía al usuario al endpoint de autorización del proveedor OIDC pidiendo el scope openid, con un code challenge de PKCE y un nonce. El proveedor autentica al usuario y devuelve un código de autorización de un solo uso. El cliente cambia el código y el verificador PKCE en el endpoint de token y recibe un ID token y un access token. El cliente valida el ID token para autenticar al usuario y después puede llamar al endpoint UserInfo con el access token para obtener más claims.
  actor U as Usuario
  participant C as Cliente (RP)
  participant P as Proveedor OIDC
  C->>P: Authorize (scope=openid, code_challenge, nonce)
  P->>U: Autenticación y consentimiento
  P->>C: Código de autorización (un solo uso)
  C->>P: Intercambia código + code_verifier
  P->>C: ID token + access token
  C->>C: Valida el ID token (firma, iss, aud, exp, nonce)
  C->>P: (opcional) UserInfo con el access token
  P->>C: Más claims
El flujo Authorization Code de OIDC: el scope openid hace que el proveedor devuelva un ID token (identidad) junto al access token (autorización). El cliente valida el ID token para iniciar la sesión del usuario.

La única diferencia conceptual con OAuth a secas es que vuelve un ID token y el cliente lo valida para autenticar al usuario. Todo lo demás, las redirecciones, PKCE, el intercambio del código, es el OAuth que ya conoces. Esa reutilización es la razón por la que OIDC ganó: les dio a los desarrolladores autenticación casi gratis encima de un marco de autorización que ya estaban desplegando.


Del ID token a la sesión

Algo que confunde al principio: el ID token es la prueba de que hubo un inicio de sesión en un momento concreto. No es un token de sesión de larga duración, ni está pensado para mandarlo a tus propias APIs en cada petición. Su trabajo es validarse una vez, justo después del login, para que el cliente sepa quién es el usuario.


Lo que viene después depende del cliente. Una vez validado el ID token, la app crea su propia sesión, normalmente con una cookie de sesión segura y HTTP-only, y es esa sesión, no el ID token, la que acompaña al usuario en las peticiones siguientes. El ID token ya cumplió y se puede descartar.


A partir de ahí, los dos tokens toman caminos distintos. Si la app tiene que seguir llamando a APIs en nombre del usuario, eso es autorización: usa el access token y lo renueva con el refresh token de OAuth cuando caduca, y el ID token no pinta nada. Si la app tiene que mantener al usuario dentro, eso le toca a su propia sesión, con su propia política de expiración e independiente de los tokens.


Un fallo habitual es mandar el ID token al back end como credencial bearer, mezclando “quién inició sesión” con “puede llamar a esta API”. Mantenlos separados: el ID token autentica el inicio de sesión, el access token autoriza las llamadas y tu cookie de sesión da la continuidad. Esa separación también explica por qué el logout es un problema aparte. Cerrar tu propia sesión es fácil; cerrar la del proveedor y la de todas las demás apps es lo difícil.


Logout: tres mecanismos para un problema difícil

Entrar es la mitad fácil. Salir de forma limpia de muchas apps que comparten proveedor es difícil de verdad, la misma lección que dejó el single logout de SAML. OIDC define tres mecanismos.


flowchart TD
  accTitle: Mecanismos de logout de OIDC
  accDescr: OIDC define tres mecanismos de logout. En el RP-initiated, la aplicación envía al usuario al endpoint end-session del proveedor para cerrar la sesión central. En el front-channel, el navegador carga URLs de logout ocultas en cada aplicación. En el back-channel, el proveedor llama directamente al endpoint de logout de cada aplicación, de servidor a servidor. El back-channel es el más fiable porque no depende del navegador.
  L[El usuario cierra sesión] --> RP[RP-initiated<br/>redirige al end_session del proveedor]
  RP --> FC[Front-channel<br/>el navegador carga URLs ocultas]
  RP --> BC[Back-channel<br/>el proveedor llama a cada servidor]
  FC --> R[Sesiones cerradas<br/>mejor esfuerzo]
  BC --> R
Tres maneras de cerrar sesión: RP-initiated manda al usuario al proveedor, front-channel usa el navegador para avisar a cada app y back-channel usa llamadas de servidor a servidor. Back-channel es el más fiable; ninguno es infalible.

En el logout RP-initiated, la app (la relying party) manda al usuario al end_session_endpoint del proveedor, que cierra la sesión central. Es la base y el más soportado. En el front-channel, la página de logout del proveedor carga iframes ocultos que apuntan a la URL de logout de cada app, de modo que el navegador dispara un cierre local en todas. Depende de que el navegador permita esas peticiones, y las restricciones a las cookies de terceros lo debilitan cada vez más. En el back-channel, el proveedor llama directamente al endpoint de logout registrado de cada app, de servidor a servidor, con un logout token. No depende del navegador, así que es el más fiable y la recomendación actual allí donde está soportado.


Como en SAML, lo honesto es reconocer que el cierre coordinado es un mejor esfuerzo: basta una app caída, o una que implementa mal su endpoint, para que la cadena se rompa. Las sesiones cortas y la revocación en el proveedor son los controles en los que de verdad te apoyas. Federar el inicio de una sesión es fácil; federar su final es un problema que se repite en todos los protocolos de este módulo.


Lo esencial de seguridad

OIDC hereda los riesgos de OAuth y suma algunos alrededor del ID token. Lo innegociable viene de OIDC Core y de las guías de seguridad de OAuth.


Valida el ID token completo. Verifica la firma y rechaza de entrada alg: none, un fallo histórico y devastador de los JWT en el que el atacante simplemente quita la firma. Después comprueba iss, aud, exp y el nonce que enviaste. Usa state y PKCE: state frena la falsificación de peticiones en la redirección y PKCE protege el intercambio del código, y ambos son obligatorios en el OIDC moderno igual que en OAuth 2.1. Ata el nonce: genera uno nuevo en cada login, envíalo y confírmalo en el ID token, que es lo que impide reutilizar un ID token capturado.


Si un cliente confía en varios proveedores, protégete de los ataques de mezcla confirmando que el emisor que respondió es el mismo con el que empezaste, para que un emisor malicioso o confundido no te cuele su propio token. Nunca identifiques usuarios con el access token, y nunca mandes el ID token a una API como bearer; la confusión de destinatarios que vimos antes es un fallo de seguridad, no una cuestión de estilo. Y pide pocos claims, prefiere el logout back-channel donde exista y mantén las sesiones cortas pase lo que pase.


El patrón rima con todos los protocolos del módulo: el estándar es sólido, y casi todas las brechas reales vienen de una implementación que validó de menos. Para los usos de mayor exigencia, como la banca abierta o la administración pública, los perfiles FAPI endurecen todavía más estos requisitos.


OIDC y SAML: el mismo trabajo en épocas distintas

OIDC y SAML resuelven los dos la autenticación federada, así que ¿cuándo usar cada uno?


SAML 2.0OpenID Connect
Formato de datosXMLJSON y JWT
TransporteRedirecciones y POST del navegadorHTTP, REST y redirecciones
Artefacto de identidadAserción SAMLID token (con un access token de OAuth)
Mejor paraSingle sign-on web empresarial y B2BWeb, móvil, apps de una sola página, consumo
Origen2005, independiente2014, sobre OAuth 2.0

SAML es XML, se apoya en redirecciones del navegador y está muy asentado en la empresa y en el SaaS B2B. OIDC es JSON y JWT, se lleva bien con REST y es la opción por defecto para todo lo nuevo: login de consumidores, apps móviles, aplicaciones de una sola página y, cada vez más, también la empresa. Si hoy construyes autenticación y la otra parte lo soporta, elige OIDC con Authorization Code y PKCE; sirve igual para web, móvil y aplicaciones de una sola página, y reutiliza todo lo que aprendiste de OAuth. Recurre a SAML cuando tengas que integrarte con un entorno que solo habla SAML. Casi todas las organizaciones grandes usan los dos, y lo que cuenta es manejarse con soltura en ambos, no casarse con uno.


Resumen

OpenID Connect es autenticación construida con limpieza sobre OAuth.


  1. OIDC es OAuth 2.0 más una capa de identidad. Autentica al usuario; OAuth solo autorizaba al cliente.
  2. El ID token habla de identidad y lo lee el cliente; el access token habla de autorización y es para la API. No intercambies sus papeles.
  3. El ID token es un JWT de claims, iss, sub, aud, exp, iat, más los de perfil y email, y hay que validarlo (firma, emisor, destinatario, expiración, nonce) antes de fiarse. Usa sub, no el email, como clave del usuario.
  4. Discovery, JWKS y UserInfo permiten que las integraciones se configuren solas y que la rotación de claves no rompa nada.
  5. Authorization Code con PKCE es el flujo que hay que usar; Hybrid es de nicho; Implicit está obsoleto.
  6. El ID token prueba que hubo un inicio de sesión; después el cliente crea su propia sesión y usa el access token para las APIs. El logout tiene tres formas, RP-initiated, front-channel y back-channel, la última es la más fiable, ninguna es infalible y las sesiones cortas siguen siendo imprescindibles.


Ejercicios prácticos

Están pensados para hacerse, no solo para leerse.


  1. Lee el documento de discovery de un proveedor. Abre en el navegador https://accounts.google.com/.well-known/openid-configuration, o el de cualquier proveedor. Localiza authorization_endpoint, token_endpoint, userinfo_endpoint y jwks_uri, y anota qué scopes y algoritmos de firma soporta.
  2. Decodifica un ID token. Tras un flujo “Iniciar sesión con”, usando el playground OIDC de algún proveedor, pega el ID token en un decodificador de JWT y señala iss, sub, aud, exp y dos claims de identidad. Comprueba que aud coincide con el client ID.
  3. Distingue los tokens. Con un ID token y un access token del mismo login, decide cuál mandarías a una API del back end y cuál usarías para mostrar “Hola, Sara”, y explica por qué intercambiarlos es un fallo.
  4. Audita una validación. Un cliente inicia sesión decodificando en base64 el contenido del ID token y leyendo el email, sin comprobar la firma ni el aud. Enumera cada comprobación que falta y el ataque que permite cada omisión.
  5. Diseña el logout. Tienes cinco apps detrás de un único proveedor OIDC y necesitas “cerrar sesión en todas partes”. Elige entre front-channel y back-channel, justifícalo y di en qué te seguirías apoyando si una app está caída en el momento del logout.
  6. Relaciona scopes y claims. Una funcionalidad nueva necesita el nombre del usuario y su email verificado, nada más. Enumera los scopes exactos que pedirías, los claims que esperas y dónde leerías cada uno (ID token o UserInfo), y explica por qué pedir profile, email y un scope propio sería pedir de más.

Tres preguntas para ponerte a prueba

  1. En una o dos frases para cada uno, explica para qué sirve y a quién va dirigido el ID token y el access token, y da un fallo concreto que provoca confundirlos.
  2. ¿Por qué sub, y no email, es el identificador estable correcto para un usuario, y qué se rompe si una app usa el email como clave de sus cuentas?
  3. Tienes que añadir un proveedor de identidad nuevo a tu app sin URLs escritas a mano y con rotación de claves transparente. ¿Qué piezas de OIDC lo hacen posible y cómo encajan entre sí?

Preguntas frecuentes

¿Qué es OpenID Connect (OIDC)?

OpenID Connect es una capa de identidad delgada sobre OAuth 2.0 que añade autenticación real del usuario. OAuth solo autoriza a un cliente a acceder a recursos; OIDC además emite un ID token, un JWT firmado que dice quién es el usuario, para qué app es y cuándo se autenticó. Es el estándar detrás de 'Iniciar sesión con Google, Microsoft o Apple', publicado por la OpenID Foundation en 2014.

¿Cuál es la diferencia entre un ID token y un access token?

El ID token habla de identidad y es para el cliente: un JWT firmado con claims sobre el usuario (sub, email, name) que el cliente valida para iniciarle sesión. El access token habla de autorización y es para el resource server: da acceso a la API y el cliente debe tratarlo como opaco. El ID token sirve para saber quién es el usuario; el access token, para llamar a las APIs.

¿OpenID Connect reemplaza a OAuth 2.0?

No, está construido sobre OAuth 2.0. OIDC reutiliza los flujos, endpoints y tokens de OAuth y añade las piezas de identidad que OAuth dejó fuera a propósito: el ID token, el endpoint UserInfo, los claims estándar y el discovery. Cuando inicias sesión con un proveedor estás usando OIDC, que usa OAuth por debajo. Son una sola pila: OIDC se encarga de la autenticación y OAuth de la autorización.

¿Qué es el endpoint de discovery .well-known/openid-configuration?

Es un documento JSON estándar que el proveedor OIDC publica en /.well-known/openid-configuration con todo lo que un cliente necesita para integrarse: los endpoints de authorization, token y UserInfo, la URI de JWKS con las claves de verificación, y los scopes, claims y algoritmos que soporta. Permite que el cliente se configure solo en vez de llevar las URLs escritas a mano, y es lo que hace que la rotación de claves pase desapercibida.

¿Qué claims estándar contiene un ID token de OIDC?

Todo ID token lleva iss (emisor), sub (el identificador estable y único del usuario), aud (el cliente al que va dirigido), exp (expiración) e iat (emisión), y a menudo auth_time y nonce. Los scopes profile y email añaden claims como email, email_verified, given_name, family_name y name. El cliente tiene que comprobar iss, aud, exp y la firma antes de fiarse de cualquiera de ellos.

¿Cómo funciona el logout en OpenID Connect?

OIDC define tres mecanismos. En el RP-initiated, la app manda al usuario al endpoint end-session del proveedor para cerrar la sesión central. En el front-channel, el navegador carga URLs de logout ocultas en cada app. En el back-channel, el proveedor llama al endpoint de logout de cada app de servidor a servidor. Igual que en SAML, el cierre coordinado es difícil y muchas veces se queda en un mejor esfuerzo, así que las sesiones cortas siguen siendo el control fiable.