Tema 2, parte II
Autentificación en servicios web REST
Indice
- HTTP Basic
- Cookies
- Tokens
2b.1 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"
A favor y en contra de HTTP Basic
- A favor:
- Estándar HTTP, funciona out-of-the-box
- Es RESTful, no obliga a mantener estado en el servidor
- En contra:
- 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.
- Estamos enviando datos cuya privacidad es crítica en cada petición
2b.2 Autentificación basada en cookies
Cookies
- Aunque HTTP es originalmente un protocolo sin estado, en la mayoría de aplicaciones web del MundoReal™ existe el concepto de "sesión"
- Se recuerdan ciertos datos mientras vamos navegando entre páginas (usuario autentificado, carro de la compra, ...)
- Cookies: extensión al protocolo HTTP que permite a cliente y servidor almacenar datos "persistentes" entre ciclos petición/respuesta
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 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"
HttpSession session = request.getSession();
Usuario u = new Usuario("login", "password")
session.setAttribute("miUsuario", u);
...
Usuario u = session.getAttribute("miUsuario");
Ejemplo Java
use Rack::Session::Pool, :expire_after => 60*60
...
u = Usuario.new('login', 'password')
session[:miUsuario] = u
...
Ejemplo Sinatra
Autentificación con sesiones
Tras hacer login correctamente, guardamos en la sesión un flag indicando que el cliente se ha autentificado OK
require 'rack'
require 'sinatra'
use Rack::Session::Pool, :expire_after => 60*60
#aqui llegariamos con login?nombre=pepe&password=pepe
get '/login' do
if (params[:nombre] ... #aquí habría que comprobar que login y password son correctos
session[:usuario] = params[:nombre]
end
get '/ver_recurso_protegido' do
if (session[:usuario].nil?)
status 403
else
"hola #{session[:usuario]}"
end
end
get '/logout' do
session.clear
"adios"
end
Cookies vs. HTTP Basic
A favor de las cookies
- 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
2b.3. Autentificación basada en 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
Como se ve, es similar a la idea de cookies como identificadores de sesión, solo que implementada por nosotros en lugar del navegador
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
- Las tres partes se concatenan con '.'
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJob2xhIjoibXVuZG8ifQ.pJPDprjxsouVfaaXau-Fyspj6rpKc7_hCui1RSaERAE
Comprobar un JWT
- 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
¿Dónde viaja el JWT?
- Generado por el servidor: no hay un estándar. Por ejemplo, en el cuerpo de la respuesta
- Del cliente al servidor para autentificarse:
Ejemplo en Ruby
> require 'jwt'
> token = JWT.encode({"login"=>"pepe"},'secret')
> JWT.decode(token, 'secret')
=> [{"login"=>"pepe"}, {"typ"=>"JWT", "alg"=>"HS256"}] #devuelve payload y cabecera
> token = token + "a" #manipulamos el token y...
> JWT.decode(token, 'secret')
JWT::DecodeError: Signature verification failed #falla la comprobación
from /Library/Ruby/Gems/2.0.0/gems/jwt-1.0.0/lib/jwt.rb:119:in `verify_signature'
...
Ejemplo interactivo con 'irb'. Para instalar 'jwt': sudo gem install jwt
Cookies vs. Tokens
- Ventajas de las cookies
- Las sesiones estándar de PHP, JavaEE, .NET, Rails, etc. usan cookies por defecto y ya nos lo dan todo implementado. Los tokens hay que gestionarlos "manualmente".
- Ventajas de los tokens
- 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