https://github.com/alexferl/echo-jwt
JWT middleware for the Echo framework
https://github.com/alexferl/echo-jwt
echo echo-jwt echo-middleware jwt jwt-middleware labstack-echo
Last synced: 3 months ago
JSON representation
JWT middleware for the Echo framework
- Host: GitHub
- URL: https://github.com/alexferl/echo-jwt
- Owner: alexferl
- License: mit
- Created: 2022-10-19T15:13:06.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-02-06T18:01:39.000Z (over 1 year ago)
- Last Synced: 2025-01-22T16:48:44.606Z (5 months ago)
- Topics: echo, echo-jwt, echo-middleware, jwt, jwt-middleware, labstack-echo
- Language: Go
- Homepage:
- Size: 54.7 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# echo-jwt [](https://goreportcard.com/report/github.com/alexferl/echo-jwt) [](https://codecov.io/gh/alexferl/echo-jwt)
A [JWT](https://jwt.io/) middleware for the [Echo](https://github.com/labstack/echo) framework using
[lestrrat-go/jwx](https://github.com/lestrrat-go/jwx).## Motivation
You might wonder why not use the JWT middleware that ships with Echo?
The reason is that it uses the [golang-jwt/jwt](https://github.com/golang-jwt/jwt) library which,
although a good library, doesn't implement every JWT features while [lestrrat-go/jwx](https://github.com/lestrrat-go/jwx)
is the [most complete](https://jwt.io/libraries?language=Go) implementation as of this writing.
I think echo-jwt also has better defaults, like `RS256` as the default signing method and is also more flexible in what
parsing options you can pass to the token verification function through the `Options` config.
I think other features like `ExemptRoutes`, `ExemptMethods`, `OptionalRoutes` and `RefreshToken` are useful features
that most developers would want to use without having to implement them themselves.## Installing
```shell
go get github.com/alexferl/echo-jwt
```## Using
Before using the middleware you need to generate an RSA private key (RSASSA-PKCS-v1.5 using SHA-256) to
sign and verify the tokens.```shell
openssl genrsa -out private-key.pem 4096
```### Code example
```go
package mainimport (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"net/http"
"os"
"time""github.com/alexferl/echo-jwt"
"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwa"
jwx "github.com/lestrrat-go/jwx/v2/jwt"
)var privateKey *rsa.PrivateKey
func main() {
e := echo.New()e.GET("/", func(c echo.Context) error {
t := c.Get("token").(jwx.Token)
return c.JSON(http.StatusOK, t)
})e.POST("/login", func(c echo.Context) error {
builder := jwx.NewBuilder().
Subject("1").
Issuer("http://localhost:1323").
IssuedAt(time.Now()).
NotBefore(time.Now()).
Expiration(time.Now().Add(time.Minute*10)).
Claim("name", c.QueryParam("name"))token, err := builder.Build()
if err != nil {
panic(fmt.Sprintf("failed building token: %v\n", err))
}signed, err := jwx.Sign(token, jwx.WithKey(jwa.RS256, privateKey))
if err != nil {
panic(fmt.Sprintf("failed signing token: %v\n", err))
}return c.JSON(http.StatusOK, map[string]string{"access_token": string(signed)})
})key, err := loadPrivateKey("/path/to/private-key.pem")
if err != nil {
panic(fmt.Sprintf("failed loading private key: %v\n", err))
}
privateKey = keye.Use(jwt.JWT(key))
e.Logger.Fatal(e.Start("localhost:1323"))
}func loadPrivateKey(path string) (*rsa.PrivateKey, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}b, err := io.ReadAll(f)
if err != nil {
return nil, err
}block, _ := pem.Decode(b)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block: %v", err)
}key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}return key, nil
}
```Getting a token:
```shell
curl -X POST http://localhost:1323/login\?name\=alex
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOj..."}
```Using a token:
```shell
curl http://localhost:1323/ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOj...'
{"exp":1666320946,"iat":1666320346,"iss":"http://localhost:1323","name":"name","nbf":1666320346,"sub":"1"}
```### Exempt routes
By default, *all* routes except `POST /login` will require a token in
the `Authorization` header or as a cookie with the key `access_token`.You may define some additional exempted routes and methods that don't require a token:
```go
e.Use(jwt.JWTWithConfig(jwt.Config{
ExemptRoutes: map[string][]string{
"/": {http.MethodGet},
"/login": {http.MethodPost},
"/users": {http.MethodPost, http.MethodGet},
"/users/:id": {http.MethodGet},
},
Key: key,
}))
```### Configuration
```go
type Config struct {
// Skipper defines a function to skip middleware.
Skipper middleware.Skipper// Key defines the RSA key used to verify tokens.
// Required.
Key any// ExemptRoutes defines routes and methods that don't require tokens.
// Optional. Defaults to /login [POST].
ExemptRoutes map[string][]string// ExemptMethods defines methods that don't require tokens.
// Optional. Defaults to [OPTIONS].
ExemptMethods []string// OptionalRoutes defines routes and methods that
// can optionally require a token.
// Optional.
OptionalRoutes map[string][]string// ParseTokenFunc defines a function used to decode tokens.
// Optional.
ParseTokenFunc func(string, []jwt.ParseOption) (jwt.Token, error)// AfterParseFunc defines a function that will run after
// the ParseTokenFunc has successfully run.
// Optional.
AfterParseFunc func(echo.Context, jwt.Token, string, TokenSource) *echo.HTTPError// Options defines jwt.ParseOption options for parsing tokens.
// Optional. Defaults [jwt.WithValidate(true)].
Options []jwt.ParseOption// ContextKey defines the key that will be used to store the token
// on the echo.Context when the token is successfully parsed.
// Optional. Defaults to "token".
ContextKey string// CookieKey defines the key that will be used to read the token
// from an HTTP cookie.
// Optional. Defaults to "access_token".
CookieKey string// AuthHeader defines the HTTP header that will be used to
// read the token from.
// Optional. Defaults to "Authorization".
AuthHeader string// AuthScheme defines the authorization scheme in the AuthHeader.
// Optional. Defaults to "Bearer".
AuthScheme string// UseRefreshToken controls whether refresh tokens are used or not.
// Optional. Defaults to false.
UseRefreshToken bool// RefreshToken holds the configuration related to refresh tokens.
// Optional.
RefreshToken *RefreshToken
}type RefreshToken struct {
// ContextKey defines the key that will be used to store the refresh token
// on the echo.Context when the token is successfully parsed.
// Optional. Defaults to "refresh_token".
ContextKey string// ContextKeyEncoded defines the key that will be used to store the encoded
// refresh token on the echo.Context when the token is successfully parsed.
// Optional. Defaults to "refresh_token_encoded".
ContextKeyEncoded string// CookieKey defines the key that will be used to read the refresh token
// from an HTTP cookie.
// Optional. Defaults to "refresh_token".
CookieKey string// BodyMIMEType defines the expected MIME type of the request body.
// Returns a 400 Bad Request if the request's Content-Type header does not match.
// Optional. Defaults to "application/json".
BodyMIMEType string// BodyKey defines the key that will be used to read the refresh token
// from the request's body.
// Returns a 422 UnprocessableEntity if the request's body key is missing.
// Optional. Defaults to "refresh_token".
BodyKey string// Routes defines routes and methods that require a refresh token.
// Optional. Defaults to /auth/refresh [POST] and /auth/logout [POST].
Routes map[string][]string
}
```