Una app móvil inicia sesión de un usuario con un proveedor de identidad y pasa el access token a tu backend, que llama al endpoint de perfil del proveedor, obtiene una dirección de correo y crea una sesión. Todas las pruebas pasan. También permite que cualquier otra app en la que el usuario haya iniciado sesión con ese proveedor entre en tu app como él con su propio access token, porque nada comprueba para qué app se emitió el token.
OpenID Connect (OIDC) cierra esa brecha. Añade un ID token a OAuth 2.0: un JWT firmado en el que el proveedor indica quién es el usuario, para qué app es el token y cuándo inició sesión el usuario.
Este artículo dibuja el authorization code flow con PKCE como diagrama de secuencia y cubre lo que añade OIDC: el scope openid, nonce, la validación del ID token, discovery, la rotación de claves, UserInfo y el cierre de sesión.
OAuth 2.0 frente a OpenID Connect
OAuth 2.0 trata del acceso: un token para llamar a una API en nombre del usuario. OpenID Connect trata de la identidad: quién inició sesión.
| OAuth 2.0 | OpenID Connect | |
|---|---|---|
| Pregunta que responde | ¿Puede esta app llamar a esta API? | ¿Quién es el usuario y cuándo inició sesión? |
| Token | Access token | ID token |
| El token está destinado a | La API | Tu app |
| Formato del token | No definido: opaco o JWT | Un JWT firmado |
| Cómo se pide | Scopes que define la API | scope incluye openid |
| Define | Grants y endpoints | Claims, UserInfo, discovery, cierre de sesión |
En la red, un inicio de sesión OIDC es una solicitud de autorización OAuth con openid en el scope, y la respuesta de tokens trae un id_token junto al access token. El diagrama del flujo OAuth 2.0 cubre en detalle la parte OAuth.
El flujo, paso a paso
OpenID Connect Core 1.0 llama a tu app la relying party (RP) y al servidor de autorización el OpenID provider (OP). Lo que pasa por el navegador, el canal delantero, puede filtrarse a URL, historial y logs; las llamadas de tu backend al proveedor, el canal trasero, no. Los números coinciden con el diagrama.
- Inicio (1–3). El usuario hace clic en Sign in. Tu app guarda en la sesión un
state, unnoncey uncode_verifierde PKCE nuevos, y redirige el navegador al proveedor. - Solicitud de autenticación (4). El navegador sigue la redirección:
GET /authorize?response_type=code
&client_id=orders-web
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&scope=openid%20profile%20email
&state=7fJq2LbX9aQe
&nonce=n-0S6_WzA2Mj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256 HTTP/1.1
Host: op.example.com
- Inicio de sesión y consentimiento (5). El proveedor autentica al usuario (contraseña, passkey, MFA o una sesión existente) y pide su consentimiento. Tu app nunca ve las credenciales.
- Vuelta del código (6–7). El proveedor redirige al
redirect_uriregistrado con uncodede corta duración y el mismostate, que tu app compara con el de la sesión. - Solicitud de tokens (8–9). Tu backend envía por POST el
code, elredirect_uriy elcode_verifiercon sus credenciales de cliente. El proveedor comprueba el verifier contra el challenge y devuelve unid_token, unaccess_tokeny a menudo unrefresh_token. - Validación (10). Tu app valida el ID token y solo descarga las claves de firma cuando la que nombra no está en caché.
- UserInfo (11–12), opcional. Solo cuando el ID token no trae los claims que necesitas.
- Sesión (13). Tu app crea su propia sesión y establece una cookie.
Qué protegen state, nonce y PKCE
| Valor | Viaja en | Lo comprueba | Impide |
|---|---|---|---|
state |
El paso 4 y la redirección de vuelta | Tu app | Una respuesta de inicio de sesión que el usuario nunca inició (CSRF) |
nonce |
El paso 4 y luego dentro del ID token | Tu app | La repetición del ID token y la inyección de código en clientes confidenciales |
code_verifier |
Su hash SHA-256 en el paso 4, él mismo en el paso 8 | El proveedor | Canjear un código robado |
La mejor práctica actual de seguridad de OAuth, RFC 9700, permite que uno sustituya a otro en algunos casos, pero cada uno cuesta un valor aleatorio, así que envía los tres. Los clientes públicos, como las aplicaciones de una sola página y las móviles, deben usar PKCE (RFC 7636) con S256.
Dentro del ID token
El ID token del paso 9, decodificado:
{
"iss": "https://op.example.com",
"sub": "248289761001",
"aud": "orders-web",
"nonce": "n-0S6_WzA2Mj",
"iat": 1793736400,
"exp": 1793740000,
"auth_time": 1793736390
}
| Claim | Significado | Tu app |
|---|---|---|
iss |
Emisor, una URL https | Debe ser igual al issuer descubierto, exactamente |
sub |
ID del usuario, nunca reasignado dentro del emisor, como máximo 255 caracteres ASCII | Usa iss más sub como clave de la cuenta |
aud |
Audiencia, una cadena o un array | Debe encontrar su client_id en ella |
exp, iat |
Caducidad y hora de emisión, en segundos Unix | Rechaza tokens caducados o muy antiguos |
nonce |
El valor enviado en el paso 4 | Debe coincidir con la sesión |
auth_time |
Cuándo se autenticó realmente el usuario | Lo comprueba cuando envías max_age |
acr, amr |
Nivel de garantía y métodos, como pwd o mfa |
Los comprueba antes de acciones sensibles |
Core es explícito: solo iss más sub identifica a un usuario, porque las direcciones de correo cambian y pueden reasignarse.
Validar el ID token
La sección 3.1.3.7 de Core enumera las reglas:
- Fija el algoritmo (RS256 por defecto) en lugar de fiarte de la cabecera
alg, y nunca aceptesnone. - Verifica la firma con la clave JWKS que nombra
kid. Core permite que un cliente de code flow confíe en TLS en su lugar, ya que el token llegó directo del token endpoint, pero verificar cuesta poco. - Comprueba
isscontra el emisor descubierto, carácter por carácter. - Comprueba
audpara tuclient_idy rechaza las audiencias en las que no confías. - Comprueba
expeiat, permitiendo de unos segundos a unos minutos de desfase de reloj. - Comprueba
noncecontra la sesión y bórralo después. - Comprueba
auth_timeyacrsi los solicitaste.
Solo entonces crea una sesión. Con la biblioteca jose en Node.js:
import { createRemoteJWKSet, jwtVerify } from 'jose'
// issuer and jwks_uri come from the discovery document.
// The key set is cached; an unknown kid triggers a refetch
// (at most once every 30 seconds by default).
const JWKS = createRemoteJWKSet(new URL(oidc.jwks_uri))
const { payload } = await jwtVerify(tokens.id_token, JWKS, {
issuer: oidc.issuer, // exact match
audience: CLIENT_ID, // must appear in aud
algorithms: ['RS256'], // never trust the header alone
clockTolerance: 60, // seconds of clock skew
maxTokenAge: 600, // rejects an old iat
})
if (payload.nonce !== session.nonce) throw new Error('nonce mismatch')
Discovery y rotación de claves
OpenID Connect Discovery 1.0 pone los ajustes de un proveedor en un único documento JSON en la URL del emisor más /.well-known/openid-configuration. El de Google, reducido a los campos que usa este flujo, en el momento de escribir esto (noviembre de 2026):
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"id_token_signing_alg_values_supported": ["RS256"],
"code_challenge_methods_supported": ["plain", "S256"]
}
El issuer debe ser idéntico a la URL del emisor desde la que lo obtuviste y al iss de cada ID token. Configura tú mismo el emisor esperado; nunca lo tomes de un token que no hayas verificado.
Las claves rotan a través del jwks_uri: el proveedor añade una clave nueva, firma con ella y mantiene la antigua publicada durante un tiempo (Core, sección 10.1.1). Guarda el conjunto en caché, respeta Cache-Control, vuelve a descargarlo ante un kid desconocido y limita la frecuencia de esas descargas. El diagrama del flujo de autenticación JWT cubre la misma mecánica para los access tokens.
UserInfo
UserInfo devuelve claims para los scopes concedidos (profile, email, address, phone) cuando lo llamas con el access token. Su respuesta siempre contiene sub, que debe coincidir exactamente con el sub del ID token, o descartas la respuesta.
Opciones de cierre de sesión
Tres especificaciones OpenID finales cubren el cierre de sesión:
| Especificación | Cómo funciona | Ten en cuenta |
|---|---|---|
| RP-Initiated Logout 1.0 | Tu app redirige al end_session_endpoint del proveedor con id_token_hint |
Tu app también debe terminar su propia sesión |
| Front-Channel Logout 1.0 | El proveedor carga la URL de cierre de sesión de cada app en un iframe | El bloqueo de cookies de terceros puede romperlo |
| Back-Channel Logout 1.0 | El proveedor envía por POST un logout token firmado a cada app | Tu app debe encontrar y terminar la sesión en el servidor |
Core también señala que el exp del ID token no guarda relación con la duración de la sesión.
Errores que aparecen en las revisiones
- Un access token como prueba de identidad. La escena inicial. Inicia sesión de los usuarios solo con un ID token que tu backend obtuvo del token endpoint y validó.
- Omitir
audononce. Todas las apps registradas en el mismo proveedor reciben ID tokens válidamente firmados con el mismoiss; soloauddice que un token se emitió para ti, y solononcelo vincula a este inicio de sesión. - El implicit flow.
response_type=id_token tokenpone los tokens en el fragmento de la URL; la RFC 9700 dice que los clientes no deberían usar response types que devuelvan access tokens en la respuesta de autorización. Usacodecon PKCE, también en aplicaciones de una sola página. - ID tokens enviados a las API. Las API deberían aceptar solo access tokens emitidos para ellas; el diagrama de arquitectura de un API gateway muestra dónde suele estar esa comprobación.
El diagrama del flujo de OpenID Connect
Contenido del diagrama
- User
- Browser
- Relying party (your app)
- OpenID provider
- all checks must pass
- claims missing from the ID token
- ID token: for your app (aud = client_id)
- Access token: for UserInfo and APIs
- 1. Click Sign in
- 2. GET /login
- 3. 302 to the provider's /authorize
- 4. GET /authorize: scope=openid, state, nonce, code_challenge
- 5. Log in (password, passkey, MFA) and consent
- 6. 302 to redirect_uri with code, state
- 7. GET /callback?code&state
- 8. POST /token: code, code_verifier, client auth
- 9. id_token, access_token
- 10. GET jwks_uri (when kid is not cached)
- 11. GET /userinfo, Bearer access_token
- 12. Claims; sub must match the ID token
- 13. 302 to the app, Set-Cookie: session
Cómo leerlo:
- Cuatro participantes: el usuario, el navegador, tu app (la relying party) y el OpenID provider. El tiempo corre hacia abajo.
- Los colores muestran el canal: azul a través del navegador, verde azulado de tu backend al proveedor, violeta para las acciones del usuario. Las flechas discontinuas son respuestas y redirecciones.
- Los pasos 3 y 4 llevan
scope=openid,state,noncey el challenge de PKCE; el paso 8 lleva el verifier. - El fragmento
criticalcontiene la descarga de claves y las comprobaciones que deben pasar antes de que exista una sesión. El fragmentooptes la llamada a UserInfo. - La columna derecha asocia los campos de discovery con los pasos, enumera los parámetros de PKCE, decodifica un ID token y muestra qué token es para quién.
Cómo dibujar con claridad diagramas de OpenID Connect
- Dale al navegador su propia línea de vida. La separación entre canal delantero y trasero es la historia de seguridad del flujo.
- Escribe en las flechas los parámetros comprobados:
state,nonce,code_challenge,code_verifier. - Detalla la validación. «Validar token» no se puede revisar; «firma, iss, aud, exp, iat, nonce» sí.
- Indica qué token va adónde, con su audiencia. Muchos errores de OIDC son un token enviado a la parte equivocada.
- Un flujo por diagrama. Dibuja por separado el inicio de sesión, la renovación y el cierre de sesión, con ID y tokens de ejemplo.
El ejemplo de diagrama de secuencia cubre la notación; el diagrama del flujo SAML SSO muestra el protocolo basado en XML que los clientes empresariales aún piden.
Constrúyelo en Archboard
- Inicia sesión con Google o GitHub y crea un diagrama.
- Desde el pack Secuencia, añade un Actor para el usuario y tres nodos Línea de vida de la misma altura: Browser, Relying party (tu app) y OpenID provider.
- Dibuja los 13 mensajes como aristas rectas numeradas, de arriba abajo. Haz discontinuas las respuestas y colorea de forma distinta el canal delantero y el trasero.
- Añade un Fragmento combinado del pack Secuencia con el operador
criticalalrededor de la descarga de claves y las comprobaciones, y un segundo Fragmento combinado conoptalrededor de la llamada a UserInfo. - Añade un Texto del pack Básico junto a la relying party para los valores de sesión y las comprobaciones, además de una leyenda de colores.
- Añade un bloque Markdown del pack Básico para discovery, PKCE y un ID token decodificado, y dos nodos Token del pack Seguridad para el ID token y el access token.
El ejemplo usa 9 nodos de diagrama, dentro del plan Free. Tu agente de IA también puede dibujarlo por MCP.
Preguntas frecuentes
¿Cuál es la diferencia entre OAuth 2.0 y OpenID Connect?
OAuth 2.0 da a una app un access token para llamar a una API. OpenID Connect añade el inicio de sesión encima: el scope openid, un ID token que dice quién es el usuario y endpoints estándar de discovery y UserInfo.
¿Es un ID token lo mismo que un access token? No. El ID token es un JWT firmado para tu app que dice quién inició sesión. El access token es para las API, incluida UserInfo. Nunca envíes ID tokens a las API ni aceptes access tokens como prueba de inicio de sesión.
¿Necesito PKCE si ya envío un nonce? Usa ambos. La RFC 9700 exige PKCE para los clientes públicos. Un cliente confidencial puede apoyarse en el nonce con precauciones adicionales, pero PKCE impide canjear un código robado, mientras que el nonce solo detecta un código inyectado después de la respuesta de tokens.
¿Cómo valido un ID token?
Verifica la firma con la clave JWKS que coincida con su kid y comprueba que iss sea igual al emisor, que aud contenga tu client ID, que exp no haya pasado, que iat sea reciente y que nonce coincida con la sesión.
¿Qué es /.well-known/openid-configuration?
El documento de discovery de OpenID Connect. Enumera el emisor, los endpoints, los algoritmos y el jwks_uri de un proveedor, para que tu app pueda configurarse desde una sola URL.