Aunque HTTP es originalmente un protocolo sin estado, en la mayoría de aplicaciones web del mundo real existe el concepto de “sesión”
Sesión: conjunto de datos que “se recuerdan” mientras vamos navegando entre páginas (usuario autentificado, carro de la compra, …)
Cookies
Extensión al protocolo HTTP que permite al cliente almacenar datos “persistentes” entre ciclos petición/respuesta
Son pares clave=valor
Típicamente se crean a petición del servidor
Las almacena el cliente pero las envía al servidor en cada petición
Mantenimiento de sesiones con cookies
Casi todos los frameworks de programación del servidor pueden generar automáticamente cookies pseudoaleatorias bastante largas para ser usadas como “id de sesión”
Esto permite almacenar datos en el servidor exclusivos de cada usuario. El “id de sesión” sirve como “clave” para recuperar los datos
API de sesiones
En casi todos los frameworks web las cookies de sesión son transparentes al desarrollador. Se nos da un API mediante el que podemos almacenar/recuperar objetos en la “sesión”, una especie de BD privada de cada usuario, guardada en el servidor
Sesiones en Express
var express = require('express');
var session = require('express-session');
var app = express();
app.use(session({secret:'123456'}));
//Ejemplo de uso de sesiones. No RESTful
app.post('/addProducto', function(pet, resp) {
var obj = pet.query;
if (!pet.session.prods)
pet.session.prods = [];
pet.session.prods.push(obj);
console.log(pet.session.prods);
resp.send("Añadido");
})
Autentificación con sesiones
Tras hacer login correctamente, guardamos en la sesión un dato indicando que el cliente se ha autentificado OK. Si no está en la sesión, no se ha autentificado
Express hace sencillo modularizar el chequeo de autenticación
function checkAuth(pet, resp, next) {
if (pet.session.usuarioActual)
next();
else {
resp.status(401);
resp.send("Debes autentificarte");
}
}
app.get('/restringido2', checkAuth, function(pet, resp) {
resp.send("Si estás viendo esto es que eres importante!!!");
});
A favor de las sesiones
Las sesiones basadas en cookies vienen ya implementadas en la mayoría de plataformas de desarrollo web en el servidor
Si alguien intercepta la comunicación, es mucho más sencillo invalidar la sesión que obligar al usuario a cambiar el password
En contra de las sesiones
En teoría un API REST no debería guardar nada de estado entre peticiones
Autentificación con HTTP Basic
HTTP Basic
Mecanismo estándar de autentificación en HTTP
Como HTTP no tiene estado, hay que enviar las credenciales en cada petición
Se envía login y password en Base64 (=¡sin cifrar!) dentro de la cabecera Authorization
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
HTTP Basic (2)
Si se intenta acceder a un recurso protegido sin cabecera Authorization, el servidor responde con un status 401 y una cabecera WWW-Authenticate
401 UNAUTHORIZED HTTP/1.1
...
WWW-Authenticate: Basic realm="nombre del realm"
Cuando el navegador recibe un 401 + cabecera WWW-Authenticate hace que “salte” el típico cuadro de diálogo de login
A favor de HTTP Basic
Estándar HTTP, funciona out-of-the-box
Es RESTful, no obliga a mantener estado en el servidor
En contra de HTTP Basic
Login y password se transmiten sin cifrar. Por tanto hay que usar HTTPs. Una mejora es HTTP Digest, que hace un hash MD5 de los datos.
Un fallo de seguridad implica que hay que cambiar el password
Autentificación con tokens
Tokens
Cuando se hace login correctamente el servidor nos devuelve un token (valor idealmente único e imposible de falsear)
A partir de este momento para cualquier operación restringida debemos enviar el token en la petición
Similar a HTTP Basic pero se está enviando un dato no tan crítico
JSON Web Token (JWT)
Estándar IETF. Hay implementación en multitud de lenguajes.
Es una cadena formada por 3 partes:
Cabecera: indica el tipo de token y el algoritmo de firma. Se codifica en Base64. Ejemplo: {"typ"=>"JWT", "alg"=>"HS256"} (indica que esto es un “JWT” y se firmará con HMAC SHA–256)
Payload: lo que queremos almacenar en el token en formato JSON (p.ej. {"login"=>"adi"}) y codificado en Base64URL
Firma: se aplica un algoritmo de hash sobre la cabecera, el payload y una clave secreta y se pasa a Base64URL
Se vuelve a aplicar el hash sobre la cabecera y el payload (más la clave secreta). Si no coincide con la firma, el token no es válido.
En teoría no se puede generar un token falso si no se conoce la clave secreta, y esta no se puede averiguar a partir de un token auténtico
Cuidado, todo se transmite “en claro”: Base64 es una codificación, no un cifrado. Por tanto normalmente habrá que usar HTTPS si no se quiere que el payload sea legible
Fecha de expiración
En el payload se suele incluir una fecha de expiración del token. En el estándar se especifica el uso de exp (con el nº segundos desde el 1/1/1970)
De paso solucionamos el problema de que el mismo payload siempre genera el mismo JWT si no cambiamos el secret
Ejemplo en Node.js
var jwt = require('jwt-simple');
var moment = require('moment');
var payload = {
login: 'pepito',
exp: moment().add(7, 'days').valueOf()
}
var secret='123456';
var token = jwt.encode(payload, secret);
console.log(token);
var decoded = jwt.decode(token, secret);
console.log(decoded);
¿Dónde viaja el JWT?
Del servidor al cliente, al ser generado: no hay un estándar. Por ejemplo, en el cuerpo de la respuesta
Del cliente al servidor para autentificarse:
En HTTP para enviar credenciales se usa la cabecera Authorization
Por semejanza con OAuth, que emplea el concepto de “bearer token”, se usa el formato
Se pueden usar también en aplicaciones nativas (p.ej. móviles)
El dominio del servicio de autenticación puede ser distinto al del API
OAuth
¿Qué es OAuth?
Protocolo de autentificación usado cuando nuestra aplicación quiere acceder al API de un tercero pero el usuario no está dispuesto a confiarnos su password
Solución: el usuario se autentifica directamente con el API del tercero y este nos cede un token, válido durante un tiempo. Si hay problemas es mucho más sencillo anular el token que cambiar el password
Más sobre OAuth
La versión actual es la 2, una simplificación de la original
El protocolo solo especifica el comportamiento general, las implementaciones se ocupan de los detalles