Definición
Los métodos no seguros son métodos HTTP que están diseñados para modificar el estado del servidor - crean, actualizan o eliminan recursos. A diferencia de los métodos seguros (GET, HEAD, OPTIONS) que son de solo lectura, los métodos no seguros tienen efectos secundarios que cambian los datos o estado del servidor. Los cuatro métodos no seguros principales son POST, PUT, PATCH y DELETE.
El término “no seguro” no significa peligroso o inseguro - significa que estos métodos no son seguros de reintentar ciegamente sin consecuencias. Si accidentalmente refrescas una página después de enviar un formulario con POST, podrías crear un pedido duplicado o enviar un email duplicado. Por esto los navegadores muestran advertencias “¿Estás seguro de que quieres reenviar este formulario?” para peticiones POST.
Debido a que los métodos no seguros modifican estado, requieren manejo más cuidadoso: autenticación y autorización apropiadas, protección CSRF, claves de idempotencia para reintentos, limitación de tasa para prevenir abuso y logging de auditoría. Mientras PUT y DELETE son idempotentes (seguros de reintentar), POST y PATCH generalmente no lo son, haciendo la lógica de reintento más compleja. Los métodos no seguros son los caballos de batalla de las APIs REST - son cómo realmente HACES cosas en lugar de solo leer información.
Ejemplo
POST - Crear Recurso: Cuando te registras para una cuenta, el navegador envía POST /api/users con tu email y contraseña. Esto crea un nuevo registro de usuario. Enviar el mismo POST nuevamente crea otro usuario duplicado (a menos que la API implemente verificaciones de idempotencia).
PUT - Reemplazar Recurso: Cuando editas tu perfil y guardas, el cliente envía PUT /api/users/123 con todos tus campos de perfil. Esto reemplaza completamente el recurso de usuario. PUT es idempotente - enviarlo 5 veces resulta en los mismos datos de perfil.
PATCH - Actualización Parcial: Para cambiar solo tu dirección de email, el cliente envía PATCH /api/users/123 con {"email": "[email protected]"}. Esto actualiza solo el campo de email. PATCH puede ser idempotente si se diseña cuidadosamente (valores absolutos) pero a menudo no lo es (cambios incrementales).
DELETE - Eliminar Recurso: Cuando eliminas una publicación, el cliente envía DELETE /api/posts/456. Esto elimina la publicación. DELETE es idempotente - eliminar la misma publicación 10 veces resulta en que la publicación esté eliminada (intentos subsecuentes devuelven 404).
Checkout de E-commerce: Cuando haces clic en “Hacer Pedido,” el cliente envía POST /api/orders con ítems del carrito e información de envío. Esto crea un pedido, decrementa inventario y cobra tu tarjeta. Refrescar accidentalmente crearía un pedido duplicado - por eso los sistemas de pago usan claves de idempotencia.
Botón de Me Gusta en Redes Sociales: Hacer clic en “me gusta” en una publicación podría enviar POST /api/posts/789/likes. Esto crea un registro de me gusta. Hacer clic nuevamente podría quitar el me gusta (toggle) o crear un duplicado. Las APIs bien diseñadas hacen esto idempotente: POST crea me gusta si no existe, devuelve me gusta existente si existe.
Carga de Archivo: Subir un archivo envía PUT /api/files/photo.jpg con el contenido del archivo. PUT es idempotente - subir el mismo archivo múltiples veces resulta en que el mismo archivo sea almacenado, sobrescribiendo versiones previas.
Analogía
Escribir en un Cuaderno: Cuando escribes algo en un cuaderno, lo cambias permanentemente. No puedes “deshacer” la escritura leyendo la página. POST, PUT, PATCH y DELETE son como escribir - modifican el estado y tienen efectos duraderos.
Enviar un Formulario: Cuando envías por correo un formulario físico (como una declaración de impuestos), no puedes des-enviarlo abriendo tu buzón y mirando adentro. El envío (POST) es una operación no segura con consecuencias. Leer tu buzón (GET) es seguro.
Hacer una Compra: Comprar algo en una tienda cambia el estado - el dinero sale de tu cuenta, el inventario disminuye, se registra una transacción. Esta es una operación no segura. Navegar productos es seguro, pero “Agregar al Carrito” y “Checkout” son operaciones no seguras.
Editar una Pizarra: Borrar y reescribir una pizarra cambia su estado permanentemente. Esto es como PUT (reemplazar) o PATCH (actualizar parte de ella). Mirar la pizarra (GET) no la cambia.
Eliminar un Archivo: Cuando eliminas un archivo de tu computadora, desaparece (o va a la papelera). Puedes eliminarlo múltiples veces, pero después de la primera vez ya está eliminado - similar a cómo DELETE es idempotente. Leer el archivo (GET) no lo elimina.
Ejemplo de Código
# ===== POST - Crear Recurso (NO Idempotente) =====
# Cada petición crea un nuevo recurso
POST /api/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"product_id": 456,
"quantity": 2,
"shipping_address": "123 Main St"
}
# Respuesta
HTTP/1.1 201 Created
Location: /api/orders/789
Content-Type: application/json
{
"id": 789,
"product_id": 456,
"quantity": 2,
"total": 39.98,
"status": "pending"
}
# Reintentar crea OTRO pedido (id: 790) - NO idempotente
# Solución: Usar cabecera Idempotency-Key
POST /api/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
"product_id": 456,
"quantity": 2
}
# Reintentar con misma clave devuelve pedido original (id: 789) - AHORA idempotente
# ===== PUT - Reemplazar Recurso (Idempotente) =====
# Reemplaza recurso completo
PUT /api/products/456 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"name": "Widget Actualizado",
"price": 12.99,
"stock": 150,
"description": "Nueva descripción"
}
# Respuesta
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 456,
"name": "Widget Actualizado",
"price": 12.99,
"stock": 150,
"description": "Nueva descripción"
}
# Reintentar con mismos datos - recurso termina en mismo estado (idempotente)
# ===== PATCH - Actualización Parcial (Usualmente NO Idempotente) =====
# Actualiza solo campos especificados
PATCH /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"email": "[email protected]"
}
# Respuesta
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "John Doe",
"email": "[email protected]"
}
# Idempotente si se establecen valores absolutos
# NO idempotente si se usan operaciones como incremento:
PATCH /api/products/456 HTTP/1.1
Content-Type: application/json
{
"stock": { "$inc": -1 } # Decrementar en 1
}
# Reintentar decrementa nuevamente - ¡NO idempotente!
# ===== DELETE - Eliminar Recurso (Idempotente) =====
# Primera petición
DELETE /api/posts/789 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
# Respuesta
HTTP/1.1 204 No Content
# Reintentar - recurso ya eliminado
DELETE /api/posts/789 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
# Respuesta (puede ser 404 o 204, ambos aceptables)
HTTP/1.1 404 Not Found
# El resultado es el mismo: el recurso no existe (idempotente)
// Ejemplos en JavaScript de métodos no seguros
// ===== POST - Crear Recurso =====
async function createOrder(productId, quantity) {
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
product_id: productId,
quantity: quantity
})
});
if (response.ok) {
const order = await response.json();
console.log('Pedido creado:', order.id);
return order;
}
throw new Error('Falló la creación del pedido');
}
// Llamar dos veces crea DOS pedidos (no seguro de reintentar)
await createOrder(456, 2); // Crea pedido #789
await createOrder(456, 2); // Crea pedido #790 (¡duplicado!)
// POST con Clave de Idempotencia
async function createOrderSafely(productId, quantity, idempotencyKey) {
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify({
product_id: productId,
quantity: quantity
})
});
return response.json();
}
const key = crypto.randomUUID();
await createOrderSafely(456, 2, key); // Crea pedido #789
await createOrderSafely(456, 2, key); // Devuelve pedido #789 (el mismo)
// ===== PUT - Reemplazar Recurso =====
async function updateProduct(id, productData) {
const response = await fetch(`/api/products/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(productData)
});
return response.json();
}
// PUT es idempotente - seguro de reintentar
const product = {
name: 'Widget Actualizado',
price: 12.99,
stock: 150
};
await updateProduct(456, product); // Producto actualizado
await updateProduct(456, product); // Mismo resultado (idempotente)
// ===== PATCH - Actualización Parcial =====
async function updateUserEmail(userId, newEmail) {
const response = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
email: newEmail
})
});
return response.json();
}
// PATCH idempotente (valor absoluto)
await updateUserEmail(123, '[email protected]'); // Email cambiado
await updateUserEmail(123, '[email protected]'); // Email sigue siendo '[email protected]' (idempotente)
// PATCH no idempotente (operación de incremento)
async function incrementViewCount(postId) {
const response = await fetch(`/api/posts/${postId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
views: { $inc: 1 }
})
});
return response.json();
}
await incrementViewCount(789); // Vistas: 101
await incrementViewCount(789); // Vistas: 102 (¡NO idempotente!)
// ===== DELETE - Eliminar Recurso =====
async function deletePost(postId) {
const response = await fetch(`/api/posts/${postId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.status === 204 || response.status === 404) {
console.log('Publicación eliminada o ya eliminada');
return true;
}
throw new Error('Falló la eliminación de la publicación');
}
// DELETE es idempotente - seguro de reintentar
await deletePost(789); // 204 No Content (eliminado)
await deletePost(789); // 404 Not Found (ya eliminado)
// El resultado es el mismo: la publicación no existe
// ===== Manejo de Errores y Lógica de Reintento =====
async function safeRetry(operation, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Backoff exponencial
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Seguro de reintentar operaciones idempotentes
await safeRetry(() => updateProduct(456, product)); // PUT - seguro
await safeRetry(() => deletePost(789)); // DELETE - seguro
// NO seguro de reintentar operaciones no idempotentes sin clave de idempotencia
// await safeRetry(() => createOrder(456, 2)); // ¡NO HAGAS ESTO - crea duplicados!
// Seguro con clave de idempotencia
const idempKey = crypto.randomUUID();
await safeRetry(() => createOrderSafely(456, 2, idempKey)); // Seguro ahora
Diagrama
graph TB
subgraph "Métodos No Seguros (Modifican Estado)"
POST[POST
Crear recurso]
PUT[PUT
Reemplazar recurso]
PATCH[PATCH
Actualización parcial]
DELETE[DELETE
Eliminar recurso]
end
subgraph "Idempotencia"
I1[POST: NO idempotente
a menos que use claves de idempotencia]
I2[PUT: Idempotente
Seguro de reintentar]
I3[PATCH: Usualmente NO
idempotente si es incremental]
I4[DELETE: Idempotente
Seguro de reintentar]
end
POST --> I1
PUT --> I2
PATCH --> I3
DELETE --> I4
subgraph "Preocupaciones de Seguridad"
S1[Autenticación Requerida]
S2[Verificaciones de Autorización]
S3[Protección CSRF]
S4[Limitación de Tasa]
S5[Logging de Auditoría]
S6[Validación de Entrada]
end
POST -.-> S1
POST -.-> S2
POST -.-> S3
POST -.-> S4
POST -.-> S5
POST -.-> S6
PUT -.-> S1
PUT -.-> S2
PUT -.-> S3
PUT -.-> S6
PATCH -.-> S1
PATCH -.-> S2
PATCH -.-> S3
PATCH -.-> S6
DELETE -.-> S1
DELETE -.-> S2
DELETE -.-> S3
DELETE -.-> S4
DELETE -.-> S5
subgraph "Casos de Uso"
U1[POST: Registrarse, Crear pedido, Subir archivo]
U2[PUT: Actualizar perfil, Reemplazar documento]
U3[PATCH: Cambiar email, Actualizar estado]
U4[DELETE: Eliminar cuenta, Eliminar publicación]
end
style POST fill:#FFB6C1
style PUT fill:#FFD700
style PATCH fill:#FFA500
style DELETE fill:#FF6347
Notas de Seguridad
CRÍTICO - …
Configuración y Validación:
- Todos los métodos no seguros DEBEN implementar autenticación y autorización apropiadas.
- Nunca confiar en entrada del cliente - validar y sanitizar todos los datos antes de procesar.
- Implementar protección CSRF usando tokens o cookies SameSite para operaciones que cambien estado.
- Usar limitación de tasa para prevenir abuso - limitar peticiones POST, PUT, PATCH, DELETE por usuario por ventana de tiempo.
- Implementar claves de idempotencia para POST para prevenir creación de recursos duplicados desde reintentos o ataques de replay maliciosos.
- Registrar todas las operaciones que cambien estado con ID de usuario, timestamp y detalles para rastros de auditoría.
- Validar propiedad de recursos antes de permitir modificaciones - asegurar que los usuarios solo puedan modificar sus propios recursos.
- Usar HTTPS exclusivamente para proteger credenciales y datos sensibles en cuerpos de petición.
Monitoreo y Protección:
- Implementar manejo de errores apropiado sin exponer detalles internos o stack traces.
- Para operaciones DELETE implementar eliminaciones suaves o pasos de confirmación para datos críticos.
- Usar verificaciones de autorización fuertes - no solo autenticación.
- Un usuario autenticado no debería poder eliminar recursos de otro usuario.
- Implementar rollback de transacciones para operaciones complejas que involucren múltiples recursos.
- Monitorear patrones sospechosos - creación rápida, eliminación masiva o patrones de actualización inusuales.
- Usar validación de Content-Type para prevenir ataques de confusión de tipo de contenido.
- Implementar límites de tamaño en cuerpos de petición para prevenir DoS vía payloads grandes.
Mejores Prácticas
- Siempre requerir autenticación: Los métodos no seguros nunca deberían ser accesibles sin credenciales válidas
- Implementar autorización apropiada: Verificar que el usuario tenga permiso para realizar la acción
- Usar claves de idempotencia para POST: Prevenir creación de recursos duplicados desde reintentos
- Devolver códigos de estado apropiados: 201 para POST crear, 200/204 para PUT/PATCH/DELETE
- Incluir cabecera Location para POST: Apuntar al recurso recién creado
- Soportar actualizaciones parciales con PATCH: No requerir que los clientes envíen el recurso completo
- Hacer DELETE idempotente: Devolver 204 o 404 en reintento, no error
- Validar entrada exhaustivamente: Verificar tipos de datos, formatos, rangos, campos requeridos
- Implementar limitación de tasa: Prevenir abuso de operaciones que cambien estado
- Registrar rastros de auditoría: Registrar quién hizo qué y cuándo para responsabilidad
Errores Comunes
Sin protección CSRF: Operaciones que cambien estado vulnerables a ataques de falsificación de petición entre sitios.
Falta de autenticación: Permitir peticiones POST, PUT, PATCH, DELETE sin autenticar.
Autorización débil: Verificar si el usuario está autenticado pero no si posee el recurso que está modificando.
Sin idempotencia para POST: Crear recursos duplicados cuando los clientes reintentan peticiones fallidas.
Usar POST para todo: No usar PUT para actualizaciones o DELETE para eliminación - viola principios REST.
No devolver 201 para POST: Devolver 200 en lugar de 201 Created con cabecera Location.
Tratar 404 en DELETE como error: DELETE es idempotente - 404 en reintento es aceptable, no un fallo.
Sin limitación de tasa: Permitir peticiones POST ilimitadas habilita spam, abuso y ataques DoS.
Exponer errores internos: Devolver errores de base de datos o stack traces en respuestas 500.
Permitir PATCH sin validación: Aceptar parches JSON arbitrarios sin verificar campos permitidos.
Sin logging de auditoría: No registrar quién creó, actualizó o eliminó recursos.
Ignorar límites de tamaño de datos: Aceptar tamaño de cuerpo de petición ilimitado para operaciones POST/PUT.