290 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package options
 | 
						|
 | 
						|
import (
 | 
						|
	"net/http"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// ClaimsContextKeyName is the type for they key value used to pass claims using request context.
 | 
						|
// Using separate type because of the following: https://staticcheck.io/docs/checks#SA1029
 | 
						|
type ClaimsContextKeyName string
 | 
						|
 | 
						|
// ErrosContextKeyName holds the key to pass errors under.
 | 
						|
type ErrorsContextKeyName string
 | 
						|
 | 
						|
// DefaultClaimsContextKeyName is of type ClaimsContextKeyName and defaults to "claims"
 | 
						|
const DefaultClaimsContextKeyName ClaimsContextKeyName = "claims"
 | 
						|
 | 
						|
// DefaultErrorsContextKeyName is of type ErrorsContextKeyName and defaults to "oidcerrors"
 | 
						|
const DefaultErrorsContextKeyName ErrorsContextKeyName = "oidcerrors"
 | 
						|
 | 
						|
// ErrorHandler is called by the middleware if not nil
 | 
						|
type ErrorHandler func(description ErrorDescription, err error)
 | 
						|
 | 
						|
// ErrorDescription is used to pass the description of the error to ErrorHandler
 | 
						|
type ErrorDescription string
 | 
						|
 | 
						|
const (
 | 
						|
	// GetTokenErrorDescription is returned to ErrorHandler if the middleware is unable to get a token from the request
 | 
						|
	GetTokenErrorDescription ErrorDescription = "unable to get token string"
 | 
						|
	// ParseTokenErrorDescription is returned to ErrorHandler if the middleware is unable to parse the token extracted from the request
 | 
						|
	ParseTokenErrorDescription ErrorDescription = "unable to parse token string"
 | 
						|
	// ConvertTokenErrorDescription is returned to ErrorHandler if the middleware is unable to convert the token to a map
 | 
						|
	ConvertTokenErrorDescription ErrorDescription = "unable to convert token to map"
 | 
						|
)
 | 
						|
 | 
						|
// Options defines the options for OIDC Middleware.
 | 
						|
type Options struct {
 | 
						|
	Issuer                     string
 | 
						|
	DiscoveryUri               string
 | 
						|
	JwksUri                    string
 | 
						|
	JwksFetchTimeout           time.Duration
 | 
						|
	JwksRateLimit              uint
 | 
						|
	FallbackSignatureAlgorithm string
 | 
						|
	AllowedTokenDrift          time.Duration
 | 
						|
	LazyLoadJwks               bool
 | 
						|
	RequiredTokenType          string
 | 
						|
	RequiredAudience           string
 | 
						|
	RequiredClaims             map[string]interface{}
 | 
						|
	DisableKeyID               bool
 | 
						|
	HttpClient                 *http.Client
 | 
						|
	TokenString                [][]TokenStringOption
 | 
						|
	ClaimsContextKeyName       ClaimsContextKeyName
 | 
						|
	ErrorHandler               ErrorHandler
 | 
						|
	Permissive                 bool
 | 
						|
	ErrorsContextKeyName       ErrorsContextKeyName
 | 
						|
}
 | 
						|
 | 
						|
// New takes Option setters and returns an Options pointer.
 | 
						|
// Mainly used by the internal functions and most likely not
 | 
						|
// needed by any external application using this library.
 | 
						|
func New(setters ...Option) *Options {
 | 
						|
	opts := &Options{
 | 
						|
		JwksFetchTimeout:     5 * time.Second,
 | 
						|
		JwksRateLimit:        1,
 | 
						|
		AllowedTokenDrift:    10 * time.Second,
 | 
						|
		HttpClient:           http.DefaultClient,
 | 
						|
		ClaimsContextKeyName: DefaultClaimsContextKeyName,
 | 
						|
		Permissive:           false,
 | 
						|
		ErrorsContextKeyName: DefaultErrorsContextKeyName,
 | 
						|
	}
 | 
						|
 | 
						|
	for _, setter := range setters {
 | 
						|
		setter(opts)
 | 
						|
	}
 | 
						|
 | 
						|
	return opts
 | 
						|
}
 | 
						|
 | 
						|
// Option returns a function that modifies an Options pointer.
 | 
						|
type Option func(*Options)
 | 
						|
 | 
						|
// WithIssuer sets the Issuer parameter for Options.
 | 
						|
// Issuer is the authority that issues the tokens
 | 
						|
func WithIssuer(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.Issuer = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithDiscoveryUri sets the Issuer parameter for an Options pointer.
 | 
						|
// DiscoveryUri is where the `jwks_uri` will be grabbed
 | 
						|
// Defaults to `fmt.Sprintf("%s/.well-known/openid-configuration", strings.TrimSuffix(issuer, "/"))`
 | 
						|
func WithDiscoveryUri(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.DiscoveryUri = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithJwksUri sets the JwksUri parameter for an Options pointer.
 | 
						|
// JwksUri is used to download the public key(s)
 | 
						|
// Defaults to the `jwks_uri` from the response of DiscoveryUri
 | 
						|
func WithJwksUri(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.JwksUri = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithJwksFetchTimeout sets the JwksFetchTimeout parameter for an Options pointer.
 | 
						|
// JwksFetchTimeout sets the context timeout when downloading the jwks
 | 
						|
// Defaults to 5 seconds
 | 
						|
func WithJwksFetchTimeout(opt time.Duration) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.JwksFetchTimeout = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithJwksRateLimit sets the JwksFetchTimeout parameter for an Options pointer.
 | 
						|
// JwksRateLimit takes an uint and makes sure that the jwks will at a maximum
 | 
						|
// be requested these many times per second.
 | 
						|
// Defaults to 1 (Request Per Second)
 | 
						|
// Please observe: Requests that force update of jwks (like wrong keyID) will be rate limited
 | 
						|
func WithJwksRateLimit(opt uint) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.JwksRateLimit = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithFallbackSignatureAlgorithm sets the FallbackSignatureAlgorithm parameter for an Options pointer.
 | 
						|
// FallbackSignatureAlgorithm needs to be used when the jwks doesn't contain the alg key.
 | 
						|
// If not specified and jwks doesn't contain alg key, will default to:
 | 
						|
// - RS256 for key type (kty) RSA
 | 
						|
// - ES256 for key type (kty) EC
 | 
						|
//
 | 
						|
// When specified and jwks contains alg key, alg key from jwks will be used.
 | 
						|
//
 | 
						|
// Example values (one of them): RS256 RS384 RS512 ES256 ES384 ES512
 | 
						|
func WithFallbackSignatureAlgorithm(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.FallbackSignatureAlgorithm = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithAllowedTokenDrift sets the AllowedTokenDrift parameter for an Options pointer.
 | 
						|
// AllowedTokenDrift adds the duration to the token expiration to allow
 | 
						|
// for time drift between parties.
 | 
						|
// Defaults to 10 seconds
 | 
						|
func WithAllowedTokenDrift(opt time.Duration) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.AllowedTokenDrift = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithLazyLoadJwks sets the LazyLoadJwks parameter for an Options pointer.
 | 
						|
// LazyLoadJwks makes it possible to use OIDC Discovery without being
 | 
						|
// able to load the keys at startup.
 | 
						|
// Default setting is disabled.
 | 
						|
// Please observe: If enabled, it will always load even though settings
 | 
						|
// may be wrong / not working.
 | 
						|
func WithLazyLoadJwks(opt bool) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.LazyLoadJwks = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithRequiredTokenType sets the RequiredTokenType parameter for an Options pointer.
 | 
						|
// RequiredTokenType is used if only specific tokens should be allowed.
 | 
						|
// Default is empty string `""` and means all token types are allowed.
 | 
						|
// Use case could be to configure this if the TokenType (set in the header of the JWT)
 | 
						|
// should be `JWT` or maybe even `JWT+AT` to differentiate between access tokens and
 | 
						|
// id tokens. Not all providers support or use this.
 | 
						|
func WithRequiredTokenType(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.RequiredTokenType = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithRequiredAudience sets the RequiredAudience parameter for an Options pointer.
 | 
						|
// RequiredAudience is used to require a specific Audience `aud` in the claims.
 | 
						|
// Defaults to empty string `""` and means all audiences are allowed.
 | 
						|
func WithRequiredAudience(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.RequiredAudience = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithRequiredClaims sets the RequiredClaims parameter for an Options pointer.
 | 
						|
// RequiredClaims is used to require specific claims in the token
 | 
						|
// Defaults to empty map (nil) and won't check for anything else
 | 
						|
// Works with primitive types, slices and maps.
 | 
						|
// Please observe: slices and strings checks that the token contains it, but more is allowed.
 | 
						|
// Required claim []string{"bar"} matches token []string{"foo", "bar", "baz"}
 | 
						|
// Required claim map[string]string{{"foo": "bar"}} matches token map[string]string{{"a": "b"},{"foo": "bar"},{"c": "d"}}
 | 
						|
//
 | 
						|
// Example:
 | 
						|
//
 | 
						|
// ```go
 | 
						|
// map[string]interface{}{
 | 
						|
// 	"foo": "bar",
 | 
						|
// 	"bar": 1337,
 | 
						|
// 	"baz": []string{"bar"},
 | 
						|
// 	"oof": []map[string]string{
 | 
						|
// 		{"bar": "baz"},
 | 
						|
// 	},
 | 
						|
// },
 | 
						|
// ```
 | 
						|
func WithRequiredClaims(opt map[string]interface{}) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.RequiredClaims = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithDisableKeyID sets the DisableKeyID parameter for an Options pointer.
 | 
						|
// DisableKeyID adjusts if a KeyID needs to be extracted from the token or not
 | 
						|
// Defaults to false and means KeyID is required to be present in both the jwks and token
 | 
						|
// The OIDC specification doesn't require KeyID if there's only one key in the jwks:
 | 
						|
// https://openid.net/specs/openid-connect-core-1_0.html#Signing
 | 
						|
//
 | 
						|
// This also means that if enabled, refresh of the jwks will be done if the token can't be
 | 
						|
// validated due to invalid key. The JWKS fetch will fail if there's more than one key present.
 | 
						|
func WithDisableKeyID(opt bool) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.DisableKeyID = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithHttpClient sets the HttpClient parameter for an Options pointer.
 | 
						|
// HttpClient takes a *http.Client for external calls
 | 
						|
// Defaults to http.DefaultClient
 | 
						|
func WithHttpClient(opt *http.Client) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.HttpClient = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithTokenString sets the TokenString parameter for an Options pointer.
 | 
						|
// TokenString makes it possible to configure how the JWT token should be extracted from
 | 
						|
// an http header. Not supported by Echo JWT and will be ignored if used by it.
 | 
						|
// Defaults to: 'Authorization: Bearer JWT'
 | 
						|
func WithTokenString(setters ...TokenStringOption) Option {
 | 
						|
	var tokenString []TokenStringOption
 | 
						|
	tokenString = append(tokenString, setters...)
 | 
						|
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.TokenString = append(opts.TokenString, tokenString)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithClaimsContextKeyName sets the ClaimsContextKeyName parameter for an Options pointer.
 | 
						|
// ClaimsContextKeyName is the name of key that will be used to pass claims using request context.
 | 
						|
// Not supported by Echo JWT and will be ignored if used by it.
 | 
						|
//
 | 
						|
// Important note: If you change this using `options.WithClaimsContextKeyName("foo")`, then
 | 
						|
// you also need to use it like this:
 | 
						|
// `claims, ok := r.Context().Value(options.ClaimsContextKeyName("foo")).(map[string]interface{})`
 | 
						|
//
 | 
						|
// Default: `options.DefaultClaimsContextKeyName`
 | 
						|
// Used like this: ``claims, ok := r.Context().Value(options.DefaultClaimsContextKeyName).(map[string]interface{})``
 | 
						|
//
 | 
						|
// When used with gin, it is converted to normal string - by default:
 | 
						|
// `claimsValue, found := c.Get("claims")`
 | 
						|
func WithClaimsContextKeyName(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.ClaimsContextKeyName = ClaimsContextKeyName(opt)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithErrorHandler sets the ErrorHandler parameter for an Options pointer.
 | 
						|
// You can pass a function to run custom logic on errors, logging as an example.
 | 
						|
// Defaults to nil
 | 
						|
func WithErrorHandler(opt ErrorHandler) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.ErrorHandler = opt
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// IsPermissive allows incoming requests to pass even if the client does not provide a token.
 | 
						|
func IsPermissive() Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.Permissive = true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithErrorsContextKeyName sets the ErrorsContextKeyName parameter for an Options pointer.
 | 
						|
// ErrorsContextKeyName is the name of the key that will be used to pass errors using the request context.
 | 
						|
func WithErrorsContextKeyName(opt string) Option {
 | 
						|
	return func(opts *Options) {
 | 
						|
		opts.ErrorsContextKeyName = ErrorsContextKeyName(opt)
 | 
						|
	}
 | 
						|
}
 |