Ese botón "Continuar con Google" esconde uno de los protocolos más malinterpretados del desarrollo. La confusión empieza por la base: OAuth 2.0 no autentica a nadie — delega autorización. La autenticación llega con OpenID Connect, una capa encima. Entender esta distinción (y el flujo correcto actual) separa integraciones seguras de agujeros de seguridad disfrazados de login social.
Los cuatro actores
OAuth define roles con precisión que conviene respetar:
- Resource Owner: el usuario dueño de sus datos.
- Client: tu aplicación, que quiere acceso en su nombre.
- Authorization Server: Google, GitHub... quien verifica identidad y emite tokens.
- Resource Server: la API donde viven los datos (perfil, repos, email).
El caso de uso original: "quiero permitir que esta app lea mis fotos de Google Photos SIN darle mi contraseña". Ese "sin darle mi contraseña" es la razón de ser del protocolo.
El flujo moderno: Authorization Code + PKCE
Desde 2019, PKCE (Proof Key for Code Exchange) es obligatorio para apps públicas y recomendado para todas. El flujo completo:
1. Tu app genera un verificador aleatorio (code_verifier) y
guarda su hash (code_challenge).
2. Rediriges al servidor de autorización:
https://accounts.google.com/o/oauth2/v2/auth
?client_id=TU_ID
&redirect_uri=https://tuapp.es/callback
&response_type=code
&scope=openid email profile
&state=valor_aleatorio_anti_csrf
&code_challenge=HASH&code_challenge_method=S256
3. El usuario inicia sesión en Google y aprueba scopes.
4. Google redirige a tu callback con ?code=XXX&state=...
5. Verificas que state coincide; intercambias code por tokens
EN BACKEND (con client_secret + code_verifier):
POST https://oauth2.googleapis.com/token
6. Recibes: access_token (+ refresh_token) e id_token si pediste openid.
Por qué cada pieza existe:
state: valor aleatorio vinculado a la sesión; al volver debe coincidir. Sin él, un atacante puede inyectar SU código de autorización en TU sesión (CSRF de login).- PKCE: el
code_verifiersolo vive en tu app; el intercambio final lo exige. Si alguien intercepta elcode(que viaja por URL), no sirve sin el verificador. Es el sustituto moderno del secreto imposible de guardar en apps móviles/SPA. - Intercambio en backend: el
client_secretjamás va al navegador. En SPAs puras el flujo funciona igual pero sin secret — por eso PKCE dejó de ser opcional.
OAuth ≠ login: entra OpenID Connect
Aquí está el malentendido fundacional. Un access_token de OAuth responde "¿qué puede hacer esta app?" — no "¿quién eres?". Usarlo como credencial de sesión produce los bugs clásicos: tokens sin audiencia válida aceptados entre servicios, sesiones que no expiran bien, imposibilidad de logout real.
OpenID Connect añade sobre OAuth las piezas de identidad:
- Scope especial
openid. - El ID Token: un JWT firmado cuyo payload declara quién eres:
{
"iss": "https://accounts.google.com",
"sub": "110169484474386276334",
"aud": "TU_CLIENT_ID",
"exp": 1756018800,
"email": "usuario@gmail.com",
"email_verified": true
}
Tu backend valida ese JWT: firma contra las claves públicas publicadas en /.well-known/jwks.json, emisor correcto (iss), audiencia (aud) = tu client_id, y expiración. Solo tras validar TODO se crea la sesión local. Puedes inspeccionar cualquier id_token pegándolo en nuestro decodificador JWT — verás exactamente estas claims.
Regla mnemotécnica: access_token para llamar APIs, ID token para saber quién eres, refresh_token para renovar. Nunca cruzarlos.
Scopes: pedir lo justo
Los scopes delimitan poder concedido. Buenas prácticas que evitan sustos de auditoría:
- Principio de mínimo: ¿necesitas
gmail.readonlypara mostrar "continuar con Google"? No —openid email profilebasta. - Scopes incrementales: pide permisos extra cuando la feature los usa, no todos upfront.
- Muestra al usuario QUÉ vas a acceder; las pantallas de consentimiento existen para eso.
Errores de implementación frecuentes
redirect_uri laxo: aceptar subcadenas o wildcards permite redirigir códigos a dominios del atacante. Coincidencia exacta siempre.
state ausente o predecible: el CSRF de login es real y automatizable.
Validar el id_token solo decodificándolo: Base64 no es verificación. Sin comprobar firma, iss, aud y exp, cualquiera falsifica identidad. Librerías OIDC maduras hacen todo el pipeline; usarlas, no rodar validación a mano.
Tokens en localStorage: mismos riesgos que en el debate JWT/sesiones. Para web tradicional: código → backend → sesión cookie HttpOnly. El navegador nunca toca tokens largos.
Confundir roles en arquitecturas propias: si montas tu propio authorization server interno, aplica el mismo rigor — muchos sistemas internos mueren por atajos "es que es interno".
Cuándo NO hace falta OAuth
Si tu app es un monolito con usuarios propios, login clásico con cookie de sesión es más simple y suficiente. OAuth brilla cuando: delegas identidad (social login), expones API a terceros, o conectas servicios entre sí. Añadirlo "porque es moderno" multiplica superficie de ataque sin beneficio.
Preguntas frecuentes
¿Puedo usar el access_token de Google para llamar MI API? Técnicamente puedes validarlos (Google firma JWTs también), pero pierdes control de revocación y acoplas tu auth a terceros. Patrón sano: OIDC para identidad → sesión propia para todo lo demás.
¿Qué pasa cuando expira el access_token? Con refresh_token, tu backend lo renueva silenciosamente. Los refresh rotativos (nuevo token en cada uso, detección de reuso) son hoy el estándar de seguridad.
¿Implicit flow? Obsoleto oficialmente desde OAuth 2.1 draft: exponía tokens en URL fragment sin PKCE. Si encuentras tutoriales con response_type=token, son anteriores a 2018 — ignóralos.
Inspecciona los tokens de tus integraciones con nuestro decodificador de JWT, gratis y sin salir de tu navegador.