Tema 2, parte II

Autentificación en servicios web REST

Indice

  1. HTTP Basic
  2. Cookies
  3. 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

  1. Cuando se hace login correctamente el servidor nos devuelve un token (valor idealmente único e imposible de falsear)
  2. 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:
    1. 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)
    2. Payload: lo que queremos almacenar en el token en formato JSON (p.ej. {"login"=>"adi"}) y codificado en Base64URL
    3. Firma: se aplica un algoritmo de hash sobre la cabecera, el payload y una clave secreta y se pasa a Base64URL
    4. 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:
    • 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
      
      Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
      

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

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)
    
    require 'Time'
    require 'jwt'
    
    token = JWT.encode({"login"=>"pepe", "exp"=>Time.now.to_i+60*60*24},'secret')
    
  • De paso solucionamos el problema de que el mismo payload siempre genera el mismo JWT si no cambiamos el secret

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