---
name: jwt_middleware
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func JWTMiddleware(secret string) Middleware"
description: "Middleware HTTP que extrae el JWT del header Authorization: Bearer y valida con JWTValidate. Inyecta las claims en el context del request (recuperables con JWTClaimsFromContext). Responde 401 si falta el header, formato incorrecto o token invalido."
tags: [jwt, auth, middleware, http, server, infra]
uses_functions: [jwt_validate_go_infra, http_error_response_go_infra]
uses_types: [JWTClaims_go_infra, Middleware_go_infra, HTTPError_go_infra]
returns: [Middleware_go_infra]
returns_optional: false
error_type: error_go_core
imports: [context, net/http, strings]
params:
  - name: secret
    desc: "clave HMAC para JWTValidate. Debe ser la misma usada en JWTGenerate"
output: "Middleware que protege handlers con validacion JWT. Las claims se inyectan en r.Context() con una key privada"
tested: true
tests: ["pasa con token valido", "401 sin header Authorization", "401 con formato distinto de Bearer", "401 con token invalido", "claims accesibles via JWTClaimsFromContext"]
test_file_path: "functions/infra/jwt_middleware_test.go"
file_path: "functions/infra/jwt_middleware.go"
---

## Ejemplo

```go
protected := HTTPMiddlewareChain(
    HTTPLoggerMiddleware(os.Stderr),
    JWTMiddleware(os.Getenv("JWT_SECRET")),
)
mux.Handle("GET /api/me", protected(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    claims, _ := JWTClaimsFromContext(r.Context())
    HTTPJSONResponse(w, 200, map[string]string{"user_id": claims.Subject})
})))
```

## Notas

Impura — lee headers y modifica el request. Expone el helper JWTClaimsFromContext(ctx) que devuelve (JWTClaims, bool) — el bool permite distinguir "no autenticado" de "subject vacio". Usa `context.WithValue` con una key de tipo privado `jwtCtxKey struct{}` para evitar colisiones con otros middlewares. Solo soporta cabecera `Authorization: Bearer`; para leer token desde cookie se crearia un middleware separado. En las respuestas 401 no se da detalle del motivo (token expirado vs firma invalida) para no filtrar informacion, el motivo real esta en los logs si se compone con HTTPLoggerMiddleware.
