Saltar al contenido
Open Security
Backend y Arquitectura Intermedio · 30 min

JWT no es autorización

Un token válido te dice quién sos, no qué podés hacer. La diferencia entre autenticación y autorización, y por qué confundirlas produce IDOR.

#backend#auth#idor

Antes de empezar necesitás

  • Saber qué es una API HTTP y un endpoint
  • Idea básica de qué es un token de sesión

Al terminar vas a poder

  • Distinguir autenticación (authn) de autorización (authz)
  • Leer el payload de un JWT y entender qué garantiza y qué no
  • Reconocer un IDOR en el diseño de un endpoint
  • Escribir el chequeo de ownership que falta

Un equipo agrega JWT, mete un middleware de login y da la seguridad por cerrada. El problema es que JWT resuelve una pregunta —¿quién sos?— y la gente cree que resolvió otra —¿esto lo podés hacer?—. No es lo mismo, y la confusión tiene nombre: IDOR.

Qué es (y qué no es) un JWT

Un JWT es un texto con tres partes separadas por puntos: header.payload.signature. El payload son claims (afirmaciones) en JSON, codificados en base64. La firma garantiza que nadie alteró esos claims.

vt@labs:~
# El payload de un JWT no está cifrado, solo codificado en base64.
echo "$JWT" | cut -d '.' -f2 | base64 -d 2>/dev/null

Vas a ver algo así:

{
  "sub": "user_42",
  "email": "valen@example.com",
  "role": "user",
  "exp": 1735689600
}

Pegá un JWT (usá uno de juguete, nunca uno real de producción) y mirá cómo se separa en header, payload y firma. Todo se decodifica en tu navegador: nada se envía a ningún lado.

Decodificador de JWT

Pegá un token y mirá qué dice por dentro. Un JWT está firmado, no cifrado: cualquiera lee el contenido. Acá no verificamos la firma y el token nunca sale de tu navegador.

El error: confiar en la identidad sin chequear el recurso

Mirá este endpoint. El token es válido, el usuario está autenticado. ¿Está bien?

GET /api/invoices/{id}

función obtener_factura(request, id):
    usuario = validar_jwt(request.headers["Authorization"])   # authn ✓
    si usuario es inválido:
        responder 401

    factura = db.buscar_factura(id)                           # ← sin authz
    responder 200, factura

Está roto. El usuario user_42 tiene un token perfectamente válido para su factura 100. Pero nada le impide pedir la 101:

vt@labs:~
# Mismo token válido, otro id. Si responde 200, es IDOR.
curl -s -H "Authorization: Bearer $JWT" https://api.example.com/api/invoices/101

El fix: chequear ownership

El arreglo no es criptografía nueva. Es una condición que muchas veces falta:

GET /api/invoices/{id}

función obtener_factura(request, id):
    usuario = validar_jwt(request.headers["Authorization"])   # authn ✓
    si usuario es inválido:
        responder 401

    factura = db.buscar_factura(id)
    si factura es nula:
        responder 404

    si factura.owner_id != usuario.sub:                       # authz ✓
        responder 404        # 404, no 403: no revelamos que existe

    responder 200, factura

El criterio para revisar APIs

JWT, OAuth o sesiones resuelven el transporte de identidad. La autorización es trabajo aparte y va por endpoint y por recurso. Una tabla mínima para revisar:

endpoint              authn   ownership/authz        notas
GET /invoices/{id}    sí      owner_id == sub        404 si no es tuyo
POST /invoices        sí      rol puede crear        validar input
DELETE /invoices/{id} sí      owner o rol admin      log de la acción
GET /admin/users      sí      rol == admin           rate limit

Tu API no es segura por usar JWT. Es segura cuando, además de saber quién entra, decide bien qué puede tocar.

Lo que practicás en este lab

Llevátelo a tu repo si querés, pero no es obligatorio: es tu aprendizaje.

  • Writeup de 2 párrafos: por qué un token válido no alcanza
  • Pseudocódigo del endpoint vulnerable y del corregido
  • Una tabla chica: por endpoint, qué chequeo de autorización aplica

Reto

Tomá el endpoint vulnerable de abajo y agregá el chequeo de ownership. Escribí en dos líneas qué request maliciosa deja de funcionar después del fix.

Resolvelo y escribí dos líneas explicando qué pasó. Con eso lo fijás.

¿Hiciste el lab?

Si querés, guardá lo que hiciste (comandos, notas, un repo) para volver después. Y si encontrás un error o querés mejorar este lab, contribuí al repo. El progreso se guarda solo en tu navegador.