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
- Resource Owner: Usuario o sistema que posee los recursos
- Client: Aplicación que necesita acceso a los recursos
- Authorization Server: Emite tokens y los verifica
- Resource Server: Aloja los recursos protegidos
- Access Token: Token de corta duración para acceso a recursos
- Refresh Token: Token de larga duración para renovar tokens
- Authorization Code: Código temporal para canje de token
- Redirect URI: URL de destino después de la autorización
- Scope: Define el alcance del acceso
- 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
-
¿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).
-
Explica el Authorization Code Flow. El cliente redirige al usuario a Authorization Server, recibe un código y lo canjea por un token.
-
¿Cuándo se utiliza Client Credentials Flow? Para comunicación servidor a servidor sin interacción del usuario.
-
¿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
- https://tools.ietf.org/html/rfc6749
- https://oauth.net/2/
- https://auth0.com/docs/authorization-framework/overview



