Introducción a JWT

JWT (JSON Web Token) es un método conciso y autónomo para la autenticación en la web. Consta de tres partes: el encabezado, la carga útil y la firma. El encabezado contiene el tipo de token y la información del algoritmo de cifrado, la carga útil contiene los datos a pasar (generalmente incluyendo información de permisos e identificación de usuario), y la firma se utiliza para verificar la integridad y validez del token.

JWT se utiliza principalmente en dos escenarios:

  1. Autenticación: Una vez que un usuario inicia sesión, cada solicitud posterior incluirá un JWT, lo que permite al usuario acceder a rutas, servicios y recursos permitidos.
  2. Intercambio de información: JWT es un buen método para transmitir información de forma segura entre partes, ya que pueden ser firmados digitalmente, por ejemplo, utilizando pares de claves pública/privada.

Formato de los Datos de JWT

JWT es una forma compacta y segura de representar información a intercambiar entre partes. Un JWT en realidad consta de tres partes, separadas por puntos (.``), a saber Encabezado.Carga_útil.Firma`. A continuación, detallaremos los formatos de datos de estas tres partes.

1. Encabezado

El encabezado consta típicamente de dos partes: el tipo de token, generalmente JWT, y el algoritmo de firma o cifrado usado, como HMAC SHA256 o RSA. El encabezado está representado en JSON y luego codificado en una cadena usando Base64Url. Por ejemplo:

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

Después de la codificación, se puede obtener una cadena similar a esta:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Carga_útil

La carga útil consiste en una serie de reclamaciones, que son información sobre la entidad (generalmente un usuario) y otros datos. La carga útil puede incluir múltiples reclamaciones predefinidas (también conocidas como Reclamaciones Registradas) y reclamaciones personalizadas (Reclamaciones Privadas).

Las reclamaciones predefinidas pueden incluir:

  • iss (Emisor): Emisor
  • exp (Tiempo de Expiración): Tiempo de expiración
  • sub (Sujeto): Sujeto
  • aud (Audiencia): Audiencia
  • iat (Tiempo de Emisión): Tiempo de emisión
  • nbf (No Antes): Tiempo efectivo
  • jti (ID de JWT): Identidad única del JWT

Un ejemplo de carga útil podría ser así (en formato JSON):

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

Esta información también se codificará con Base64Url, y se puede obtener una cadena similar a esta:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

3. Firma

La sección de firma se utiliza para firmar las dos cadenas codificadas mencionadas anteriormente, a fin de verificar que el mensaje no ha sido manipulado durante la transmisión. En primer lugar, es necesario especificar una clave (si se utiliza el algoritmo HMAC SHA256), y luego utilizar el algoritmo especificado en el encabezado para firmar el encabezado y la carga útil.

Por ejemplo, si tienes el siguiente encabezado y carga útil:

EncabezadoCodificado.Carga_útilCodificada

Un seudocódigo para firmarlos usando una clave podría ser:

HMACSHA256(base64UrlEncode(encabezado) + "." + base64UrlEncode(carga_útil), claveSecreta)

La cadena firmada resultante podría ser:

dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

JWT Completo

Combinando estas tres partes con un punto (.) como separador forma el JWT completo:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

El estándar JWT es altamente flexible y puede almacenar cualquier información que necesites en la carga útil (pero con el propósito de reducir el tamaño de JWT y por razones de seguridad, no se deben almacenar información sensible), y asegura la integridad de esta información a través de la firma.

Instalación de la biblioteca JWT de Go

La comunidad de Go proporciona un paquete github.com/golang-jwt/jwt para manejar JWT. La instalación de esta biblioteca es muy sencilla, simplemente ejecuta el siguiente comando en el directorio de tu proyecto:

go get -u github.com/golang-jwt/jwt/v5

Una vez instalado, puedes incluirlo en tu import de la siguiente manera:

import "github.com/golang-jwt/jwt/v5"

Creación de un token simple

Para crear un token JWT simple usando el lenguaje Go y el algoritmo HS256, debes seguir estos pasos:

Primero, genera un nuevo objeto Token, utilizando el algoritmo HS256:

token := jwt.New(jwt.SigningMethodHS256)

Luego, utiliza el método SignedString para generar una representación de cadena de este token, pasando la clave que usarás para firmarlo.

var mySigningKey = []byte("tu-secreto-de-256-bits")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("Ocurrió un error: %v", err)
}
fmt.Println(strToken)

Esto generará un token simple sin reclamaciones.

Creación de un token con parámetros

Una de las funciones principales de JWT es llevar información. Esta información está codificada en el Token a través de reclamaciones. Por ejemplo, creemos reclamaciones personalizadas:

// Estructura de reclamaciones personalizadas
type MyClaims struct {
	jwt.RegisteredClaims
	Username string `json:"username"`
	Admin    bool   `json:"admin"`
}

// Crea un token con reclamaciones personalizadas
claims := MyClaims{
    RegisteredClaims: jwt.RegisteredClaims{},
    Username: "mi_nombre_de_usuario",
	Admin: true,
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

// Clave secreta para firmar
var mySigningKey = []byte("tu-secreto-de-256-bits")

// Genera el token en formato de cadena
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("Ocurrió un error: %v", err)
}

fmt.Println(strToken)

En el código anterior, hemos definido la estructura MyClaims para contener reclamaciones registradas, así como alguna información personalizada. Luego, seguimos utilizando SignedString para generar la cadena de token.

Análisis y validación del token

Analizar y validar el JWT es muy importante, y puedes hacerlo de la siguiente manera:

// Utilizaremos la misma estructura MyClaims
tokenString := "tu-cadena-JWT"

// Necesitamos definir una función que el paquete jwt utilizará para analizar el tokenString
keyFunc := func(t *jwt.Token) (interface{}, error) {
	// Verifica que se esté utilizando el método de firma esperado
	if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
	    return nil, fmt.Errorf("Método de firma inesperado: %v", t.Header["alg"])
	}
	// Devuelve la clave secreta para el token jwt, en el formato []byte, consistente con la clave utilizada para firmar anteriormente
	return mySigningKey, nil
}

// Analizando el token
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
	log.Fatalf("Error de análisis: %v", err)
}

if !parsedToken.Valid {
    log.Fatalf("Token no válido")
}

// En esta etapa, parsedToken ha sido verificado, y podemos leer las reclamaciones
fmt.Printf("Usuario: %s, Admin: %v\n", claims.Username, claims.Admin)

En el código anterior, proporcionamos la cadena de token, la instancia MyClaims y la función de clave keyFunc a la función jwt.ParseWithClaims. Esta función validará la firma y analizará el token, llenando la variable claims si el token es válido.