JWT — Jugando con Autenticación con Tokens
Me tocó jugar con el Simple Sing-on de Zendesk, para poder loguear usuarios de una aplicación en el sistema de tickets de soporte de zendesk.
Para implementar el logueo en un simple enlace los de Zendesk implementan algo llamado JWT (JSON Web Token) y otra cosa llamada SAML (Secure Assertion Markup Language) que no viene al caso. Así que iré tomando nota en voz alta por si le sirve a otro artesano.
Existen varias formas de identificar un usuario que están relacionadas con los principios de la seguridad de la información: algo que sé, algo que tengo, algo que soy, algo que haga. El Token es algo que tengo y que sé.
Su razón de ser tienen que ver con la escalabilidad de un servicio web, si me identifico con algo que sé, usuario y clave generalmente, el problema esta en dónde guardo el "estado" de que el usuario esta identificado cuando tengo varios servicios corriendo en servidores distintos. Así que a googlear "Autenticación sin estado con Tokens", sino se me hace muy largo el post.
¿Qué es JWT (JSON Web Token)?
Es un estándar de autenticación basado en JSON, que es agnóstico de lenguaje, y lo podemos utilizar en el sabor que más nos guste Ruby, Elixir, Node.js, Python, PHP, .NET, Java, Clojure, ClojureScript, etc. etc.
JWT es un reciente estándar abierto que está siendo liderado por el organismo internacional de estándares IETF y patrocinado por grandes empresas del sector tecnológico (como Google, Microsoft, Facebook, etc).
La especificación técnica se encuentra disponible en http://tools.ietf.org/html/draft-jones-json-web-token-10 . Y en el RFC7519 https://tools.ietf.org/html/rfc7519#section-4.1.4
Te dejo otro doc. que lo explica técnicamente: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
Y la entrada en la popular Wikipedia (ENG) https://en.wikipedia.org/wiki/JSON_Web_Token
Básicamente, se agarra una maraña de datos JSON y se los encripta para ser transmitidos como una cadena de caracteres encriptada.
¿Cómo es un token JWT?
El formato de un JWT está compuesto por 3 cadenas de caracteres separadas simplemente por un punto '.', se ven algo así como el siguiente ejemplo:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTMwODE5ODExMjM0NSIsImV4cCI6IjEzMDgxOTgxMTIzNDUiLCJuYW1lIjoiUGVwZSBBcmdlbnRvIiwiYWRtaW4iOnRydWV9.J5YqqXepOKiVrrUAMcy5TEWPRPM12OiXjRZImtJBui8
Cada parte de la cadena tiene un significado particular:
Header
- La primera parte es la cabecera del token, que a su vez tiene otras dos partes, el tipo, en este caso un JWT y la codificación utilizada. Comúnmente es el algoritmo HMAC SHA256, El contenido sin codificar es el siguiente:
{
“alg”: “HS256”,
“typ”: “JWT”
}
(en este caso el header contiene ALGORITHM & TOKEN TYPE)
Luego de codificar, es la primera parte de la cadena antes del punto:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload es otra de sus partes
Está compuesto por los llamados JWT Claims donde irán colocados la atributos que definen nuestro token. Existen varios que puedes consultar aquí, los más comunes a utilizar son:
- sub: Identifica el sujeto del token, por ejemplo un identificador de usuario.
- iat: Identifica la fecha de creación del token, válido para si queremos ponerle una fecha de caducidad. En formato de tiempo UNIX
- exp: Identifica a la fecha de expiración del token. Podemos calcularla a partir del iat. También en formato de tiempo UNIX.
{
"sub": "1234567890",
"iat": "1308198112345",
"exp": "1308198112345",
"name": "Pepe Argento",
"admin": true
}
En la cadena codificada el JWT, queda asi:
eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTMwODE5ODExMjM0NSIsImV4cCI6IjEzMDgxOTgxMTIzNDUiLCJuYW1lIjoiUGVwZSBBcmdlbnRvIiwiYWRtaW4iOnRydWV9
También podemos añadirle más campos, incluso personalizados, como pueden ser el el mail del usuario, su rol, etc.
Signature es la tercer parte
Esta firma está formada por los anteriores componentes (Header y Payload) cifrados en Base64 con una clave secreta (un secreto almacenada en nuestro servidor). Así sirve de Hash para comprobar que todo está bien.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
86bae26023208e57a5880d5ad644143c567fc57baaf5a942
)
Para este ejemplo use el hash:
86bae26023208e57a5880d5ad644143c567fc57baaf5a942
Luego de codificar nuestro Token JWT, quedará con esta forma:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTMwODE5ODExMjM0NSIsImV4cCI6IjEzMDgxOTgxMTIzNDUiLCJuYW1lIjoiUGVwZSBBcmdlbnRvIiwiYWRtaW4iOnRydWV9.J5YqqXepOKiVrrUAMcy5TEWPRPM12OiXjRZImtJBui8
Y al decodificarlo deberiamos obtener los datos que hemos usado para codificarlo.
Sino aguantas la ansiedad, podes crear tus token de prueba en este sitio web: https://jwt.io/ que es el que usé para crear los ejemplos.
Examples:
Algunos ejemplos de implementación en los repositorios de JWT.io
En Clojure:
http://funcool.github.io/buddy-core/latest/
lein: [funcool/buddy “0.6.0”]
https://github.com/liquidz/clj-jwtLien: [clj-jwt “0.1.1”]
En ClojureScript:
ClojureScript JWT encode/decode (SHA version only) — gist
https://gist.github.com/aputs/b6e255e79ef99bf90ff80a1177044a01
En Ruby.
gem install jwt
gem install json-jwt
gem install json_web_token
SSO (single sign-on) en Zendesk
lo que me lleva al principio de mi problema, como usar JWT con Zendesk para hacer un inicio de sesión único.
Te dejo la info. oficial de zendesk en español.
Que dice que el payload, tiene que tener: iat (a que hora fue emitido), jti (id única para el token), email (del usuario), name (nombre del usuario)
{
"iat": "1308198112345",
"jti": "1308198112345",
"name": "Pepe Argento",
"email": "pepe@argento.com"}
Y que luego tienen que usarlo, armando una URL:
https://micompañía.zendesk.com/access/jwt?jwt=payload
El secret, lo obtienes de la configuración de seguridad de la cuenta de zendesk.
Y basándome en los ejemplos oficiales de zendesk:
Hice mi prueba de concepto con Ruby on Rails para Zendesk el SSO.
Ahora a laburar en la prueba de concepto en Clojure…