Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 7 de 30

Autenticación: claves, tokens y OAuth

Sesenta peticiones por hora contra cinco mil, el token que cualquiera puede leer, y el secreto que sobrevive a una redirección.

⏱ 16 min de lectura

Identificarse ante una API no es solo poder entrar en lo privado. En GitHub, el límite pasa de sesenta peticiones por hora a cinco mil:

Salida en pantalla
sin identificarse: 60 peticiones/hora
identificado:      5000 peticiones/hora     (83 veces más)

Así que la primera razón para autenticarse suele ser poder trabajar, y no el acceso a nada secreto.

El problema es que hay cuatro sitios donde meter una credencial, media docena de esquemas con nombres parecidos, y un montón de formas de que el secreto acabe en un sitio donde no debería.

Los cuatro sitios#

Medidos contra un servidor de eco, con lo que llega al otro lado:

Salida en pantalla
1  headers={'Authorization': 'Bearer secreto123'}  ->  Authorization: Bearer secreto123
2  auth=('ana', 'clave')                           ->  Authorization: Basic YW5hOmNsYXZl
3  headers={'X-API-Key': 'abc123'}                 ->  X-Api-Key: abc123
4  params={'api_key': 'abc123'}                    ->  ?api_key=abc123

El cuarto está mal, y esa es la primera regla de la lección. Una credencial en la dirección acaba en los registros del servidor, en los del repartidor de carga, en el historial del navegador y en la cabecera Referer que se manda al pinchar un enlace. Todos esos sitios se guardan durante años y los lee gente que no debería.

Algunas APIs solo ofrecen esa forma, y entonces no hay opción. Cuando ofrecen las dos, siempre la cabecera.

Autenticación básica#

Es la más antigua y sigue viva. requests la trae de serie:

python
requests.get(url, auth=('ana', 'clave'))

Lo que manda:

Salida en pantalla
Authorization: Basic YW5hOmNsYXZl
descodificado: ana:clave

Eso no está cifrado. Es base64, que es una codificación reversible por cualquiera. El usuario y la contraseña viajan a efectos prácticos en claro, y lo único que los protege es que la conexión sea HTTPS.

Sigue siendo aceptable en dos casos: sobre HTTPS y con una contraseña que sea un token generado, no la de la persona. Muchas APIs la usan así, poniendo el token de usuario y dejando la contraseña vacía.

Tokens#

Es lo normal hoy, y el esquema se llama Bearer, que significa "portador": quien lo lleva, entra. No hay nada más que comprobar.

python
sesion.headers['Authorization'] = f'Bearer {token}'

Eso implica que un token robado es acceso completo hasta que caduque o lo revoquen, y de ahí salen las tres reglas prácticas: que caduquen, que se puedan revocar, y que nunca estén en el código.

Hay dos familias de tokens y se confunden constantemente.

Tokens opacos. Una cadena aleatoria que no significa nada. El servidor la busca en su base de datos y mira a quién pertenece. Ventaja: se revocan borrando una fila, al instante. Inconveniente: una consulta por petición.

Tokens con contenido, los JWT. Traen la información dentro y van firmados, así que el servidor los valida sin consultar nada.

Qué es un JWT de verdad#

Esto genera más confusión que ninguna otra cosa del tema, así que vamos a abrir uno:

Salida en pantalla
trozos: 3

cabecera  {'alg': 'HS256', 'typ': 'JWT'}
carga     {'sub': '123', 'nombre': 'Ana', 'exp': 1790100000, 'rol': 'admin'}
firma     (lo único que lo protege)

Tres trozos separados por puntos, y los dos primeros son base64 que cualquiera descodifica. Ahí están el usuario, el nombre y el rol, legibles con tres líneas de Python y sin ninguna clave.

De ahí salen las dos consecuencias que hay que tener claras:

Un JWT no está cifrado, está firmado. La firma impide modificarlo, no leerlo. Nunca metas dentro nada que no quieras que se vea: ni contraseñas, ni datos personales de más, ni identificadores internos que revelen cosas.

Un JWT no se puede revocar. Es su gran inconveniente y el que la gente descubre tarde. Como el servidor lo valida solo con la firma, no consulta nada, y por tanto no hay nada que borrar. Si alguien roba un token con dos horas de vida, tiene dos horas de acceso y no puedes hacer nada.

Por eso el esquema habitual son dos tokens:

VidaPara qué
De accesominutosVa en cada petición
De refrescodías o semanasSolo sirve para pedir uno de acceso nuevo

El de acceso caduca enseguida, así que robarlo sirve de poco. El de refresco dura, y como solo se usa contra un punto concreto y sí se guarda en la base de datos, se puede revocar.

El exp de arriba, 1790100000, es la fecha de caducidad en segundos desde 1970:

Salida en pantalla
1790100000 = 2026-09-22 18:00:00+00:00

Comprobarla antes de mandar la petición, en lugar de esperar el 401, ahorra una ida y vuelta:

python
import time

def token_valido(carga, margen=60):
        return carga['exp'] - margen > time.time()

El margen es importante: si el token caduca dentro de dos segundos, la petición llegará caducada.

OAuth#

OAuth asusta por el diagrama, y la idea es sencilla: dar permiso a un programa sin darle tu contraseña.

Cuando una aplicación te pide entrar con Google y te lleva a una página de Google donde dice "esta aplicación quiere ver tu correo, ¿aceptas?", eso es OAuth. Tu contraseña se la das a Google, no a la aplicación, y la aplicación recibe un token limitado a lo que aceptaste.

Hay dos casos y es importante distinguirlos, porque casi todo lo que asusta es del segundo:

Cuando el token es tuyo y ya está. Entras en la web de la API, generas un token personal, lo copias. No hay flujo ni diagrama:

python
sesion.headers['Authorization'] = f'Bearer {os.environ["TOKEN_GITHUB"]}'

Es lo que hace falta para un guion que trabaja con tu propia cuenta, que es el 90 % de los casos y desde luego el del proyecto de la lección 10.

Cuando actúas en nombre de otros. Ahí sí hay flujo, y el que se usa hoy es el de código de autorización:

  1. Mandas al usuario a la página del proveedor, con tu identificador de aplicación y lo que quieres poder hacer.
  2. El usuario se identifica allí y acepta.
  3. El proveedor lo devuelve a tu dirección con un código de un solo uso.
  4. Tu servidor cambia ese código por un token, usando además su clave secreta.

El paso 4 ocurre entre servidores, y por eso el código que viaja por el navegador no sirve de nada solo. Esa es toda la gracia del diseño.