JWT middleware for Echo framework. This middleware uses by default golang-jwt/jwt/v5 as JWT implementation.
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.
For Echo v4
use v4.x.y
releases.
Minimal needed Echo versions:
v4.0.0
needs Echov4.7.0+
main
branch is compatible with the latest Echo version.
Add JWT middleware dependency with go modules
go get github.com/labstack/echo-jwt/v4
Use as import statement
import "github.com/labstack/echo-jwt/v4"
Add middleware in simplified form, by providing only the secret key
e.Use(echojwt.JWT([]byte("secret")))
Add middleware with configuration options
e.Use(echojwt.WithConfig(echojwt.Config{
// ...
SigningKey: []byte("secret"),
// ...
}))
Extract token in handler
import "github.com/golang-jwt/jwt/v5"
// ...
e.GET("/", func(c echo.Context) error {
token, ok := c.Get("user").(*jwt.Token) // by default token is stored under `user` key
if !ok {
return errors.New("JWT token missing or invalid")
}
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)
})
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.
func TestIntegrationMiddlewareWithHandler(t *testing.T) {
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 <TOKEN>")
res := httptest.NewRecorder()
e.ServeHTTP(res, req)
if res.Code != 200 {
t.Failed()
}
}
package main
import (
"errors"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"log"
"net/http"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(echojwt.WithConfig(echojwt.Config{
SigningKey: []byte("secret"),
}))
e.GET("/", func(c echo.Context) error {
token, ok := c.Get("user").(*jwt.Token) // by default token is stored under `user` key
if !ok {
return errors.New("JWT token missing or invalid")
}
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 != http.ErrServerClosed {
log.Fatal(err)
}
}
Test with
curl -v -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" http://localhost:8080
Output should be
* Trying 127.0.0.1:8080...
* 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"}