Saltar al contenido principal

OAuth 2.0

OAuth 2.0 es un protocolo de autorización (RFC 6749) que permite a una aplicación acceder a recursos de otra en nombre del usuario, sin que este comparta sus credenciales directamente con la aplicación.

Los casos de uso más conocidos son aquellos de acceso a un servicio con Google, GitHub o Apple, pero OAuth 2.0 también se usa para integraciones entre servicios (M2M) y delegación de acceso a APIs de terceros.

Roles en OAuth 2.0

RolDescripciónEjemplo
Resource OwnerEl usuario que da permisoEl usuario de tu app
ClientTu aplicaciónTu backend Node.js
Authorization ServerQuien autentica y emite tokensGoogle, GitHub, Auth0
Resource ServerLa API que contiene los datos del usuarioGoogle People API, GitHub API

Flujo Authorization Code con PKCE

Es el flujo estándar y más seguro para aplicaciones web y móviles. A diferencia del flujo básico, PKCE (Proof Key for Code Exchange) protege contra ataques de interceptación de código en "clientes públicos" (donde no podemos ocultar un client_secret, como una SPA o una app móvil).

Funcionamiento del flujo:

  1. Generación de claves: tu app genera un secreto aleatorio (code_verifier) y una versión hasheada de este (code_challenge). Sólo el servidor de autorización verá ambos al final para validar que eres tú.

  2. Redirección al servidor de autorización: rediriges al usuario a Google (o el proveedor que uses).

    • client_id: Identificador público de tu aplicación.
    • scope: Los permisos que pides (por ejemplo, ver el email).
    • state: Un valor aleatorio para prevenir ataques CSRF. Google te lo devolverá intacto.
  3. Consentimiento del usuario: el usuario dice "Sí, permito que esta app vea mi email".

  4. Recepción del código: Google devuelve al usuario a tu URL de callback con un code. Este código no es el token final, es solo un vale de un solo uso. Es vital validar que el state coincida.

  5. Intercambio de código por tokens: tu servidor (backend) contacta directamente con Google enviando el code y el code_verifier original. Si coinciden con el code_challenge del paso 2, Google confía en que la petición es legítima.

  6. Obtención de datos: con el access_token recibido, ya puedes pedir los datos reales (nombre, foto, etc.) al servidor de recursos.

  7. Sesión propia: una vez tienes la identidad del usuario, creas un registro en tu base de datos y emites tu propia cookie o JWT para que el usuario navegue por tu app.

¿Qué es PKCE y por qué es necesario?

PKCE (Proof Key for Code Exchange) es una extensión de seguridad para el flujo de código de autorización. Originalmente se diseñó para aplicaciones móviles, pero hoy es una recomendación obligatoria para cualquier cliente público (como aplicaciones React, Vue o móviles).

El problema: interceptación del código

En el flujo estándar de OAuth 2.0, el servidor envía el code a través de una redirección en el navegador. Un atacante (o un software malicioso en el dispositivo) podría interceptar ese código.

  • En un servidor seguro, el atacante no podría hacer nada porque no tiene el client_secret.
  • En un cliente público (SPA/móvil), no hay client_secret (porque cualquiera podría leerlo en el código fuente). Por tanto, el atacante podría intercambiar el código interceptado por un token de acceso.

La solución: el handshake dinámico

PKCE elimina la necesidad de un secreto estático mediante un secreto dinámico generado para cada login:

  1. Code Verifier: Es una cadena aleatoria criptográficamente segura creada por tu app en cada intento de login.
  2. Code Challenge: Es el resultado de aplicar un hash (SHA256) al code_verifier.

Cómo funciona la protección:

  1. Al pedir el código, envías el code_challenge. El servidor de Google se lo guarda.
  2. Al recibir el código, envías el code_verifier original.
  3. El servidor de Google le aplica el hash a tu code_verifier y comprueba si coincide con el code_challenge que le enviaste al principio.

Si un atacante intercepta el código en el paso 1, no podrá obtener el token porque no conoce el secreto original (code_verifier) que solo tu aplicación tiene en memoria.

Implementación con passport.js (Google)

Passport.js es el middleware estándar en Node.js que abstrae toda la complejidad de los pasos anteriores. Utiliza "estrategias" para conectarse a diferentes proveedores.

import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';

passport.use(new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback',
scope: ['openid', 'email', 'profile'],
},
async (accessToken, refreshToken, profile, done) => {
// Esta función se ejecuta tras el intercambio exitoso del código.
// accessToken: Permite pedir datos a las APIs de Google.
// profile: Objeto estandarizado con los datos del usuario (id, email, nombre).
try {
let user = await db.findUserByGoogleId(profile.id);

if (!user) {
user = await db.createUser({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatarUrl: profile.photos[0]?.value,
});
}

// done() indica a passport que la autenticación es correcta y pasa el usuario a req.user
return done(null, user);
} catch (err) {
return done(err);
}
}
));

// 1. Ruta que inicia el login (redirige a Google)
app.get('/auth/google',
passport.authenticate('google')
);

// 2. Ruta a la que Google devuelve al usuario
app.get('/auth/google/callback',
passport.authenticate('google', { session: false, failureRedirect: '/login' }),
(req, res) => {
// Al llegar aquí, req.user contiene el usuario devuelto por el callback de la estrategia
const token = generateAccessToken(req.user);
res.redirect(`https://tuapp.com/app?token=${token}`);
}
);

OpenID Connect (OIDC)

OpenID Connect es una extensión de OAuth 2.0 que añade una capa de identidad. Mientras que OAuth 2.0 se diseñó para dar permisos (autorización), OIDC se utiliza para saber quién es el usuario (autenticación).

La gran diferencia es que el proveedor devuelve un ID Token (un JWT firmado) que contiene los datos del usuario, evitando que tu servidor tenga que hacer una segunda llamada a una API de /userinfo.

Scopes comunes

ScopeInformación que concede
openidActiva OIDC. Incluye un sub (identificador único del usuario). Requerido para recibir el ID Token.
emailDirección de email y si está verificada.
profileNombre, foto, idioma preferido, etc.
offline_accessRefresh token para acceso prolongado sin que el usuario esté presente.

Otros flujos de OAuth 2.0

Aunque el flujo de Authorization Code es el estándar para aplicaciones web, existen otros casos:

FlujoCuándo usarloPor qué es importante
Authorization Code + PKCEAplicaciones web y móviles.El más seguro; obligatorio hoy en día.
Client CredentialsServidor a Servidor (M2M).Útil si tu backend necesita hablar con otra API (ej. enviar logs) sin que haya un usuario presente.
Device CodeSmart TVs o consolas.El usuario vincula el dispositivo introduciendo un código en su móvil.
ImplicitObsoleto.Antiguamente para SPAs. Es inseguro porque el token viaja en la URL y queda en el historial del navegador.
Resource Owner PasswordDesaconsejado.El usuario pone su clave de Google en tu app. Rompe el principio de OAuth de no compartir credenciales.

Librerías relevantes

LibreríaDescripción
passportFramework de autenticación con estrategias intercambiables.
passport-google-oauth20Estrategia OAuth2 para Google.
passport-github2Estrategia OAuth2 para GitHub.
passport-facebookEstrategia OAuth2 para Facebook.
passport-localEstrategia de usuario/contraseña local (no OAuth, pero se integra con el mismo framework).
openid-clientCliente OIDC/OAuth2 completo, bajo nivel. Ideal si no quieres usar passport.
npm install passport passport-google-oauth20
# o para OIDC directo:
npm install openid-client