jwtware

package module
v3.3.10 Latest Latest
Warning

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

Go to latest
Published: May 8, 2023 License: MIT Imports: 19 Imported by: 201

README

JSON Web Tokens

Release Discord Test Security Linter

JWT returns a JSON Web Token (JWT) auth middleware. For valid token, it sets the user in Ctx.Locals and calls next handler. For invalid token, it returns "401 - Unauthorized" error. For missing token, it returns "400 - Bad Request" error.

Special thanks and credits to Echo

Install

This middleware supports Fiber v1 & v2, install accordingly.

go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/jwt/v3
go get -u github.com/golang-jwt/jwt/v4
Signature
jwtware.New(config ...jwtware.Config) func(*fiber.Ctx) error
Config
Property Type Description Default
Filter func(*fiber.Ctx) bool Defines a function to skip middleware nil
SuccessHandler func(*fiber.Ctx) error SuccessHandler defines a function which is executed for a valid token. nil
ErrorHandler func(*fiber.Ctx, error) error ErrorHandler defines a function which is executed for an invalid token. 401 Invalid or expired JWT
SigningKey interface{} Signing key to validate token. Used as fallback if SigningKeys has length 0. nil
SigningKeys map[string]interface{} Map of signing keys to validate token with kid field usage. nil
SigningMethod string Signing method, used to check token signing method. Possible values: HS256, HS384, HS512, ES256, ES384, ES512, RS256, RS384, RS512 "HS256"
ContextKey string Context key to store user information from the token into context. "user"
Claims jwt.Claim Claims are extendable claims data defining token content. jwt.MapClaims{}
TokenLookup string TokenLookup is a string in the form of <source>:<name> that is used "header:Authorization"
AuthScheme string AuthScheme to be used in the Authorization header. The default value ("Bearer") will only be used in conjuction with the default TokenLookup value. "Bearer"
KeySetURL(deprecated) string KeySetURL location of JSON file with signing keys. ""
KeySetURLs string KeySetURL locations of JSON file with signing keys. ""
KeyRefreshSuccessHandler func(j *KeySet) KeyRefreshSuccessHandler defines a function which is executed for a valid refresh of signing keys. nil
KeyRefreshErrorHandler func(j *KeySet, err error) KeyRefreshErrorHandler defines a function which is executed for an invalid refresh of signing keys. nil
KeyRefreshInterval *time.Duration KeyRefreshInterval is the duration to refresh the JWKs in the background via a new HTTP request. nil
KeyRefreshRateLimit *time.Duration KeyRefreshRateLimit limits the rate at which refresh requests are granted. nil
KeyRefreshTimeout *time.Duration KeyRefreshTimeout is the duration for the context used to create the HTTP request for a refresh of the JWKs. 1min
KeyRefreshUnknownKID bool KeyRefreshUnknownKID indicates that the JWKs refresh request will occur every time a kid that isn't cached is seen. false
KeyFunc func() jwt.Keyfunc KeyFunc defines a user-defined function that supplies the public key for a token validation. jwtKeyFunc
HS256 Example
package main

import (
	"time"

	"github.com/gofiber/fiber/v2"

	jwtware "github.com/gofiber/jwt/v3"
	"github.com/golang-jwt/jwt/v4"
)

func main() {
	app := fiber.New()

	// Login route
	app.Post("/login", login)

	// Unauthenticated route
	app.Get("/", accessible)

	// JWT Middleware
	app.Use(jwtware.New(jwtware.Config{
		SigningKey: []byte("secret"),
	}))

	// Restricted Routes
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

func login(c *fiber.Ctx) error {
	user := c.FormValue("user")
	pass := c.FormValue("pass")

	// Throws Unauthorized error
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	// Create the Claims
	claims := jwt.MapClaims{
		"name":  "John Doe",
		"admin": true,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// Create token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Generate encoded token and send it as response.
	t, err := token.SignedString([]byte("secret"))
	if err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(fiber.Map{"token": t})
}

func accessible(c *fiber.Ctx) error {
	return c.SendString("Accessible")
}

func restricted(c *fiber.Ctx) error {
	user := c.Locals("user").(*jwt.Token)
	claims := user.Claims.(jwt.MapClaims)
	name := claims["name"].(string)
	return c.SendString("Welcome " + name)
}

HS256 Test

Login using username and password to retrieve a token.

curl --data "user=john&pass=doe" http://localhost:3000/login

Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
}

Request a restricted resource using the token in Authorization request header.

curl localhost:3000/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"

Response

Welcome John Doe
RS256 Example
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"log"
	"time"

	"github.com/gofiber/fiber/v2"

	jwtware "github.com/gofiber/jwt/v3"
	"github.com/golang-jwt/jwt/v4"
)

var (
	// Obviously, this is just a test example. Do not do this in production.
	// In production, you would have the private key and public key pair generated
	// in advance. NEVER add a private key to any GitHub repo.
	privateKey *rsa.PrivateKey
)

func main() {
	app := fiber.New()

	// Just as a demo, generate a new private/public key pair on each run. See note above.
	rng := rand.Reader
	var err error
	privateKey, err = rsa.GenerateKey(rng, 2048)
	if err != nil {
		log.Fatalf("rsa.GenerateKey: %v", err)
	}

	// Login route
	app.Post("/login", login)

	// Unauthenticated route
	app.Get("/", accessible)

	// JWT Middleware
	app.Use(jwtware.New(jwtware.Config{
		SigningMethod: "RS256",
		SigningKey:    privateKey.Public(),
	}))

	// Restricted Routes
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

func login(c *fiber.Ctx) error {
	user := c.FormValue("user")
	pass := c.FormValue("pass")

	// Throws Unauthorized error
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	// Create the Claims
	claims := jwt.MapClaims{
		"name":  "John Doe",
		"admin": true,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// Create token
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

	// Generate encoded token and send it as response.
	t, err := token.SignedString(privateKey)
	if err != nil {
		log.Printf("token.SignedString: %v", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(fiber.Map{"token": t})
}

func accessible(c *fiber.Ctx) error {
	return c.SendString("Accessible")
}

func restricted(c *fiber.Ctx) error {
	user := c.Locals("user").(*jwt.Token)
	claims := user.Claims.(jwt.MapClaims)
	name := claims["name"].(string)
	return c.SendString("Welcome " + name)
}

RS256 Test

The RS256 is actually identical to the HS256 test above.

JWKs Test

The tests are identical to basic JWT tests above, with exception that KeySetURL(deprecated) or KeySetUrls to valid public keys collection in JSON format should be supplied.

Custom KeyFunc example

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.

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. Default to an internal implementation verifying the signing algorithm and selecting the proper key.

package main

import (
	"fmt"
  "github.com/gofiber/fiber/v2"

  jwtware "github.com/gofiber/jwt/v3"
  "github.com/golang-jwt/jwt/v4"
)

func main() {
	app := fiber.New()

	app.Use(jwtware.New(jwtware.Config{
		KeyFunc: customKeyFunc(),
	}))

	app.Get("/ok", func(c *fiber.Ctx) error {
		return c.SendString("OK")
	})
}

func customKeyFunc() jwt.Keyfunc {
	return func(t *jwt.Token) (interface{}, error) {
		// Always check the signing method
		if t.Method.Alg() != jwtware.HS256 {
			return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"])
		}

		// TODO custom implementation of loading signing key like from a database
    signingKey := "secret"

		return []byte(signingKey), nil
	}
}

Documentation

Index

Constants

View Source
const (
	// HS256 represents a public cryptography key generated by a 256 bit HMAC algorithm.
	HS256 = "HS256"

	// HS384 represents a public cryptography key generated by a 384 bit HMAC algorithm.
	HS384 = "HS384"

	// HS512 represents a public cryptography key generated by a 512 bit HMAC algorithm.
	HS512 = "HS512"

	// ES256 represents a public cryptography key generated by a 256 bit ECDSA algorithm.
	ES256 = "ES256"

	// ES384 represents a public cryptography key generated by a 384 bit ECDSA algorithm.
	ES384 = "ES384"

	// ES512 represents a public cryptography key generated by a 512 bit ECDSA algorithm.
	ES512 = "ES512"

	// P256 represents a cryptographic elliptical curve type.
	P256 = "P-256"

	// P384 represents a cryptographic elliptical curve type.
	P384 = "P-384"

	// P521 represents a cryptographic elliptical curve type.
	P521 = "P-521"

	// RS256 represents a public cryptography key generated by a 256 bit RSA algorithm.
	RS256 = "RS256"

	// RS384 represents a public cryptography key generated by a 384 bit RSA algorithm.
	RS384 = "RS384"

	// RS512 represents a public cryptography key generated by a 512 bit RSA algorithm.
	RS512 = "RS512"

	// PS256 represents a public cryptography key generated by a 256 bit RSA algorithm.
	PS256 = "PS256"

	// PS384 represents a public cryptography key generated by a 384 bit RSA algorithm.
	PS384 = "PS384"

	// PS512 represents a public cryptography key generated by a 512 bit RSA algorithm.
	PS512 = "PS512"
)

Variables

This section is empty.

Functions

func New

func New(config ...Config) fiber.Handler

New ...

Types

type Config

type Config struct {
	// Filter defines a function to skip middleware.
	// Optional. Default: nil
	Filter func(*fiber.Ctx) bool

	// SuccessHandler defines a function which is executed for a valid token.
	// Optional. Default: nil
	SuccessHandler fiber.Handler

	// ErrorHandler defines a function which is executed for an invalid token.
	// It may be used to define a custom JWT error.
	// Optional. Default: 401 Invalid or expired JWT
	ErrorHandler fiber.ErrorHandler

	// Signing key to validate token. Used as fallback if SigningKeys has length 0.
	// Required. This, SigningKeys or KeySetUrl.
	SigningKey interface{}

	// Map of signing keys to validate token with kid field usage.
	// Required. This, SigningKey or KeySetUrl(deprecated) or KeySetUrls.
	SigningKeys map[string]interface{}

	// URL where set of private keys could be downloaded.
	// Required. This, SigningKey or SigningKeys or KeySetURLs
	// Deprecated, use KeySetURLs
	KeySetURL string

	// URLs where set of private keys could be downloaded.
	// Required. This, SigningKey or SigningKeys or KeySetURL(deprecated)
	// duplicate key entries are overwritten as encountered across urls
	KeySetURLs []string

	// KeyRefreshSuccessHandler defines a function which is executed on successful refresh of key set.
	// Optional. Default: nil
	KeyRefreshSuccessHandler KeyRefreshSuccessHandler

	// KeyRefreshErrorHandler defines a function which is executed for refresh key set failure.
	// Optional. Default: nil
	KeyRefreshErrorHandler KeyRefreshErrorHandler

	// KeyRefreshInterval is the duration to refresh the JWKs in the background via a new HTTP request. If this is not nil,
	// then a background refresh will be requested in a separate goroutine at this interval until the JWKs method
	// EndBackground is called.
	// Optional. If set, the value will be used only if `KeySetUrl`(deprecated) or `KeySetUrls` is also present
	KeyRefreshInterval *time.Duration

	// KeyRefreshRateLimit limits the rate at which refresh requests are granted. Only one refresh request can be queued
	// at a time any refresh requests received while there is already a queue are ignored. It does not make sense to
	// have RefreshInterval's value shorter than this.
	// Optional. If set, the value will be used only if `KeySetUrl`(deprecated) or `KeySetUrls` is also present
	KeyRefreshRateLimit *time.Duration

	// KeyRefreshTimeout is the duration for the context used to create the HTTP request for a refresh of the JWKs. This
	// defaults to one minute. This is only effectual if RefreshInterval is not nil.
	// Optional. If set, the value will be used only if `KeySetUrl`(deprecated) or `KeySetUrls` is also present
	KeyRefreshTimeout *time.Duration

	// KeyRefreshUnknownKID indicates that the JWKs refresh request will occur every time a kid that isn't cached is seen.
	// Without specifying a RefreshInterval a malicious client could self-sign X JWTs, send them to this service,
	// then cause potentially high network usage proportional to X.
	// Optional. If set, the value will be used only if `KeySetUrl`(deprecated) or `KeySetUrls` is also present
	KeyRefreshUnknownKID *bool

	// Signing method, used to check token signing method.
	// Optional. Default: "HS256".
	// Possible values: "HS256", "HS384", "HS512", "ES256", "ES384", "ES512", "RS256", "RS384", "RS512"
	SigningMethod string

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

	// Claims are extendable claims data defining token content.
	// Optional. Default value jwt.MapClaims
	Claims jwt.Claims

	// TokenLookup is a string in the form of "<source>:<name>" that is used
	// to extract token from the request.
	// Optional. Default value "header:Authorization".
	// Possible values:
	// - "header:<name>"
	// - "query:<name>"
	// - "param:<name>"
	// - "cookie:<name>"
	TokenLookup string

	// AuthScheme to be used in the Authorization header.
	// Optional. Default: "Bearer".
	AuthScheme 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.
	//
	// 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.
	// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
	KeyFunc jwt.Keyfunc
}

Config defines the config for JWT middleware

type KeyRefreshErrorHandler added in v3.1.0

type KeyRefreshErrorHandler func(j *KeySet, err error)

KeyRefreshErrorHandler is a function signature that consumes a set of signing key set and an error. Presence of original signing key set allows to update configuration or stop background refresh.

type KeyRefreshSuccessHandler added in v3.1.0

type KeyRefreshSuccessHandler func(j *KeySet)

KeyRefreshSuccessHandler is a function signature that consumes a set of signing key set. Presence of original signing key set allows to update configuration or stop background refresh.

type KeySet added in v3.1.0

type KeySet struct {
	Keys   map[string]*rawJWK
	Config *Config
	// contains filtered or unexported fields
}

KeySet represents a JSON Web Key Set.

func (*KeySet) StopRefreshing added in v3.1.0

func (j *KeySet) StopRefreshing()

StopRefreshing ends the background goroutine to update the JWKs. It can only happen once and is only effective if the JWKs has a background goroutine refreshing the JWKs keys.

Jump to

Keyboard shortcuts

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