echo-jwt Go Report Card codecov

A JWT middleware for the Echo framework using lestrrat-go/jwx.

Installing

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.

openssl genrsa -out private-key.pem 4096

Code example:

package main

import (
	"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 = key

	e.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:

curl -X POST http://localhost:1323/login\?name\=alex
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOj..."}

Using a token:

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:

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

type Config struct {
    // Skipper defines a function to skip middleware.
    Skipper middleware.Skipper

    // Key defines the RSA key used to verify tokens.
    // Required.
    Key interface{}

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

GitHub

View Github