Más sobre JWT

En el artículo anterior aprendimos qué es JWT, y su uso básico, lo que nos resultará suficiente para crear middlewares de autenticación en la gran mayoría de los casos.

No obstante, esta técnica tiene otros recursos y recovecos que debemos conocer antes de lanzarnos a implementarla en un proyecto real. Por ejemplo, métodos de los que aún no hemos hablado, o control de excepciones que puedan producirse.

En este artículo vamos a conocer todo aquello que necesitemos para poder emplear JWT en cualquier proyecto. Así podremos ofrecer a los usuarios de nuestra aplicación todo el nivel de seguridad que JWT puede darnos.

Continuaremos empleando la librería que usamos en el artículo anterior, y seguiremos a partir del punto en que lo dejamos, por lo que, si este es tu primer contacto con JWT, te aconsejo que leas antes el primer artículo. Si no, no entenderás este.

OTROS MÉTODOS DE JWT

La clase JWT cuenta con otros métodos que, si bien no son estrictamente imprescindibles para la creación y manipulación del token de seguridad para autenticación, si pueden ayudarnos en algunos momentos concretos. Realmente no son métodos específicos de la gestión de tokens, sino algunas mejoras sobre funciones propias de PHP. Estos son usados internamente por la clase, por lo que no debemos preocuparnos de ellos. Sin embargo, hay dos métodos públicos que sí nos interesa conocer.

CODIFICACIÓN EN BASE 64

Los primeros que puede interesarnos conocer son JWT::urlsafeB64Encode() y JWT::urlsafeB64Decode(). Estos son una adaptación de las funciones de PHP base64_encode()base64_decode(), modificadas de tal modo que se puedan codificar en base 64 cadenas que representen a una URL, pero sin que se incluyan los signos = que las funciones originales de PHP incluyen por defecto. Cuando se codifica una URL en base64, y PHP incluye un signo =, este es interpretado por el navegador como que estamos pasando valores por la URL (método GET). Esto puede dar lugar a malentendidos por parte del intérprete. Sin embargo, suprimir los signos = tiene el problema de que, si la URL lleva, realmente, valores pasados por GET, al descodificar la URL los habríamos perdido. Con los métodos proporcionados por la clase JWT, al descodificar se recupera la URL original integramente, con todos los valores pasados por la query. Suponte que tienes algo como lo siguiente:

$E64 = JWT::urlsafeB64Encode("index.php?a=69");
var_dump($E64);

En tu navegador obtendrás:

string(19) "aW5kZXgucGhwP2E9Njk"

Ahora vamos a descodificar esta cadena con el método que JWT nos ofrece:

var_dump(JWT::urlsafeB64Decode($E64));

En tu navegador obtendrás:

string(14) "index.php?a=69"

Cómo ves, hemos evitado los signos = a la hora de tener un dato codificado que transmitir, pero, una vez consumido, lo podemos recuperar tal y como era en origen.

EXCEPCIONES

Este es un tema realmente importante cuando trabajamos con la clase JWT. Las excepciones que se pueden producir a la hora de crear o verificar un token vienen dadas por la naturaleza inherente a un sistema de autenticación. Para que nos entendamos. Las excepciones que pueden saltar, y que nos interesa capturar, son las siguientes:

  • Al crear el token:
    • Establecer un algoritmo de codificación que no esté soportado por nuestro sistema (por ejemplo, un algoritmo de OpenSSL, si no contamos con esta capa de seguridad).
    • Establecer un algoritmo de codificación que no esté contemplado por la clase.
  • Al descifrar el token:
    • Que no exista el token. Esto puede ocurrir si, por ejemplo, no ha habido autenticación del usuario, por no existir este.
    • Que el algoritmo de descodificación no coincida con el que se usó para la codificación.
    • Que la clave privada empleada para la descodificación no coincida con la empleada en la codificación.
    • Que el token haya expirado.

Controlar estas excepciones es importante, para evitar que se lance un Fatal Error, y se nos aborte la ejecución de nuestro script.

La mejor manera de controlar las excepciones es utilizar un bloque try...catch. Ten en cuenta, que en lo que se refiere a autenticación, las excepciones se van a producir, sí o sí, antes o después. Es normal, ya que siempre puede darse alguno de los casos que desencadenan estas excepciones.

AL CREAR UN TOKEN

La mejor manera de crear un token es la siguiente:

De este modo, si se produce una excepción, la variable del token se creará igualmente, pero con el valor null, lo que nos permitirá detectar que el usuario no está autenticado.

Si lo que queremos es identificar la causa de la excepción, podemos hacerlo así:

Las posibles excepciones durante la codificación vendrán dadas, como hemos dicho, por el empleo de un algoritmo inadecuado. Por ejemplo, si tratamos de emplear el algoritmo RS256 en una plataforma sin OpenSSL, obtendremos la excepción OpenSSL unable to sign data. Si tratamos de usar un algoritmo no contemplado por la clase (digamos, XS309, por poner un ejemplo), obtendremos la excepción Algorithm not supported.

Observa, en la línea resaltada, que hemos precedido el uso de JWT::encode() con el signo @. Esto es porque, si se produce una excepción, aunque esta sea capturada, PHP nos lanza un mensaje de tipo Warning que, aunque puede ser interesante durante el desarrollo, desde luego estamos seguros de que no lo queremos ver en producción. Por supuesto, el server de producción tendrá, suponemos, desactivada la salida de estos mensajes, pero por si acaso.

Otra posible causa de errores es que los parámetros relativos a los datos a codificar en el token, o la clave privada, sean una cadena vacía, o un valor null. Esto ya es más delicado, porque no se lanza una excepción, y se obtiene un token. Sin embargo, el problema vendrá luego, al tratar de usar los datos que descodifiquemos. Una forma de solucionarlo es hacer algún tipo de comprobación previa y, si alguno de los datos no nos cuadra, lanzar una excepción específica, así:

AL DESCODIFICAR EL TOKEN

Aquí es donde más excepciones pueden producirse. vamos a empezar viendo que ocurre cuando el token es un valor null o una cadena vacía. Al igual que antes, la descodificación la encerraremos en un bloque try...catch, así:

En el caso de que el token no tenga contenido que pueda ser descifrado, se obtendrá la excepción Wrong number of segments.

Ahora vamos a suponer que empleamos un algoritmo de descodificación que no coincide con el que se empleó para la codificación. Vamos a crear nuestro token con el algoritmo por defecto (HS256), y a tratar de descodificarlo con, digamos, HS384. La excepción obtenida es Algorithm not allowed.

Otra posible cause de problemas es que la clave privada que empleamos para la descodificación no coincida con la que se empleó en la codificación. La excepción obtenida es Signature verification failed.

Una de las excepciones más habituales que se producen es que el periodo de vida del token haya expirado. En ese caso la excepción obtenida es Expired token.

CONCLUYENDO

Con esto ya tenemos lo que necesitamos para poder emplear la clase JWT y construir los middlewares adecuados para controlar, en nuestra aplicación, el permiso de usuarios autenticados para acceder o no a determinadas zonas o contenidos específicos.

   

Deja un comentario