Documentation
¶
Overview ¶
Package middleware provee middlewares HTTP reutilizables para el ecosistema GoKit: autenticación JWT, autorización por rol, verificación de sesión activa, limitación de tasa (rate limiting), CORS y logging de peticiones.
Todos los middlewares siguen la firma estándar de Go (Middleware = func(http.Handler) http.Handler), por lo que son compatibles con net/http y con routers populares como chi, gorilla/mux o Gin (vía adaptador).
Ejemplo combinando varios middlewares con Chain:
handler := middleware.Chain(mux,
middleware.RequestLogger(log),
middleware.CORS(corsConfig),
middleware.RateLimit(limiter, nil),
)
http.ListenAndServe(":8080", handler)
Para proteger una ruta específica con autenticación y roles:
authed := middleware.RequireAuth(jwtManager)
admin := middleware.RequireRole("admin")
mux.Handle("/admin", authed(admin(adminHandler)))
Index ¶
- Variables
- func Chain(h http.Handler, mws ...Middleware) http.Handler
- func ClaimsFromContext(ctx context.Context) (*token.Claims, bool)
- func FiberAuth() fiber.Handler
- func FiberLogger() fiber.Handler
- func FiberRecovery() fiber.Handler
- func GinAuth() gin.HandlerFunc
- func GinLogger() gin.HandlerFunc
- func GinRecovery() gin.HandlerFunc
- func RegisterFiberRoutes(group fiber.Router, routes []Route[fiber.Handler], opts ...RegisterOption)
- func RegisterGinRoutes(group *gin.RouterGroup, routes []Route[gin.HandlerFunc], ...)
- func RemoteIPKeyFunc(r *http.Request) string
- type CORSConfig
- type KeyFunc
- type Logger
- type MemoryRateLimiter
- type Middleware
- func CORS(cfg CORSConfig) Middleware
- func RateLimit(limiter RateLimiter, keyFunc KeyFunc) Middleware
- func RequestLogger(log Logger) Middleware
- func RequireActiveSession(sessions *token.SessionManager) Middleware
- func RequireAuth(manager *token.JWTManager) Middleware
- func RequireRole(roles ...string) Middleware
- type RateLimiter
- type RegisterOption
- type RegisterOptions
- type Route
Constants ¶
This section is empty.
Variables ¶
var ( // ErrMissingAuthHeader se retorna cuando la petición no incluye el // header Authorization. ErrMissingAuthHeader = errors.New("falta el header Authorization") // ErrInvalidAuthHeader se retorna cuando el header Authorization no // tiene el formato esperado "Bearer <token>". ErrInvalidAuthHeader = errors.New("el header Authorization debe tener el formato 'Bearer <token>'") )
Functions ¶
func Chain ¶
func Chain(h http.Handler, mws ...Middleware) http.Handler
Chain compone varios middlewares en un único http.Handler. Se aplican en el orden en que se pasan: el primero de la lista es el más externo (se ejecuta primero en la petición, último en la respuesta).
Ejemplo de uso:
handler := middleware.Chain(finalHandler,
middleware.RequestLogger(log), // se ejecuta primero
middleware.CORS(corsConfig),
middleware.RequireAuth(jwtManager), // se ejecuta justo antes del handler
)
func ClaimsFromContext ¶
ClaimsFromContext extrae los claims del JWT validado por RequireAuth desde el contexto de la petición. El segundo valor devuelto es false si RequireAuth no se ejecutó antes en la cadena de middlewares (por ejemplo, si se llama desde una ruta pública).
Ejemplo de uso dentro de un handler protegido por RequireAuth:
func perfilHandler(w http.ResponseWriter, r *http.Request) {
claims, ok := middleware.ClaimsFromContext(r.Context())
if !ok {
http.Error(w, "no autorizado", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Hola, %s", claims.Username)
}
func FiberLogger ¶
FiberLogger registra peticiones usando el Logger Global de GoKit
func GinAuth ¶
func GinAuth() gin.HandlerFunc
GinAuth es el middleware de autenticación nativo para Gin
func GinLogger ¶
func GinLogger() gin.HandlerFunc
GinLogger registra peticiones usando el Logger Global de GoKit
func GinRecovery ¶
func GinRecovery() gin.HandlerFunc
GinRecovery captura panics en los handlers de Gin
func RegisterFiberRoutes ¶
RegisterFiberRoutes registra un conjunto de rutas en un fiber.Router y deja constancia de cada endpoint en el logger global de GoKit.
Si se pasa WithAuthManager, las rutas con Protected: true reciben automáticamente el middleware de autenticación (validado contra ese manager) antes de llegar al handler. Sin WithAuthManager, Protected solo aparece en el log.
Ejemplo:
authGroup := r.Group("/auth")
middleware.RegisterFiberRoutes(authGroup, []middleware.Route[fiber.Handler]{
{Method: "POST", Path: "/signup", Handler: handler.SignUp, Protected: false},
{Method: "POST", Path: "/signin", Handler: handler.SignIn, Protected: false},
}, middleware.WithGroupName("auth"))
func RegisterGinRoutes ¶
func RegisterGinRoutes(group *gin.RouterGroup, routes []Route[gin.HandlerFunc], opts ...RegisterOption)
RegisterGinRoutes registra un conjunto de rutas en un *gin.RouterGroup y deja constancia de cada endpoint en el logger global de GoKit.
Si se pasa WithAuthManager, las rutas con Protected: true reciben automáticamente el middleware de autenticación (validado contra ese manager) antes de llegar al handler. Sin WithAuthManager, Protected solo aparece en el log.
Ejemplo:
authGroup := r.Group("/auth")
middleware.RegisterGinRoutes(authGroup, []middleware.Route[gin.HandlerFunc]{
{Method: "POST", Path: "/signup", Handler: handler.SignUp, Protected: false},
{Method: "POST", Path: "/signin", Handler: handler.SignIn, Protected: false},
}, middleware.WithGroupName("auth"))
RegisterGinRoutes
func RemoteIPKeyFunc ¶
RemoteIPKeyFunc usa la IP remota de la conexión TCP como clave de rate limit. Es el KeyFunc por defecto si RateLimit recibe nil.
Advertencia: si tu servicio está detrás de un proxy o balanceador de carga, r.RemoteAddr será la IP del proxy, no la del cliente real, y todas las peticiones compartirán el mismo límite. En ese caso, provee tu propio KeyFunc que lea (y valide) X-Forwarded-For o X-Real-IP según la configuración de tu infraestructura — sin validación, un cliente podría falsificar ese header para evadir el límite o afectar a otros.
Types ¶
type CORSConfig ¶
type CORSConfig struct {
// AllowedOrigins es la lista de orígenes permitidos (ej.
// "https://app.example.com"). Usa "*" para permitir cualquier origen —
// pero ten en cuenta que la especificación CORS prohíbe combinar "*"
// con AllowCredentials = true; en ese caso, "*" se ignora.
AllowedOrigins []string
// AllowedMethods es la lista de métodos HTTP permitidos en la petición
// real. Si está vacía, se usa un conjunto por defecto razonable
// (GET, POST, PUT, PATCH, DELETE, OPTIONS).
AllowedMethods []string
// AllowedHeaders es la lista de headers que el cliente puede enviar.
// Si está vacía, se usa un conjunto por defecto (Authorization, Content-Type).
AllowedHeaders []string
// AllowCredentials indica si se permite el envío de cookies/credenciales
// (Access-Control-Allow-Credentials). No es compatible con
// AllowedOrigins = ["*"] según la especificación CORS.
AllowCredentials bool
// MaxAge es cuánto tiempo, en segundos, el navegador puede cachear la
// respuesta a una petición preflight (OPTIONS). Si es 0, no se envía
// la cabecera y el navegador usa su valor por defecto.
MaxAge int
}
CORSConfig define la configuración del middleware CORS.
type KeyFunc ¶
KeyFunc extrae la clave de limitación de tasa a partir de la petición (ej. la IP remota, un userID ya autenticado, una API key del header).
type Logger ¶
type Logger interface {
Info(message string)
}
Logger es la interfaz mínima que necesita RequestLogger. El *logger.Logger de GoKit (paquete github.com/AndresGT/GoKit/logger) la implementa directamente, ya que su método Info tiene esta misma firma; cualquier otro logger compatible funciona igual. No se importa el paquete logger aquí a propósito, para no forzar esa dependencia en quien no lo use.
type MemoryRateLimiter ¶
type MemoryRateLimiter struct {
// contains filtered or unexported fields
}
MemoryRateLimiter implementa RateLimiter en memoria usando el algoritmo de ventana fija (fixed window): permite como máximo 'limit' peticiones por 'window' y por clave.
Adecuado para una sola instancia del servicio. En despliegues con varias instancias/réplicas, el límite se aplica de forma independiente en cada una (no es un límite global) — para eso necesitas un backend compartido (ej. Redis) implementando la interfaz RateLimiter.
func NewMemoryRateLimiter ¶
func NewMemoryRateLimiter(limit int, window time.Duration) *MemoryRateLimiter
NewMemoryRateLimiter crea un limitador de tasa en memoria: como máximo 'limit' peticiones por 'window' y por clave. Si los valores no son válidos, se usan valores por defecto seguros (1 petición / 1 minuto).
func (*MemoryRateLimiter) Allow ¶
func (l *MemoryRateLimiter) Allow(key string) bool
Allow implementa RateLimiter.
func (*MemoryRateLimiter) Cleanup ¶
func (l *MemoryRateLimiter) Cleanup()
Cleanup elimina las claves cuya ventana ya expiró. El mapa interno de MemoryRateLimiter crece con cada clave nueva vista (IP, usuario...) y nunca se reduce por sí solo; en un proceso de larga duración esto es un crecimiento de memoria no acotado. Llama a Cleanup periódicamente (por ejemplo, con un time.Ticker cada pocos minutos) para evitarlo.
Ejemplo de uso:
limiter := middleware.NewMemoryRateLimiter(100, time.Minute)
go func() {
ticker := time.NewTicker(5 * time.Minute)
for range ticker.C {
limiter.Cleanup()
}
}()
type Middleware ¶
Middleware es el tipo estándar de middleware HTTP usado en este paquete: una función que envuelve un http.Handler con otro http.Handler. Es compatible con la firma que usan la mayoría de routers de Go.
func CORS ¶
func CORS(cfg CORSConfig) Middleware
CORS aplica las cabeceras CORS según la configuración proporcionada y responde directamente (204 No Content) a las peticiones preflight (OPTIONS), sin llegar al siguiente handler.
Ejemplo de uso:
cfg := middleware.CORSConfig{
AllowedOrigins: []string{"https://app.example.com"},
AllowCredentials: true,
}
handler := middleware.CORS(cfg)(mux)
func RateLimit ¶
func RateLimit(limiter RateLimiter, keyFunc KeyFunc) Middleware
RateLimit limita el número de peticiones permitidas por clave (ver KeyFunc). Responde 429 Too Many Requests cuando se supera el límite.
Si keyFunc es nil, se usa RemoteIPKeyFunc.
Ejemplo de uso:
limiter := middleware.NewMemoryRateLimiter(60, time.Minute) // 60 req/min por IP
handler := middleware.RateLimit(limiter, nil)(mux)
// Limitar por usuario autenticado en vez de por IP:
byUser := func(r *http.Request) string {
if claims, ok := middleware.ClaimsFromContext(r.Context()); ok {
return claims.UserID
}
return middleware.RemoteIPKeyFunc(r)
}
handler := middleware.RequireAuth(jwtManager)(
middleware.RateLimit(limiter, byUser)(mux),
)
func RequestLogger ¶
func RequestLogger(log Logger) Middleware
RequestLogger registra cada petición HTTP (método, ruta, status y duración) usando el Logger proporcionado. Si el handler nunca llama explícitamente a WriteHeader, se asume el código 200 por defecto, tal como hace net/http.
Ejemplo de uso:
log := logger.New() handler := middleware.RequestLogger(log)(mux)
func RequireActiveSession ¶
func RequireActiveSession(sessions *token.SessionManager) Middleware
RequireActiveSession verifica, además de la firma y expiración del JWT, que la sesión asociada (claims.SessionID) siga activa según el SessionManager proporcionado. Responde 401 Unauthorized si la sesión fue revocada, expiró o no existe.
Esto cubre una limitación inherente de los JWT: un access token firmado correctamente sigue siendo válido hasta que expira por sí solo, aunque el usuario haya cerrado sesión, cambiado su contraseña o se le haya revocado el acceso. Encadenar este middleware permite invalidar el acceso de forma inmediata a costa de una consulta adicional al SessionStore en cada petición — una decisión de diseño consciente entre rendimiento y capacidad de revocación instantánea; úsalo en las rutas donde esa garantía importe.
Debe encadenarse DESPUÉS de RequireAuth.
Ejemplo de uso:
authed := middleware.RequireAuth(jwtManager)
activeSession := middleware.RequireActiveSession(sessionManager)
mux.Handle("/perfil", authed(activeSession(http.HandlerFunc(perfilHandler))))
func RequireAuth ¶
func RequireAuth(manager *token.JWTManager) Middleware
RequireAuth valida el access token Bearer del header Authorization usando el JWTManager proporcionado. Si el token es válido, inyecta sus claims en el contexto de la petición (recuperables con ClaimsFromContext) y continúa la cadena; en caso contrario responde 401 Unauthorized sin llegar al siguiente handler.
Rechaza explícitamente los refresh tokens: solo un token cuyo TokenType sea token.TokenTypeAccess se considera una credencial de acceso válida.
Ejemplo de uso:
authed := middleware.RequireAuth(jwtManager)
mux.Handle("/perfil", authed(http.HandlerFunc(perfilHandler)))
func RequireRole ¶
func RequireRole(roles ...string) Middleware
RequireRole restringe el acceso a los usuarios cuyo claim Role esté entre los roles permitidos. Responde 403 Forbidden si el rol no está permitido.
Debe encadenarse DESPUÉS de RequireAuth: depende de los claims que este último inyecta en el contexto. Si RequireAuth no se ejecutó antes, responde 401 Unauthorized.
Ejemplo de uso:
authed := middleware.RequireAuth(jwtManager)
adminOnly := middleware.RequireRole("admin", "superadmin")
mux.Handle("/admin", authed(adminOnly(http.HandlerFunc(adminHandler))))
type RateLimiter ¶
type RateLimiter interface {
// Allow indica si, en este momento, se permite una nueva petición para
// la clave indicada (ej. una IP, un userID, una API key).
Allow(key string) bool
}
RateLimiter define el contrato para backends de limitación de tasa. Permite implementar el algoritmo o backend que prefieras (memoria, Redis, etc.) sin modificar el middleware RateLimit que lo consume.
type RegisterOption ¶
type RegisterOption func(*RegisterOptions)
RegisterOption modifica un RegisterOptions.
func WithAuthManager ¶
func WithAuthManager(manager *token.JWTManager) RegisterOption
WithAuthManager habilita la aplicación automática de autenticación en las rutas marcadas como Protected: true, validando contra el manager dado.
func WithGroupName ¶
func WithGroupName(name string) RegisterOption
WithGroupName etiqueta el grupo de rutas en el log de arranque.
type RegisterOptions ¶
type RegisterOptions struct {
// AuthManager, si se proporciona, hace que las rutas con
// Protected: true reciban automáticamente un middleware de
// autenticación validado contra este manager, además de reflejarlo en
// el log. Si se omite, Protected es solo informativo.
AuthManager *token.JWTManager
// GroupName es una etiqueta opcional (ej. "auth", "admin") que se
// incluye en el log de arranque para identificar a qué grupo
// pertenece cada ruta registrada.
GroupName string
}
RegisterOptions configura el comportamiento de RegisterGinRoutes y RegisterFiberRoutes.
type Route ¶
type Route[H any] struct { // Method es el verbo HTTP en mayúsculas ("GET", "POST", "PUT", "DELETE", ...). Method string // Path es la ruta relativa al grupo/router en el que se registra. Path string // Handler es el handler nativo del framework para este endpoint. Handler H // Protected indica si el endpoint requiere autenticación. Por sí solo // solo afecta al log de arranque; para que además se aplique el // middleware de autenticación automáticamente, registra las rutas con // WithAuthManager. Protected bool }
Route describe un endpoint a registrar en un router concreto. H es el tipo de handler nativo del framework (gin.HandlerFunc o fiber.Handler), lo que permite reutilizar la misma estructura tanto para Gin como para Fiber sin duplicar la definición de rutas.
Ejemplo:
routes := []middleware.Route[gin.HandlerFunc]{
{Method: "POST", Path: "/signup", Handler: handler.SignUp, Protected: false},
{Method: "POST", Path: "/signin", Handler: handler.SignIn, Protected: false},
}