Haciendo del Desarrollo y la Arquitectura Web, ciencia y pasión.

JWT sobre Codeigniter

    Hasta ahora, gran parte de la autenticación en sistemas se hacía por variables de sesión y cookies, de tal manera que se almacenaban estas en el lado del servidor creando un entorno relativamente robusto. El inconveniente es que esta arquitectura tiene un problema con la escalabilidad al tener que registrar en bbdd cada login del usuario.


Bien, entonces ¿que aporta JWT? Pues resumiendo mucho: el almacenamiento del token en el cliente. El proceso es sencillo, el usuario se loga en el servidor por medio de una pareja clave-contraseña o a traves de un proveedor de servicios externos, tipo Google, Facebook, etc. Tras esta autorización se construye un token con una serie de campos, especificamente para este usuario, hora, rol, etc. Tras la creación del token éste será empleado en cada operación que se haga al servidor.
Esta manera de autenticarse contra un servidor encaja con la arquitectura REST, haciendo que nuestro servicio web esté completamente sin información de estado: stateles. Entonces al almacenar toda la información de sesión en el cliente nuestra arquitectura se vuelve escalable sin afectar al rendimiento. Otra de las virtudes de un servicio web de este tipo es que se desacopla del cliente, así que nuestro servicio puede consumirse por igual, desde una aplicación de escritorio, un movil o una web.

Entonces, al tema. Un token que forma tiene, de que está compuesto? ¿que es eso de JWT? pues JWT responde al acrónimo Json Web Tokens, éste está dividido en tres substrings separados por un caracter punto y podria tener el siguiente aspecto:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFzZCIsInBzd2QiOiJhc2QifQ.h8siYric8C6-7dLKbtIRJYOhUNX6UwUWU31k9IY6BBc

Las tres secciones se corresponden con el Header, el Payload y la Signatura.

La primera parte o Header es la cabecera del token y contiene el typ de token y su codificacion empleada. El tipo será JWT, la codificación tipicamente sera SHA256:

{
"typ": "JWT",
"alg": "HS256
}

Posteriormente se codifica a eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

La segunda sección corresponde con el Payload donde tenemos los campos denominados JWT Claims con la información importante. Estos campos no pretenden ser obligatorios, solo dan un punto de partida para trabajar.

iss : "issuer" o emisor del token JWT.
sub: "subject" o sujeto del token, se trata de un parametro que identifica al usuario, por ejemplo el id.
aud: "Audience" o audiencia a la cual esta dirigida el token.
exp: "Expiracion" identifica el momento de la expiracion o despues del cual no debe ser aceptado el token.
nbf: "Not before", identifica el momento antes del cual no debes ser aceptado el token.
iat: "Issued At" momento de la creación del token.
jti: "JWT ID" es un identificador unico para el token.

Todo este array de parametros se codificaría en

eyJoZWFkZXIiOnsidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifSwidXNlcm5hbWUiOiJ0ZXN0IiwicHN3ZCI6InRlc3QifQ

Por último la tercera parte la Signatura recoge las dos anteriores secciones codificadas en Base64 junto a una clave secreta almacenada en nuestro Backend.

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload), secret
);

finalmente quedaría : 

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiT9.eyfoZWFkZXIiOnsidHlwIjoiSldUfIsiswsiWxnIjoiSFMyNTYifSwidXNlcm5hbWUiOiJ0ZXN0IiwicHN3ZCI6InRlc3QifQ.8P6REarWqsB06YvWg1Qakfk6ZnW191E6ZY7XA6Pl5TCvGZU

Pero vamos a ver un poco de código y la manera en como construiriamos nuestro token. Posteriormente como almacenarlo y finalmente como lanzarlo dentro de los headers para poder ser recuperado en una operación que requiera autorizacion.

El entorno elegido es Codeigniter que ha sido ampliado con una implementacion de JWT de Firebase aqui (https://github.com/firebase/php-jwt). Este helper de JWT dispone de dos metodos encode y decode, que son los que usaremos.

Para ordenar un poco el asunto crearemos un helper intermedio que nos ayudará a invocar decode y a encode 

class AUTHORIZATION
{
public static function validateToken($token)
{
$CI =& get_instance();
return JWT::decode($token, $CI->config->item('jwt_key'));
}
public static function generateToken($data)
{
$CI =& get_instance();
return JWT::encode($data, $CI->config->item('jwt_key'));
}
}

Nuestro controlador que extenderá de Rest_controller solo permitira acciones POST. Tenemos dos metodos, token_post, que construirá el token a partir del usuario y contraseña. En caso de no validar usuario y contraseña directamente devolvemos una respuesta no autorizada. Y tokenRetrieve que nos devolverá el token decodificado 

class Auth extends REST_Controller {
/**
* URL: http://local.jwt/auth/token
* Method: POST
*/
public function token_post() {
$user = $this->post('username');
$pswd = $this->post('pswd');
//verificacion de usuario y paswd
if ($this->valid_user_pswd($user,$pswd)){
$tokenData = array();
$tokenData['header'] = array("typ" => "JWT", "alg" => "HS256");
$tokenData['payload'] = array(
'sub' => $user,
'exp' => time() + (7 * 24 * 60 * 60),
'iat' => time(),
'jti' => 1
); $output['token'] = AUTHORIZATION::generateToken($tokenData);
$this->set_response($output, REST_Controller::HTTP_OK);
} else
{
$this->set_response("Unauthoised", REST_Controller::HTTP_UNAUTHORIZED);
}
}

/**
* URL: http://local.jwt/auth/tokenRetrieve
* Method: POST
* Header Key: Authorization
* Value: Auth token generated in GET call
*/
public function tokenRetrieve_post() {
$headers = $this->input->request_headers(); if (array_key_exists('Authorization', $headers) && !empty($headers['Authorization'])) {
$decodedToken = AUTHORIZATION::validateToken($headers['Authorization']);
if ($decodedToken != false) {
$this->set_response($decodedToken, REST_Controller::HTTP_OK);
return;
}
}
$this->set_response("Unauthoised", REST_Controller::HTTP_UNAUTHORIZED);
}

Nuestra vista tiene un mini formulario de login con dos botones uno de envío de credenciales y otro que nos recuperará el token, pero decodificado. En la consola del log podemos ver su contenido.

El formulario no tiene ningun misterio

 <?php echo form_open(''); ?>
<input id="username" name="username" type="text" value="">
<input id="pswd" name="pswd" type="password" value="">
<input id="submit" name="submit" type="button" value="submit">
<input id="retrieve" name="retrieve" type="button" value="retrieve"> <?php echo form_close(); ?>
<div id="container"></div>

la parte de jquery que lanzará las operaciones es como sigue:

<script>
$(function () {
$('#submit').click(function () {
var username = $('#username').val();
var pswd = $('#pswd').val();
$.ajax({
method: "POST",
url: "<?php echo base_url(); ?>auth/token",
data: {username: username, pswd: pswd},
error: function (jqXHR, textStatus, errorThrown) {
console.log("catacrocker");
$("#container").html("no existe el token o no esta autorizado");
}
})
.done(function (msg) {
console.log(msg.token);
alert("Token: " + msg.token);
tokenSuccess(msg.token)
localStorage.setItem('token',msg.token);
});
});
$('#retrieve').click(function () {
var token = localStorage.getItem("token");
$.ajax({
method: "POST",
url: "<?php echo base_url(); ?>auth/tokenretrieve",
beforeSend: function (request) {
request.setRequestHeader("Authorization", token);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log("catacrocker");
$("#container").html("no existe el token o no esta autorizado");
}
})
.done(function (msg) {
console.log(msg);
$("#container").html("El Sujeto del toke es: "+msg.payload.sub);
})

});
}); </script>

prestar atencion a los metodos que almacenan y recuperan el token almacenado localmente. De tal manera que en cada operación habría que recuperar el token localmente almacenado y enviarlo para verificar la autorización.

     localStorage.setItem('token',msg.token);
var token = localStorage.getItem("token");

Notar tambien la construcción del envio por ajax de la autorización en el header, no en el body como se acostumbra:    

beforeSend: function (request) {
              request.setRequestHeader("Authorization", token); }

 Bueno, todo este desarrollo lo tienes en https://github.com/danielgimeno/CodeIgniter-JWT-Sample.git, con toda la capa de ajax para lanzar el tema. Éste es un fork de otro repo donde está desarrollada la parte servidora aqui: https://github.com/ParitoshVaidya/CodeIgniter-JWT-Sample.git.