echojwt

package module
v4.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2023 License: MIT Imports: 8 Imported by: 174

README

Sourcegraph GoDoc Go Report Card Codecov License

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.

For Echo v4 use v4.x.y releases. Minimal needed Echo versions:

  • v4.0.0 needs Echo v4.7.0+

main branch is compatible with the latest Echo version.

Usage

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

Full example

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

Documentation

Index

Examples

Constants

View Source
const (
	// AlgorithmHS256 is token signing algorithm
	AlgorithmHS256 = "HS256"
)

Variables

View Source
var ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")

ErrJWTInvalid denotes an error raised when JWT token value is invalid or expired

View Source
var ErrJWTMissing = echo.NewHTTPError(http.StatusUnauthorized, "missing or malformed jwt")

ErrJWTMissing denotes an error raised when JWT token value could not be extracted from request

Functions

func CreateExtractors

func CreateExtractors(lookups string) ([]middleware.ValuesExtractor, error)

CreateExtractors creates ValuesExtractors from given lookups. Lookups is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used to extract key from the request. Possible values:

  • "header:<name>" or "header:<name>:<cut-prefix>" `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we want to cut is `<auth-scheme> ` note the space at the end. In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
  • "query:<name>"
  • "param:<name>"
  • "form:<name>"
  • "cookie:<name>"

Multiple sources example: - "header:Authorization,header:X-Api-Key"

func JWT

func JWT(signingKey interface{}) echo.MiddlewareFunc

JWT returns a JSON Web Token (JWT) auth middleware.

For valid token, it sets the user in context and calls next handler. For invalid token, it returns "401 - Unauthorized" error. For missing token, it returns "400 - Bad Request" error.

See: https://jwt.io/introduction

func WithConfig

func WithConfig(config Config) echo.MiddlewareFunc

WithConfig returns a JSON Web Token (JWT) auth middleware or panics if configuration is invalid.

For valid token, it sets the user in context and calls next handler. For invalid token, it returns "401 - Unauthorized" error. For missing token, it returns "400 - Bad Request" error.

See: https://jwt.io/introduction

Example (Usage)
e := echo.New()

e.Use(echojwt.WithConfig(echojwt.Config{
	SigningKey: []byte("secret"),
}))

e.GET("/", func(c echo.Context) error {
	// make sure that your imports are correct versions. for example if you use `"github.com/golang-jwt/jwt"` as
	// import this cast will fail and `"github.com/golang-jwt/jwt/v5"` will succeed.
	// Although `.(*jwt.Token)` looks exactly the same for both packages but this struct is still different
	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)
})

// ----------------------- start server on random port -----------------------
l, err := net.Listen("tcp", ":0")
if err != nil {
	log.Fatal(err)
}
go func(e *echo.Echo, l net.Listener) {
	s := http.Server{Handler: e}
	if err := s.Serve(l); err != http.ErrServerClosed {
		log.Fatal(err)
	}
}(e, l)
time.Sleep(100 * time.Millisecond)

// ----------------------- execute HTTP request with valid token and check the response -----------------------
requestURL := fmt.Sprintf("http://%v", l.Addr().String())
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
	log.Fatal(err)
}
req.Header.Set(echo.HeaderAuthorization, "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ")

res, err := http.DefaultClient.Do(req)
if err != nil {
	log.Fatal(err)
}

body, err := io.ReadAll(res.Body)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Response: status code: %d, body: %s\n", res.StatusCode, body)
Output:

Response: status code: 200, body: {"admin":true,"name":"John Doe","sub":"1234567890"}

Types

type Config

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

	// BeforeFunc defines a function which is executed just before the middleware.
	BeforeFunc middleware.BeforeFunc

	// SuccessHandler defines a function which is executed for a valid token.
	SuccessHandler func(c echo.Context)

	// ErrorHandler defines a function which is executed when all lookups have been done and none of them passed Validator
	// function. ErrorHandler is executed with last missing (ErrExtractionValueMissing) or an invalid key.
	// It may be used to define a custom JWT error.
	//
	// Note: when error handler swallows the error (returns nil) middleware continues handler chain execution towards handler.
	// This is useful in cases when portion of your site/api is publicly accessible and has extra features for authorized users
	// In that case you can use ErrorHandler to set default public JWT token value to request and continue with handler chain.
	ErrorHandler func(c echo.Context, err error) error

	// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
	// ignore the error (by returning `nil`).
	// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
	// In that case you can use ErrorHandler to set a default public JWT token value in the request context
	// and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
	ContinueOnIgnoredError bool

	// Context key to store user information from the token into context.
	// Optional. Default value "user".
	ContextKey string

	// Signing key to validate token.
	// This is one of the three options to provide a token validation key.
	// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
	// Required if neither user-defined KeyFunc nor SigningKeys is provided.
	SigningKey interface{}

	// Map of signing keys to validate token with kid field usage.
	// This is one of the three options to provide a token validation key.
	// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
	// Required if neither user-defined KeyFunc nor SigningKey is provided.
	SigningKeys map[string]interface{}

	// Signing method used to check the token's signing algorithm.
	// Optional. Default value HS256.
	SigningMethod string

	// KeyFunc defines a user-defined function that supplies the public key for a token validation.
	// The function shall take care of verifying the signing algorithm and selecting the proper key.
	// A user-defined KeyFunc can be useful if tokens are issued by an external party.
	// Used by default ParseTokenFunc implementation.
	//
	// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
	// This is one of the three options to provide a token validation key.
	// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
	// Required if neither SigningKeys nor SigningKey is provided.
	// Not used if custom ParseTokenFunc is set.
	// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
	KeyFunc jwt.Keyfunc

	// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
	// to extract token from the request.
	// Optional. Default value "header:Authorization".
	// Possible values:
	// - "header:<name>" or "header:<name>:<cut-prefix>"
	// 			`<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
	//			value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
	//			want to cut is `<auth-scheme> ` note the space at the end.
	//			In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
	// If prefix is left empty the whole value is returned.
	// - "query:<name>"
	// - "param:<name>"
	// - "cookie:<name>"
	// - "form:<name>"
	// Multiple sources example:
	// - "header:Authorization:Bearer ,cookie:myowncookie"
	TokenLookup string

	// TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
	// This is one of the two options to provide a token extractor.
	// The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
	// You can also provide both if you want.
	TokenLookupFuncs []middleware.ValuesExtractor

	// ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token
	// parsing fails or parsed token is invalid.
	// Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library
	ParseTokenFunc func(c echo.Context, auth string) (interface{}, error)

	// Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation.
	// Not used if custom ParseTokenFunc is set.
	// Optional. Defaults to function returning jwt.MapClaims
	NewClaimsFunc func(c echo.Context) jwt.Claims
}

Config defines the config for JWT middleware.

func (Config) ToMiddleware

func (config Config) ToMiddleware() (echo.MiddlewareFunc, error)

ToMiddleware converts Config to middleware or returns an error for invalid configuration

type TokenError added in v4.0.1

type TokenError struct {
	Token *jwt.Token
	Err   error
}

TokenError is used to return error with error occurred JWT token when processing JWT token

func (*TokenError) Error added in v4.0.1

func (e *TokenError) Error() string

func (*TokenError) Unwrap added in v4.0.1

func (e *TokenError) Unwrap() error

type TokenExtractionError added in v4.1.0

type TokenExtractionError struct {
	Err error
}

TokenExtractionError is catch all type for all errors that occur when the token is extracted from the request. This helps to distinguish extractor errors from token parsing errors even if custom extractors or token parsing functions are being used that have their own custom errors.

func (*TokenExtractionError) Error added in v4.1.0

func (e *TokenExtractionError) Error() string

func (TokenExtractionError) Is added in v4.1.0

func (e TokenExtractionError) Is(target error) bool

Is checks if target error is same as TokenExtractionError

func (*TokenExtractionError) Unwrap added in v4.1.0

func (e *TokenExtractionError) Unwrap() error

type TokenParsingError added in v4.1.0

type TokenParsingError struct {
	Err error
}

TokenParsingError is catch all type for all errors that occur when token is parsed. In case of library default token parsing functions are being used this error instance wraps TokenError. This helps to distinguish extractor errors from token parsing errors even if custom extractors or token parsing functions are being used that have their own custom errors.

func (*TokenParsingError) Error added in v4.1.0

func (e *TokenParsingError) Error() string

func (TokenParsingError) Is added in v4.1.0

func (e TokenParsingError) Is(target error) bool

Is checks if target error is same as TokenParsingError

func (*TokenParsingError) Unwrap added in v4.1.0

func (e *TokenParsingError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL