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:
- 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.
- 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.