Skip to content

JWT

This recipe demonstrates JWT authentication with Echo using the echo-jwt middleware:

  • JWT authentication using the HS256 algorithm.
  • The token is read from the Authorization request header.

See the JWT middleware page for full configuration options.

Define a claims type that embeds jwt.RegisteredClaims, then point the middleware at it with NewClaimsFunc. Inside the restricted handler, retrieve the parsed token from the context with the generic 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 examples
type 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)
}
}

When tokens are signed by an external identity provider, supply a KeyFunc that resolves the signing key dynamically. This example validates tokens issued by Google Sign-In by fetching Google’s public key set.

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)
}
}

Log in with a username and password to retrieve a token.

Terminal window
curl -X POST -d 'username=jon' -d 'password=shhh!' localhost:1323/login

Response:

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
}

Request a restricted resource using the token in the Authorization request header.

Terminal window
curl localhost:1323/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"

Response:

Terminal window
Welcome Jon Snow!