Skip to content
IRC-CodingIRC-Coding
OAuth 2.0Authorization Code FlowAccess TokenRefresh TokenAPI Security

OAuth 2.0: Authorization Code, Flows & Access Token

OAuth 2.0 es el framework de autorización estándar. Authorization Code Flow, Client Credentials, Access/Refresh Token con ejemplos.

S

schutzgeist

5 min read
OAuth 2.0: Authorization Code, Flows & Access Token

OAuth 2.0: Authorization Code, Flows y Access Token

Este artículo es una explicación de conceptos sobre el framework de autorización OAuth 2.0, incluyendo flujos, tokens e implementación práctica.

En resumen

OAuth 2.0 es un framework de autorización estandarizado que permite a aplicaciones de terceros acceder a recursos de usuarios sin conocer sus credenciales de acceso.

Descripción técnica compacta

OAuth 2.0 es un protocolo de autorización ampliamente utilizado para aplicaciones web, de escritorio y móviles. Permite que una aplicación (cliente) acceda a recursos de otro sistema (servidor de recursos) en nombre de un usuario.

Características clave:

  • Delegación: Los usuarios delegan derechos de acceso a las aplicaciones
  • Basado en tokens: Access tokens en lugar de contraseñas
  • Separación de roles: Distinción clara entre los actores involucrados
  • Estandarizado: RFC 6749 define el framework

Roles participantes:

  • Resource Owner: El usuario que posee los recursos
  • Client: La aplicación que desea acceder a los recursos
  • Authorization Server: Emite y valida tokens
  • Resource Server: Proporciona los recursos protegidos

OAuth 2.0 no transmite la identidad del usuario, solo autoriza acceso a recursos. Para verificación de identidad existe OpenID Connect.

Puntos clave para pruebas

  • Framework de autorización, no de autenticación
  • Roles: Resource Owner, Client, Authorization Server, Resource Server
  • Access Token y opcionalmente Refresh Token
  • Authorization Code Flow para aplicaciones web
  • Client Credentials Flow para comunicación servidor a servidor
  • Basado en HTTP y compatible con REST APIs
  • Acceso basado en tokens aumenta la seguridad
  • Protección contra el compartir contraseñas mediante mecanismo de delegación

Componentes principales

  1. Resource Owner: Usuario o sistema que posee los recursos
  2. Client: Aplicación que necesita acceso a los recursos
  3. Authorization Server: Emite tokens y los verifica
  4. Resource Server: Aloja los recursos protegidos
  5. Access Token: Token de corta duración para acceso a recursos
  6. Refresh Token: Token de larga duración para renovar tokens
  7. Authorization Code: Código temporal para canje de token
  8. Redirect URI: URL de destino después de la autorización
  9. Scope: Define el alcance del acceso
  10. Grant Type: Determina el flujo de OAuth 2.0

Flujos de OAuth 2.0

1. Authorization Code Flow (para aplicaciones web)

1. Client → User: Redirige a Authorization Server
2. User → Authorization Server: Inicia sesión y aprueba
3. Authorization Server → Client: Authorization Code
4. Client → Authorization Server: Canjea código por Access Token
5. Authorization Server → Client: Access Token + Refresh Token
6. Client → Resource Server: Solicita recursos con Access Token

2. Client Credentials Flow (para servidor a servidor)

1. Client → Authorization Server: Envía Client Credentials
2. Authorization Server → Client: Access Token
3. Client → Resource Server: Solicita recursos con Access Token

3. Implicit Flow (obsoleto, para single-page apps)

1. Client → User: Redirige a Authorization Server
2. User → Authorization Server: Inicia sesión y aprueba
3. Authorization Server → Client: Access Token directamente en el fragmento

Ejemplos prácticos

Implementación de Authorization Code Flow

// Frontend: Solicita autorización
const authUrl = 'https://auth.example.com/authorize';
const params = new URLSearchParams({
  response_type: 'code',
  client_id: 'your_client_id',
  redirect_uri: 'https://yourapp.com/callback',
  scope: 'read:profile write:data',
  state: generateRandomString() // Protección CSRF
});

window.location.href = `${authUrl}?${params}`;

// Manejador de callback
async function handleCallback(code, state) {
  // Valida el estado
  if (state !== getStoredState()) {
    throw new Error('Invalid state - CSRF attack detected');
  }
  
  // Canjea el código por token
  const tokenResponse = await fetch('https://auth.example.com/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      client_id: 'your_client_id',
      client_secret: 'your_client_secret',
      redirect_uri: 'https://yourapp.com/callback'
    })
  });
  
  const tokens = await tokenResponse.json();
  localStorage.setItem('access_token', tokens.access_token);
  localStorage.setItem('refresh_token', tokens.refresh_token);
}

Backend: Validación de tokens

// Ejemplo con Spring Boot
@RestController
@RequestMapping("/api")
public class ApiController {
    
    @GetMapping("/profile")
    public ResponseEntity<UserProfile> getProfile(
            @RequestHeader("Authorization") String authHeader) {
        
        // Extrae y valida el token
        String token = authHeader.replace("Bearer ", "");
        
        if (!tokenValidator.isValid(token)) {
            return ResponseEntity.status(401).build();
        }
        
        // Extrae los claims del token
        Claims claims = tokenValidator.getClaims(token);
        String userId = claims.getSubject();
        List<String> scopes = claims.get("scope", List.class);
        
        // Verifica el scope
        if (!scopes.contains("read:profile")) {
            return ResponseEntity.status(403).build();
        }
        
        UserProfile profile = userService.getProfile(userId);
        return ResponseEntity.ok(profile);
    }
    
    @PostMapping("/data")
    public ResponseEntity<?> createData(
            @RequestHeader("Authorization") String authHeader,
            @RequestBody DataRequest request) {
        
        String token = authHeader.replace("Bearer ", "");
        
        if (!tokenValidator.isValid(token)) {
            return ResponseEntity.status(401).build();
        }
        
        Claims claims = tokenValidator.getClaims(token);
        List<String> scopes = claims.get("scope", List.class);
        
        // Verifica permiso de escritura
        if (!scopes.contains("write:data")) {
            return ResponseEntity.status(403).build();
        }
        
        Data data = dataService.create(request, claims.getSubject());
        return ResponseEntity.ok(data);
    }
}

Client Credentials Flow

# Ejemplo en Python para comunicación servidor a servidor
import requests

def get_access_token():
    token_url = "https://auth.example.com/token"
    
    data = {
        'grant_type': 'client_credentials',
        'client_id': 'your_client_id',
        'client_secret': 'your_client_secret',
        'scope': 'api:read api:write'
    }
    
    response = requests.post(token_url, data=data)
    response.raise_for_status()
    
    token_data = response.json()
    return token_data['access_token']

def api_call():
    access_token = get_access_token()
    
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json'
    }
    
    response = requests.get(
        'https://api.example.com/data',
        headers=headers
    )
    
    return response.json()

Flujo de Refresh Token

// Renueva el token con Refresh Token
async function refreshAccessToken() {
  const refreshToken = localStorage.getItem('refresh_token');
  
  if (!refreshToken) {
    throw new Error('No refresh token available');
  }
  
  try {
    const response = await fetch('https://auth.example.com/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        client_id: 'your_client_id',
        client_secret: 'your_client_secret'
      })
    });
    
    if (!response.ok) {
      throw new Error('Token refresh failed');
    }
    
    const tokens = await response.json();
    localStorage.setItem('access_token', tokens.access_token);
    
    if (tokens.refresh_token) {
      localStorage.setItem('refresh_token', tokens.refresh_token);
    }
    
    return tokens.access_token;
  } catch (error) {
    // Refresh Token inválido, se requiere nuevo login
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
    redirectToLogin();
  }
}

Consideraciones de seguridad

State Parameter (Protección CSRF)

function generateState() {
  return Math.random().toString(36).substring(2, 15) +
         Math.random().toString(36).substring(2, 15);
}

function storeState(state) {
  sessionStorage.setItem('oauth_state', state);
}

function validateState(receivedState) {
  const storedState = sessionStorage.getItem('oauth_state');
  sessionStorage.removeItem('oauth_state');
  return storedState === receivedState;
}

PKCE (Proof Key for Code Exchange)

// PKCE para aplicaciones móviles y single-page
function generatePKCE() {
  const codeVerifier = generateRandomString(128);
  const codeChallenge = base64urlEncode(sha256(codeVerifier));
  
  return { codeVerifier, codeChallenge };
}

// Solicitud de autorización con PKCE
const params = new URLSearchParams({
  response_type: 'code',
  client_id: 'your_client_id',
  code_challenge: pkce.codeChallenge,
  code_challenge_method: 'S256',
  redirect_uri: 'https://yourapp.com/callback'
});

Ventajas y desventajas

Ventajas

  • Seguridad: No se comparten contraseñas con terceros
  • Estandarización: Ampliamente utilizado y bien documentado
  • Flexibilidad: Diferentes flujos para diversos casos de uso
  • Escalabilidad: Autorización centralizada para múltiples servicios
  • Revocación: Los tokens pueden ser revocados en cualquier momento

Desventajas

  • Complejidad: Más pasos que la autenticación simple
  • Rendimiento: Llamadas de red adicionales para renovación de tokens
  • Gestión de estado: El cliente debe mantener el estado de los tokens
  • Riesgos de seguridad: Una implementación incorrecta puede crear vulnerabilidades

Preguntas frecuentes de pruebas

  1. ¿Cuál es la diferencia entre OAuth 2.0 y OpenID Connect? OAuth 2.0 es para autorización, OpenID Connect añade identidad (autenticación).

  2. Explica el Authorization Code Flow. El cliente redirige al usuario a Authorization Server, recibe un código y lo canjea por un token.

  3. ¿Cuándo se utiliza Client Credentials Flow? Para comunicación servidor a servidor sin interacción del usuario.

  4. ¿Cuál es el propósito del parámetro State? Protección contra CSRF verificando que la solicitud fue iniciada por el cliente.

Recursos principales

  1. https://tools.ietf.org/html/rfc6749
  2. https://oauth.net/2/
  3. https://auth0.com/docs/authorization-framework/overview
Volver al blog
Share:

Entradas relacionadas