JWT
Esta receita demonstra autenticação JWT com Echo usando o middleware
echo-jwt:
- Autenticação JWT usando o algoritmo HS256.
- O token é lido do header de request
Authorization.
Veja a página do middleware JWT para opções completas de configuração.
Servidor
Seção intitulada “Servidor”Usando claims customizadas
Seção intitulada “Usando claims customizadas”Defina um tipo de claims que incorpora jwt.RegisteredClaims, então aponte o middleware
para ele com NewClaimsFunc. Dentro do handler restrito, recupere o token analisado
do contexto com o echo.ContextGet genérico.
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")
// Demo only: hard-coded credentials compared in plain text. In production, // look up the user and verify a hashed password with a constant-time compare // (e.g. golang.org/x/crypto/bcrypt's CompareHashAndPassword) to avoid timing attacks. 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) }}Usando uma KeyFunc definida pelo usuário
Seção intitulada “Usando uma KeyFunc definida pelo usuário”Quando tokens são assinados por um provedor de identidade externo, forneça uma KeyFunc que
resolva a chave de assinatura dinamicamente. Este exemplo valida tokens emitidos pelo
Google Sign-In buscando o conjunto de chaves públicas do 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
Seção intitulada “Cliente”Faça login com usuário e senha para recuperar um token.
curl -X POST -d 'username=jon' -d 'password=shhh!' localhost:1323/loginResponse:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"}Request
Seção intitulada “Request”Solicite um recurso restrito usando o token no header de request Authorization.
curl localhost:1323/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"Response:
Welcome Jon Snow!