Echo JWT middleware
JWT middleware for Echo framework. This middleware uses by default golang-jwt/jwt/v5 as JWT implementation.
Versioning
This repository does not use semantic versioning. MAJOR version tracks which Echo version should be used. MINOR version tracks API changes (possibly backwards incompatible) and PATCH version is incremented for fixes.
NB: When golang-jwt MAJOR version changes this library will release MINOR version with breaking change. Always
add at least one integration test in your project.
For Echo v5 use v5.x.y releases.
Minimal needed Echo versions:
v5.0.0needs Echov5.0.0+v4.0.0needs Echov4.7.0+
main branch is compatible with the latest Echo version.
Usage
Add JWT middleware dependency with go modules
Use as import statement
Add middleware in simplified form, by providing only the secret key
Add middleware with configuration options
// ...
SigningKey: []byte("secret"),
// ...
}))
Extract token in handler
// ...
e.GET("/", func(c *echo.Context) error {
token, err := echo.ContextGet[*jwt.Token](c,"user")
if err != nil {
return echo.ErrUnauthorized.Wrap(err)
}
claims, ok := token.Claims.(jwt.MapClaims) // by default claims is of type `jwt.MapClaims`
if !ok {
return errors.New("failed to cast claims as jwt.MapClaims")
}
return c.JSON(http.StatusOK, claims)
})
IMPORTANT: Integration Testing with JWT Library
Ensure that your project includes at least one integration test to detect changes in major versions of the
golang-jwt/jwt library early.
This is crucial because type assertions like token := c.Get("user").(*jwt.Token) may fail silently if the imported
version of the JWT library (e.g., import "github.com/golang-jwt/jwt/v5") differs from the version used internally by
dependencies (e.g., echo-jwt may now use v6). Such discrepancies can lead to invalid casts, causing your handlers to
panic or throw errors. Integration tests help safeguard against these version mismatches.
e := echo.New()
e.Use(echojwt.WithConfig(echojwt.Config{
SigningKey: []byte("secret"),
}))
// use handler that gets token from context to fail your CI flow when `golang-jwt/jwt` library version changes
// a) `token, ok := c.Get("user").(*jwt.Token)`
// b) `token := c.Get("user").(*jwt.Token)`
e.GET("/example", exampleHandler)
req := httptest.NewRequest(http.MethodGet, "/example", nil)
req.Header.Set(echo.HeaderAuthorization, "Bearer
res := httptest.NewRecorder()
e.ServeHTTP(res, req)
if res.Code != 200 {
t.Failed()
}
}
Full example
import (
"errors"
"log/slog"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo-jwt/v5"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
"net/http"
)
func main() {
e := echo.New()
e.Use(middleware.RequestLogger())
e.Use(middleware.Recover())
e.Use(echojwt.WithConfig(echojwt.Config{
SigningKey: []byte("secret"),
}))
e.GET("/", func(c *echo.Context) error {
token, err := echo.ContextGet[*jwt.Token](c, "user")
if err != nil {
return echo.ErrUnauthorized.Wrap(err)
}
claims, ok := token.Claims.(jwt.MapClaims) // by default claims is of type `jwt.MapClaims`
if !ok {
return errors.New("failed to cast claims as jwt.MapClaims")
}
return c.JSON(http.StatusOK, claims)
})
if err := e.Start(":8080"); err != nil {
slog.Error("Failed to start server", "error", err)
}
}
Test with
Output should be
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Date: Sun, 27 Nov 2022 21:34:17 GMT
< Content-Length: 52
<
{"admin":true,"name":"John Doe","sub":"1234567890"}