Flujo de Código de Autorización

Autenticación Security Notes Jan 6, 2025 JAVASCRIPT

Definición

¿Alguna vez has recogido un paquete en correos donde primero te dan un resguardo y luego lo intercambias por el paquete real en el mostrador? El flujo de código de autorización funciona igual - primero obtienes un “código” temporal, y luego tu servidor lo intercambia por el token de acceso real. Este paso intermedio es lo que hace este flujo el más seguro de OAuth 2.0.

El proceso funciona así: cuando haces clic en “Iniciar sesión con Google”, tu navegador te lleva a Google donde introduces tus credenciales. Si todo es correcto, Google no te da el token directamente - te redirige de vuelta a la aplicación con un código de autorización en la URL. Este código es inútil por sí solo - es de un solo uso, expira en minutos, y solo funciona cuando tu servidor backend lo intercambia directamente con Google, presentando también un secreto que el navegador nunca ve.

¿Por qué tanto rodeo? Seguridad. El código de autorización pasa por el navegador (que puede ser interceptado), pero el access token nunca lo hace. El intercambio código→token ocurre servidor-a-servidor, donde puedes usar credenciales secretas que nunca se exponen al usuario. Es como recibir un ticket de guardarropa: el ticket puede pasar de mano en mano, pero solo el encargado autorizado puede darte tu abrigo.

Ejemplo

Inicio de Sesión con Redes Sociales: Cuando usas “Conectar con GitHub” en una aplicación, te redirige a GitHub para autorizar. GitHub te devuelve a la app con un código. El servidor de la app intercambia ese código por un token que le permite acceder a tu información de GitHub. El navegador nunca ve el token real - solo el código temporal.

Aplicaciones Empresariales con Azure AD: Cuando accedes a una aplicación corporativa protegida por Azure AD, el flujo es: login en portal de Microsoft → código de autorización → tu servidor intercambia por token → acceso a recursos de Microsoft 365. Las credenciales secretas de la app nunca salen del servidor.

Fintech Conectando con Bancos: Cuando una app de gestión financiera quiere acceder a tu banco (Open Banking), te redirige al portal del banco, autorizas el acceso, el banco te devuelve con un código, y la app fintech obtiene un token para leer tus transacciones. Tus credenciales bancarias nunca pasan por la app de terceros.

E-commerce con PayPal: Al pagar con PayPal, te redirige a PayPal donde autorizas el pago. PayPal te devuelve a la tienda con un código de autorización. El servidor de la tienda usa ese código para completar la transacción. La tienda nunca ve tus datos de PayPal directamente.

Analogía

El Ticket del Guardarropa: Dejas tu abrigo y recibes un ticket numerado (código de autorización). El ticket por sí solo no es tu abrigo - cualquiera podría encontrarlo. Pero cuando lo presentas al encargado autorizado (tu servidor), junto con la identificación del establecimiento (client secret), recibes tu abrigo (access token). El encargado verifica que el ticket es válido y que tú eres quien debe recogerlo.

El Sistema de Turnos del Banco: Llegas al banco y sacas un ticket con número (código). Ese número no te da acceso a tu cuenta - solo te permite pasar al mostrador cuando te llamen. En el mostrador, presentas el ticket más tu DNI (client secret) para realizar operaciones. El ticket sin DNI es inútil.

La Reserva de Restaurante con Código: Haces una reserva online y recibes un código de confirmación. Al llegar, das el código al maître, pero también verifican tu nombre en la lista. El código solo confirma que hubo una reserva, pero el acceso real requiere verificación adicional.

El Sistema de Click & Collect: Compras online y recibes un código de recogida. En la tienda, das el código al empleado, quien verifica en su sistema y te entrega el producto. El código es solo el primer paso - el empleado hace la verificación real en su terminal interna, fuera de tu vista.

Diagrama

sequenceDiagram
    participant User as Usuario
    participant App as Aplicación Cliente
    participant Auth as Servidor de Autorización
    participant API as Servidor de Recursos

    User->>App: 1. Clic en "Iniciar sesión"
    App->>Auth: 2. Redirigir a /authorize
(client_id, redirect_uri, scope, state) Auth->>User: 3. Mostrar formulario de login User->>Auth: 4. Introducir credenciales Auth->>User: 5. Mostrar pantalla de consentimiento User->>Auth: 6. Aprobar permisos Auth->>App: 7. Redirigir con código de autorización Note over App: El código es de un solo uso,
expira en minutos App->>Auth: 8. Intercambiar código por tokens
(código + client_secret) Note over App,Auth: Llamada servidor-a-servidor
(canal seguro) Auth->>App: 9. Access token + Refresh token App->>API: 10. Petición API con Bearer token API->>App: 11. Recurso protegido

Code Example


// Paso 1: Redirigir al servidor de autorización
const authUrl = 'https://oauth.proveedor.com/authorize?' +
  'response_type=code&' +
  'client_id=tu_client_id&' +
  'redirect_uri=https://tuapp.com/callback&' +
  'scope=read:user%20read:email&' +
  'state=token_csrf_aleatorio';  // CRÍTICO: prevenir CSRF

// En el navegador
window.location.href = authUrl;

// Paso 2: Manejar callback e intercambiar código por token (lado servidor)
// El usuario fue redirigido a: https://tuapp.com/callback?code=ABC123&state=token_csrf_aleatorio

app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // CRÍTICO: Verificar state para prevenir CSRF
  if (state !== req.session.oauthState) {
    return res.status(403).json({ error: 'State inválido' });
  }

  // Intercambiar código por token (servidor a servidor)
  const tokenResponse = await fetch('https://oauth.proveedor.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      client_id: 'tu_client_id',
      client_secret: process.env.CLIENT_SECRET,  // NUNCA exponer al navegador
      redirect_uri: 'https://tuapp.com/callback'
    })
  });

  const tokens = await tokenResponse.json();
  // tokens = { access_token: "...", refresh_token: "...", expires_in: 3600 }

  // Almacenar tokens de forma segura y crear sesión de usuario
  req.session.accessToken = tokens.access_token;
  res.redirect('/dashboard');
});

Notas de Seguridad

SECURITY NOTES

CRÍTICO - …

Configuración y Validación:

  • Usa SIEMPRE la extensión PKCE para clientes públicos (SPAs, apps móviles) ya que no pueden guardar secretos de forma segura.
  • Valida el parámetro state en cada callback para prevenir ataques CSRF - genera un valor aleatorio único por sesión y compáralo al recibir el callback.
  • Los códigos de autorización deben ser de un solo uso y expirar en máximo 10 minutos.
  • NUNCA intercambies códigos en el navegador - siempre en el servidor backend donde puedes usar el client_secret de forma segura.

Monitoreo y Protección:

  • Valida redirect_uri de forma exacta, sin comodines ni redirecciones abiertas - los atacantes pueden registrar dominios similares.
  • El client_secret nunca debe exponerse a navegadores, apps móviles o código frontend.
  • Usa HTTPS obligatoriamente para todas las redirecciones y endpoints.
  • Implementa gestión de sesión robusta post-autenticación con tokens de sesión seguros.
  • Almacena client_secrets en bóvedas seguras o variables de entorno, nunca en código fuente o repositorios.

Estándares y RFCs