jwt

package module
v1.7.0 Latest Latest
Warning

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

Go to latest
Published: Mar 16, 2021 License: MIT Imports: 7 Imported by: 0

README

test Go Reference Go Report Card codecov

jwtcache-go

jwtcache-go is a small wrapper lib for caching JWTs.

The initial purpose for developing this lib was caching tokens that were issued for service2service communication.

An exemplary use case for this are Keycloak service accounts.

Download:

go get github.com/kernle32dll/jwtcache-go

Detailed documentation can be found on pkg.go.dev.

Basic usage

TL;DR:

package main

import (
	"github.com/kernle32dll/jwtcache-go"

	"context"
	"log"
)

func main() {
	cache := jwt.NewCache(
		jwt.Name("my cache"),
		jwt.TokenFunction(func(ctx context.Context) (string, error) {
			// ... actually acquire the token, and return it here
			return "someToken", nil
		}),
	)

	token, err := cache.EnsureToken(context.Background())
	if err != nil {
		// oh no...
	}

	log.Printf("got token: %s", token)
}

First, you have to instantiate a jwt.Cache. This is done via jwt.NewCache (which takes option style parameters).

The most important option parameter being the jwt.TokenFunction, which provides a token to the cache if required (either no token is cached yet, or the existing token is expired). Look at pkg.go.dev for other parameters.

With the cache instantiated, you can call the EnsureToken function to start transparently using the cache. Internally, the cache will then use the jwt.TokenFunction to fetch a new token, and cache it afterwards for the validity time provided by the token. Subsequent calls to EnsureToken will then return this cached token, till it expires.

token, err := jwt.EnsureToken(context.Background())

Implementation detail: The validity check is done via the exp claim of the JWT. If it is not set, the token is never cached. However, the token is still passed trough (and a warning is logged).

JWT parser customization

Per default, jwtcache-go ignores JWTs it cannot parse, but still returns them from the token function. However, it is possible to change this via the jwt.RejectUnparsable(true) option.

To make the most of this, you can also adjust the underlying JWT parser, with the jwt.ParseOptions(...) function. For example, you can easily enable signature verification like so:

package main

import (
	"github.com/kernle32dll/jwtcache-go"
	"github.com/lestrrat-go/jwx/jwa"
	jwtParser "github.com/lestrrat-go/jwx/jwt"

	"context"
)

func main() {

	cache := jwt.NewCache(
		jwt.Name("signed cache"),
		jwt.TokenFunction(func(ctx context.Context) (string, error) {
			// ... actually acquire the token, and return it here
			return "someToken", nil
		}),
		// !! HMAC is shown for simplicity - use RSA, ECDSA or EdDSA instead !!
		jwt.ParseOptions(jwtParser.WithVerify(jwa.HS256, []byte("supersecretpassphrase"))),
		jwt.RejectUnparsable(true) // Propagate parsing errors, instead of swallowing them
	)

	_, err := cache.EnsureToken(context.Background())
	if err != nil {
		// this will always happen, as "someToken" is not actually a valid HMAC signed JWT!
	}
}

Advanced usage

In addition to the jwt.Cache, this lib has an additional trick up its sleeve in the form of jwt.CacheMap.

A jwt.CacheMap behaves identically to a jwt.Cache, with the difference that - as the name suggest - the cache is actually a map compromised of several caches.

package main

import (
	"github.com/kernle32dll/jwtcache-go"
	"github.com/lestrrat-go/jwx/jwa"

	"context"
	"log"
)

func main() {
	tenantCache := jwt.NewCacheMap(
		jwt.MapName("my cache map"),
		jwt.MapTokenFunction(func(ctx context.Context, tenantUUID string) (string, error) {
			// ... actually acquire the token, and return it here
			return "some-token", nil
		}),
	)

	token, err := tenantCache.EnsureToken(context.Background(), "d1851563-c529-42d9-994b-6b996ec4b605")
	if err != nil {
		// oh no...
	}

	log.Printf("got token for tenant: %s", token)
}

The use-case jwt.CacheMap was implemented for was multi-tenant applications, which need to independently cache JWTs per tenant (a good cache key might be the UUID of a tenant, for example).

Implementation detail: The underlying map is concurrency-safe, and lazily initialized.

Compatibility

jwt-cache-go is automatically tested against Go 1.15.X and 1.16.X.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotImplemented is the default behavior for the cache, if the
	// token function is not supplied.
	ErrNotImplemented = errors.New("not implemented")
)

Functions

This section is empty.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache is a simple caching implementation to reuse JWTs till they expire.

func NewCache

func NewCache(opts ...Option) *Cache

NewCache returns a new JWT cache.

func (*Cache) EnsureToken

func (jwtCache *Cache) EnsureToken(ctx context.Context) (string, error)

EnsureToken returns either the cached token if existing and still valid, or calls the internal token function to fetch a new token. If an error occurs in the latter case, it is passed trough.

type CacheMap

type CacheMap struct {
	// contains filtered or unexported fields
}

CacheMap is a mapped implementation of Cache, which allows storing JWTs by a key (for example a tenant UUID). As a bonus, the map is concurrency safe.

func NewCacheMap

func NewCacheMap(opts ...MapOption) *CacheMap

NewCacheMap returns a new mapped JWT cache.

func (*CacheMap) EnsureToken

func (cacheMap *CacheMap) EnsureToken(ctx context.Context, key string) (string, error)

EnsureToken returns either the cached token if existing and still valid, or calls the internal token function to fetch a new token. If an error occurs in the latter case, it is passed trough.

type LoggerContract added in v1.2.0

type LoggerContract interface {
	Infof(format string, args ...interface{})
	Debugf(format string, args ...interface{})
}

LoggerContract defines the logging methods required by the cache. This allows to use different kinds of logging libraries.

type MapOption

type MapOption func(*mapConfig)

MapOption represents an option for the mapped cache.

func MapHeadroom

func MapHeadroom(headroom time.Duration) MapOption

MapHeadroom sets the headroom on how much earlier the cached tokens should be considered expired. The default is 1 second.

func MapLogger

func MapLogger(logger LoggerContract) MapOption

MapLogger sets the logger to be used. The default is the logrus default logger.

func MapName

func MapName(name string) MapOption

MapName sets the name of the cache. The default is an empty string.

func MapParseOptions added in v1.6.0

func MapParseOptions(parseOptions ...jwt.ParseOption) MapOption

MapParseOptions set the parse options which are used to parse a JWT. This can be used to implement signature validation for example.

The default empty.

func MapRejectUnparsable added in v1.6.0

func MapRejectUnparsable(rejectUnparsable bool) MapOption

MapRejectUnparsable sets if the cache should reject (and return the accompanying error) token which are not parsable. Note, unparsable can mean a failed signature check.

The default is false.

func MapTokenFunction

func MapTokenFunction(tokenFunc func(ctx context.Context, key string) (string, error)) MapOption

MapTokenFunction set the function which is called to retrieve a new JWT when required. The default always returns an error with "not implemented".

type Option

type Option func(*config)

Option represents an option for the cache.

func Headroom

func Headroom(headroom time.Duration) Option

Headroom sets the headroom on how much earlier the cached token should be considered expired. The default is 1 second.

func Logger

func Logger(logger LoggerContract) Option

Logger sets the logger to be used. The default is the logrus default logger.

func Name

func Name(name string) Option

Name sets the name of the cache. The default is an empty string.

func ParseOptions added in v1.6.0

func ParseOptions(parseOptions ...jwt.ParseOption) Option

ParseOptions set the parse options which are used to parse a JWT. This can be used to implement signature validation for example.

The default empty.

func RejectUnparsable added in v1.6.0

func RejectUnparsable(rejectUnparsable bool) Option

RejectUnparsable sets if the cache should reject (and return the accompanying error) token which are not parsable. Note, unparsable can mean a failed signature check.

The default is false.

func TokenFunction

func TokenFunction(tokenFunc func(ctx context.Context) (string, error)) Option

TokenFunction set the function which is called to retrieve a new JWT when required. The default always returns an error with "not implemented".

Jump to

Keyboard shortcuts

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