JWT
Esta receta demuestra autenticación JWT con Echo usando el middleware
echo-jwt:
- Autenticación JWT usando el algoritmo HS256.
- El token se lee desde el header de request
Authorization.
Consulta la página del middleware JWT para ver todas las opciones de configuración.
Servidor
Sección titulada «Servidor»Usar claims personalizados
Sección titulada «Usar claims personalizados»Define un tipo de claims que embebe jwt.RegisteredClaims, y luego apunta el middleware a él
con NewClaimsFunc. Dentro del handler restringido, recupera el token parseado desde el
contexto con el genérico echo.ContextGet.
package main
import ( "context" "net/http" "time"
"github.com/golang-jwt/jwt/v5" echojwt "github.com/labstack/echo-jwt/v5" "github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware")
// jwtCustomClaims are custom claims extending default ones.// See https://github.com/golang-jwt/jwt for more examplestype jwtCustomClaims struct { Name string `json:"name"` Admin bool `json:"admin"` jwt.RegisteredClaims}
func login(c *echo.Context) error { username := c.FormValue("username") password := c.FormValue("password")
// Throws unauthorized error if username != "jon" || password != "shhh!" { return echo.ErrUnauthorized }
// Set custom claims claims := &jwtCustomClaims{ "Jon Snow", true, jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 72)), }, }
// Create token with claims token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Generate encoded token and send it as response. t, err := token.SignedString([]byte("secret")) if err != nil { return err }
return c.JSON(http.StatusOK, map[string]string{ "token": t, })}
func accessible(c *echo.Context) error { return c.String(http.StatusOK, "Accessible")}
func restricted(c *echo.Context) error { token, err := echo.ContextGet[*jwt.Token](c, "user") if err != nil { return echo.ErrUnauthorized.Wrap(err) } claims := token.Claims.(*jwtCustomClaims) name := claims.Name return c.String(http.StatusOK, "Welcome "+name+"!")}
func main() { e := echo.New()
// Middleware e.Use(middleware.RequestLogger()) e.Use(middleware.Recover())
// Login route e.POST("/login", login)
// Unauthenticated route e.GET("/", accessible)
// Restricted group r := e.Group("/restricted")
// Configure middleware with the custom claims type config := echojwt.Config{ NewClaimsFunc: func(c *echo.Context) jwt.Claims { return new(jwtCustomClaims) }, SigningKey: []byte("secret"), } r.Use(echojwt.WithConfig(config)) r.GET("", restricted)
sc := echo.StartConfig{Address: ":1323"} if err := sc.Start(context.Background(), e); err != nil { e.Logger.Error("failed to start server", "error", err) }}Usar un KeyFunc definido por el usuario
Sección titulada «Usar un KeyFunc definido por el usuario»Cuando los tokens están firmados por un proveedor de identidad externo, proporciona un KeyFunc
que resuelva dinámicamente la signing key. Este ejemplo valida tokens emitidos por Google Sign-In
obteniendo el conjunto de claves públicas de Google.
package main
import ( "context" "errors" "fmt" "net/http"
echojwt "github.com/labstack/echo-jwt/v5"
"github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" "github.com/lestrrat-go/jwx/v3/jwk")
func getKey(token *jwt.Token) (any, error) {
// For a demonstration purpose, Google Sign-in is used. // https://developers.google.com/identity/sign-in/web/backend-auth // // This user-defined KeyFunc verifies tokens issued by Google Sign-In. // // Note: In this example, it downloads the keyset every time the restricted route is accessed. keySet, err := jwk.Fetch(context.Background(), "https://www.googleapis.com/oauth2/v3/certs") if err != nil { return nil, err }
keyID, ok := token.Header["kid"].(string) if !ok { return nil, errors.New("expecting JWT header to have a key ID in the kid field") }
key, found := keySet.LookupKeyID(keyID)
if !found { return nil, fmt.Errorf("unable to find key %q", keyID) }
return key.PublicKey()}
func accessible(c *echo.Context) error { return c.String(http.StatusOK, "Accessible")}
func restricted(c *echo.Context) error { user, err := echo.ContextGet[*jwt.Token](c, "user") if err != nil { return echo.ErrUnauthorized.Wrap(err) } claims := user.Claims.(jwt.MapClaims) name := claims["name"].(string) return c.String(http.StatusOK, "Welcome "+name+"!")}
func main() { e := echo.New()
// Middleware e.Use(middleware.RequestLogger()) e.Use(middleware.Recover())
// Unauthenticated route e.GET("/", accessible)
// Restricted group r := e.Group("/restricted") { config := echojwt.Config{ KeyFunc: getKey, } r.Use(echojwt.WithConfig(config)) r.GET("", restricted) }
sc := echo.StartConfig{Address: ":1323"} if err := sc.Start(context.Background(), e); err != nil { e.Logger.Error("failed to start server", "error", err) }}Cliente
Sección titulada «Cliente»Inicia sesión con un username y password para obtener un token.
curl -X POST -d 'username=jon' -d 'password=shhh!' localhost:1323/loginResponse:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"}Request
Sección titulada «Request»Solicita un recurso restringido usando el token en el header de request Authorization.
curl localhost:1323/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"Response:
Welcome Jon Snow!