Concepto clave
La autenticación JWT (JSON Web Token) es un estándar abierto que define una forma compacta y autónoma de transmitir información de forma segura entre partes como un objeto JSON. Imagina que es como un pase de acceso temporal que emite un club nocturno: contiene tu información (nombre, tipo de membresía), está firmado digitalmente para evitar falsificaciones, y tiene una fecha de expiración. En microservicios, JWT permite que un servicio verifique la identidad del usuario sin necesidad de consultar constantemente una base de datos central, lo que es crucial para el alto rendimiento.
Los middlewares en Go con gorilla/mux son funciones que se ejecutan antes o después del manejador principal de una ruta. Piensa en ellos como filtros de seguridad en un aeropuerto: cada middleware verifica algo específico (como el token JWT) antes de permitir que la solicitud llegue a su destino. Esto mantiene tu código limpio y reutilizable, separando la lógica de autenticación de la lógica de negocio.
Cómo funciona en la práctica
Veamos el flujo paso a paso para implementar autenticación JWT en un microservicio REST con gorilla/mux:
- El cliente envía credenciales (usuario/contraseña) a un endpoint de login.
- El servidor valida las credenciales y, si son correctas, genera un JWT firmado con una clave secreta.
- El JWT se devuelve al cliente, quien lo almacena (normalmente en localStorage o cookies).
- Para solicitudes posteriores, el cliente incluye el JWT en el encabezado Authorization.
- Un middleware intercepta cada solicitud, verifica la firma del JWT y extrae la información del usuario.
- Si el token es válido, el middleware pasa el control al manejador de la ruta; si no, devuelve un error 401.
Este enfoque es stateless, lo que significa que el servidor no necesita almacenar sesiones, mejorando la escalabilidad.
Código en acción
Aquí tienes un ejemplo funcional de un middleware de autenticación JWT en Go. Primero, instala las dependencias necesarias: go get github.com/golang-jwt/jwt/v5 y go get github.com/gorilla/mux.
package main
import (
"fmt"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/gorilla/mux"
)
var jwtKey = []byte("mi_clave_secreta_super_segura")
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
func generateJWT(username string) (string, error) {
expirationTime := time.Now().Add(15 * time.Minute)
claims := &Claims{
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
http.Error(w, "Token no proporcionado", http.StatusUnauthorized)
return
}
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Token inválido o expirado", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
// Lógica de login simplificada
token, _ := generateJWT("usuario_ejemplo")
w.Write([]byte(token))
}).Methods("POST")
r.Handle("/protegido", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Acceso concedido a ruta protegida"))
}))).Methods("GET")
http.ListenAndServe(":8080", r)
}Ahora, mejoremos el middleware para pasar el nombre de usuario al manejador, usando el contexto de la solicitud. Esto es útil para acceder a datos del usuario en las rutas protegidas.
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
http.Error(w, "Token no proporcionado", http.StatusUnauthorized)
return
}
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Token inválido o expirado", http.StatusUnauthorized)
return
}
// Antes: no pasábamos información
// Después: agregamos el username al contexto
ctx := context.WithValue(r.Context(), "username", claims.Username)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// En el manejador protegido, puedes acceder así:
// username := r.Context().Value("username").(string)Errores comunes
- No validar la firma del JWT: Si usas una clave incorrecta o no verificas la firma, un atacante podría modificar el token. Siempre usa
jwt.ParseWithClaimscon una función de validación. - Almacenar información sensible en el JWT: El payload del JWT es base64 codificado, no encriptado. No incluyas contraseñas o datos críticos; úsalo solo para identificadores como username o userID.
- Olvidar manejar la expiración: Los tokens deben tener un tiempo de vida corto (ej., 15 minutos) para reducir riesgos. Configura
ExpiresAty maneja el errorjwt.ErrTokenExpired. - No usar HTTPS en producción: Los JWT viajan en texto claro en los encabezados. Sin HTTPS, son vulnerables a interceptación. Implementa SSL/TLS siempre.
- Crear middlewares ineficientes: Evita lógica pesada en middlewares (como consultas a BD) para no afectar el rendimiento. Mantenlos simples y rápidos.
Checklist de dominio
- Puedo generar un JWT válido con claims personalizados y fecha de expiración.
- Implementé un middleware en gorilla/mux que verifica JWT y devuelve errores HTTP apropiados.
- Sé cómo pasar datos del usuario (ej., username) del middleware a los manejadores usando context.
- Probé mi API con herramientas como curl o Postman, enviando tokens en el header Authorization.
- Entiendo la diferencia entre autenticación (JWT) y autorización (roles/permissions) y puedo extender el middleware para esto.
- Configuré tiempos de expiración cortos y manejo la renovación de tokens con refresh tokens si es necesario.
- Documenté los endpoints de login y rutas protegidas en mi microservicio.
Extiende el middleware para autorización basada en roles
En este ejercicio, mejorarás el middleware de autenticación JWT para incluir autorización básica, permitiendo el acceso a rutas solo a usuarios con roles específicos. Sigue estos pasos:
- Crea una estructura
Claimsextendida que incluya un campoRole(puede ser un string como "admin" o "user"). - Modifica la función
generateJWTpara aceptar un rol y incluirlo en los claims al generar el token. - Implementa un nuevo middleware llamado
roleMiddlewareque, después de la autenticación, verifique si el usuario tiene el rol requerido (pasado como parámetro). - Usa este middleware en una ruta protegida, por ejemplo,
/admin, que solo sea accesible para usuarios con rol "admin". - Prueba tu implementación con curl o Postman: genera un token con rol "user" e intenta acceder a
/admin, luego genera uno con rol "admin" y verifica el acceso.
Entrega el código completo de tu archivo main.go con las modificaciones.
Pistas- Recuerda agregar el campo Role en la estructura Claims y actualizar la generación del token.
- Puedes encadenar middlewares en gorilla/mux usando r.Handle().Use() o aplicándolos en secuencia.
- Para pasar el rol requerido como parámetro, define el middleware como una función que retorne http.Handler.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.