Cómo autenticar JWT con PHP de forma segura

Un seguidor de Twitter me envía esta pregunta:

¿Cuál esería una forma segura de autenticar el jwt que llega en la petición?

Ante todo gracias Ezequiel y perdona la tardanza en responder.

Vamos por partes para que se entienda bien de qué estamos hablando.

Qué es JWT

JWT son las siglas de JSON Web Tokens. Se trata de una tecnología desarrollada por la empresa Auth0 cuyo objetivo es la transmisión segura de información a través de una red insegura (Internet).

Como en la mayoría de los mecanismos de seguridad, se trata del envío de información codificada utilizando métodos muy difíciles de descifrar para quien no tenga la información completa pero muy simples para quien sí la posea.

Desde afuera un token JWT es una cadena de caracteres aleatoria, por ejemplo:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

En realidad, si la miras con algo más de detenimiento notarás que se trata de una cadena particular formada por tres cadenas unidas por un .

Cuáles son los componentes de un token JWT

Un token JWT está conformado por:

  1. Un encabezado
  2. La carga útil o payload (El mensaje que se busca transmitir)
  3. Una firma

El encabezado indica el algoritmo que se utilizará para realizar la codificación y la posterior verificación.

El payload es una cadena que representa un objeto JSON.

Por último, la firma se utilizará para darle confiabilidad a la transmisión.

Dependiendo del algoritmo utilizado esta cadena puede ser una frase cualquiera o un certificado digital.

Para qué se usa JWT

Existen muchos casos en los que vale la pena utilizar un token JWT. Uno de los usos más comunes es cuando se busca garantizar la identidad de una entidad que requiere hacer uso de un Servicio Web de nuestra propiedad.

En general, este mecanismo se adapta mejor que el uso de sesiones estándar.

Más aún, si la comunicación se realiza entre más de dos actores, JWT ofrece un soporte difícil de lograr con las sesiones.

Una ventaja clara de JWT contra otros tipos de autenticación es el hecho de que los tokens pueden ser verificados sin necesidad de acceder a información externa (Bases de datos por ejemplo).

Un punto muy importante a tener en cuenta cuando se usan tokens JWT es que la información transportada no se encuentra encriptada. Sólo la firma lo está.

Esto permite garantizar que la información transportada no ha sido alterada durante la transmisión, pero no puede garantizar que un agente malicioso la haya leído.

Cómo leer un token JWT usando PHP

En el caso de la pregunta que realiza Ezequiel vale el supuesto de que el token ha sido generado por algún tercero del cual se pretende validar su autenticidad.

Para ello existen, además de la posibilidad de descifrar el token manualmente, una serie de librerías que se detallan en el sitio de Auth0. Dado que se trata de un estándar abierto cualquiera puede crear su propia implementación.

Tomaré como base de este ejemplo este artículo publicado en el propio sitio de Auth0.

El primer paso para trabajar con un token JWT es verificar su autenticidad.

Cómo verificar la autenticidad de un token JWT usando PHP

Para verificar la autenticidad del token se requiere calcular la firma y validarla contra la recibida.

Si coinciden se puede asumir que el mensaje no ha sido alterado por terceros, en caso contrario el token debe ser rechazado.

Podemos realizar esta verificación utilizando este código:

<?php
require 'bootstrap.php';
use Carbon\Carbon;

// get the local secret key
$secret = getenv('SECRET');

if (! isset($argv[1])) {
    exit('Please provide a key to verify');
}

$jwt = $argv[1];

// split the token
$tokenParts = explode('.', $jwt);
$header = base64_decode($tokenParts[0]);
$payload = base64_decode($tokenParts[1]);
$signatureProvided = $tokenParts[2];

// check the expiration time - note this will cause an error if there is no 'exp' claim in the token
$expiration = Carbon::createFromTimestamp(json_decode($payload)->exp);
$tokenExpired = (Carbon::now()->diffInSeconds($expiration, false) < 0);

// build a signature based on the header and payload using the secret
$base64UrlHeader = base64UrlEncode($header);
$base64UrlPayload = base64UrlEncode($payload);
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
$base64UrlSignature = base64UrlEncode($signature);

// verify it matches the signature provided in the token
$signatureValid = ($base64UrlSignature === $signatureProvided);

echo "Header:\n" . $header . "\n";
echo "Payload:\n" . $payload . "\n";

if ($tokenExpired) {
    echo "Token has expired.\n";
} else {
    echo "Token has not expired yet.\n";
}

if ($signatureValid) {
    echo "The signature is valid.\n";
} else {
    echo "The signature is NOT valid\n";
}

Para que este código pueda ejecutarse será necesario crear un archivo composer.json con este contenido:

{
  "require": {
    "vlucas/phpdotenv": "^2.4",
    "nesbot/carbon": ">=2.61.0"
  },
  "autoload": {
    "psr-4": {
      "Src\\": "src/"
    }
  }
}

Y ejecutar el comando composer install para instalar todas las dependencias usando composer.

Por último, es necesario definir la clave secreta en un archivo .env por ejemplo:

SECRET=Th1s1sMyS3cr3t!

De esta forma será posible validar cualquier token que haya sido generado utilizando esta clave secreta.

Puedes probar esto entrando en la página de JWT e ingresando estos datos:

Con eso puedes copiar el texto en el cuadro «Encoded» y ejecutar el script de validación de esta forma:

php validate_jwt.php eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.xSRMoZpDHCAREK9x0ELx5yL8Y0qkpPt3eVrK9ItiyUo

Lo cual te dará un resultado como este:

Header:
{"alg":"HS256","typ":"JWT"}
Payload:
{"sub":"1234567890","name":"John Doe","exp":1516239022}
Token has expired.
The signature is valid.

Con lo cual, se ha confirmado la autenticidad del token recibido.

Algunas librerías JWT para PHP

Esta verificación se realizó analizando manualmente el token y, si bien esto es perfectamente posible, no es lo más conveniente en un ambiente de producción (Principalmente por motivos de mantenibilidad a largo plazo).

Algo más recomendable es la utilización de alguna librería.

Una bastante simple es la desarrollada por Firebase.

Una algo más compleja pero muy interesante para usos avanzados es la implementación de Spomky.

https://www.techiediaries.com/php-jwt-authentication-tutorial/

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a acceder mercados y clientes más sofisticados y exigentes

2 comentarios

  1. Una duda importante. Si los datos que se envian no están encriptados y se cualquiera los puede ver, de que sirve??

    gracias.

    1. Son diferentes problemas:

      ¿Cómo sé que la información que me llega fue realmente enviada por quien dice ser el emisor?
      ¿Cómo sé que la información que recibí es realmente la que el emisor quería enviar? (Es decir, cómo sé que no fue alterada en el camino)
      ¿Cómo evito que, si alguien intercepta el mensaje en el camino, pueda ver su contenido?

      JWT soluciona los problemas 1 y 2. Para resolver el 3 necesitás, encima de JWT, usar un mecanismo de encipción de la información.

      Saludos,

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.