539 lines
19 KiB
Go
539 lines
19 KiB
Go
package jwt
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/lestrrat-go/backoff/v2"
|
|
"github.com/lestrrat-go/jwx/jwa"
|
|
"github.com/lestrrat-go/jwx/jwe"
|
|
"github.com/lestrrat-go/jwx/jwk"
|
|
"github.com/lestrrat-go/jwx/jws"
|
|
"github.com/lestrrat-go/option"
|
|
)
|
|
|
|
type Option = option.Interface
|
|
|
|
// GlobalOption describes an Option that can be passed to `Settings()`.
|
|
type GlobalOption interface {
|
|
Option
|
|
globalOption()
|
|
}
|
|
|
|
type globalOption struct {
|
|
Option
|
|
}
|
|
|
|
func (*globalOption) globalOption() {}
|
|
|
|
// ParseRequestOption describes an Option that can be passed to `ParseRequest()`.
|
|
type ParseRequestOption interface {
|
|
ParseOption
|
|
httpParseOption()
|
|
}
|
|
|
|
type httpParseOption struct {
|
|
ParseOption
|
|
}
|
|
|
|
func (*httpParseOption) httpParseOption() {}
|
|
|
|
// ParseOption describes an Option that can be passed to `Parse()`.
|
|
// ParseOption also implements ReadFileOption, therefore it may be
|
|
// safely pass them to `jwt.ReadFile()`
|
|
type ParseOption interface {
|
|
ReadFileOption
|
|
parseOption()
|
|
}
|
|
|
|
type parseOption struct {
|
|
Option
|
|
}
|
|
|
|
func newParseOption(n interface{}, v interface{}) ParseOption {
|
|
return &parseOption{option.New(n, v)}
|
|
}
|
|
|
|
func (*parseOption) parseOption() {}
|
|
func (*parseOption) readFileOption() {}
|
|
|
|
// SignOption describes an Option that can be passed to Sign() or
|
|
// (jwt.Serializer).Sign
|
|
type SignOption interface {
|
|
Option
|
|
signOption()
|
|
}
|
|
|
|
type signOption struct {
|
|
Option
|
|
}
|
|
|
|
func newSignOption(n interface{}, v interface{}) SignOption {
|
|
return &signOption{option.New(n, v)}
|
|
}
|
|
|
|
func (*signOption) signOption() {}
|
|
|
|
// EncryptOption describes an Option that can be passed to Encrypt() or
|
|
// (jwt.Serializer).Encrypt
|
|
type EncryptOption interface {
|
|
Option
|
|
encryptOption()
|
|
}
|
|
|
|
type encryptOption struct {
|
|
Option
|
|
}
|
|
|
|
func newEncryptOption(n interface{}, v interface{}) EncryptOption {
|
|
return &encryptOption{option.New(n, v)}
|
|
}
|
|
|
|
func (*encryptOption) encryptOption() {}
|
|
|
|
// ValidateOption describes an Option that can be passed to Validate().
|
|
// ValidateOption also implements ParseOption, therefore it may be
|
|
// safely passed to `Parse()` (and thus `jwt.ReadFile()`)
|
|
type ValidateOption interface {
|
|
ParseOption
|
|
validateOption()
|
|
}
|
|
|
|
type validateOption struct {
|
|
ParseOption
|
|
}
|
|
|
|
func newValidateOption(n interface{}, v interface{}) ValidateOption {
|
|
return &validateOption{newParseOption(n, v)}
|
|
}
|
|
|
|
func (*validateOption) validateOption() {}
|
|
|
|
type identAcceptableSkew struct{}
|
|
type identClock struct{}
|
|
type identContext struct{}
|
|
type identDecrypt struct{}
|
|
type identDefault struct{}
|
|
type identFlattenAudience struct{}
|
|
type identInferAlgorithmFromKey struct{}
|
|
type identJweHeaders struct{}
|
|
type identJwsHeaders struct{}
|
|
type identKeySet struct{}
|
|
type identKeySetProvider struct{}
|
|
type identPedantic struct{}
|
|
type identValidator struct{}
|
|
type identToken struct{}
|
|
type identTypedClaim struct{}
|
|
type identValidate struct{}
|
|
type identVerify struct{}
|
|
type identVerifyAuto struct{}
|
|
type identFetchBackoff struct{}
|
|
type identFetchWhitelist struct{}
|
|
type identHTTPClient struct{}
|
|
type identJWKSetFetcher struct{}
|
|
|
|
type identHeaderKey struct{}
|
|
type identFormKey struct{}
|
|
|
|
type VerifyParameters interface {
|
|
Algorithm() jwa.SignatureAlgorithm
|
|
Key() interface{}
|
|
}
|
|
|
|
type verifyParams struct {
|
|
alg jwa.SignatureAlgorithm
|
|
key interface{}
|
|
}
|
|
|
|
func (p *verifyParams) Algorithm() jwa.SignatureAlgorithm {
|
|
return p.alg
|
|
}
|
|
|
|
func (p *verifyParams) Key() interface{} {
|
|
return p.key
|
|
}
|
|
|
|
// WithVerify forces the Parse method to verify the JWT message
|
|
// using the given key. XXX Should have been named something like
|
|
// WithVerificationKey
|
|
func WithVerify(alg jwa.SignatureAlgorithm, key interface{}) ParseOption {
|
|
return newParseOption(identVerify{}, &verifyParams{
|
|
alg: alg,
|
|
key: key,
|
|
})
|
|
}
|
|
|
|
// WithKeySet forces the Parse method to verify the JWT message
|
|
// using one of the keys in the given key set.
|
|
//
|
|
// The key and the JWT MUST have a proper `kid` field set.
|
|
// The key to use for signature verification is chosen by matching
|
|
// the Key ID of the JWT and the ID of the given key set.
|
|
//
|
|
// When using this option, keys MUST have a proper 'alg' field
|
|
// set. This is because we need to know the exact algorithm that
|
|
// you (the user) wants to use to verify the token. We do NOT
|
|
// trust the token's headers, because they can easily be tampered with.
|
|
//
|
|
// However, there _is_ a workaround if you do understand the risks
|
|
// of allowing a library to automatically choose a signature verification strategy,
|
|
// and you do not mind the verification process having to possibly
|
|
// attempt using multiple times before succeeding to verify. See
|
|
// `jwt.InferAlgorithmFromKey` option
|
|
//
|
|
// If you have only one key in the set, and are sure you want to
|
|
// use that key, you can use the `jwt.WithDefaultKey` option.
|
|
//
|
|
// If provided with WithKeySetProvider(), this option takes precedence.
|
|
func WithKeySet(set jwk.Set) ParseOption {
|
|
return newParseOption(identKeySet{}, set)
|
|
}
|
|
|
|
// UseDefaultKey is used in conjunction with the option WithKeySet
|
|
// to instruct the Parse method to default to the single key in a key
|
|
// set when no Key ID is included in the JWT. If the key set contains
|
|
// multiple keys then the default behavior is unchanged -- that is,
|
|
// the since we can't determine the key to use, it returns an error.
|
|
func UseDefaultKey(value bool) ParseOption {
|
|
return newParseOption(identDefault{}, value)
|
|
}
|
|
|
|
// WithToken specifies the token instance that is used when parsing
|
|
// JWT tokens.
|
|
func WithToken(t Token) ParseOption {
|
|
return newParseOption(identToken{}, t)
|
|
}
|
|
|
|
// WithHeaders is passed to `jwt.Sign()` function, to allow specifying arbitrary
|
|
// header values to be included in the header section of the jws message
|
|
//
|
|
// This option will be deprecated in the next major version. Use
|
|
// jwt.WithJwsHeaders() instead.
|
|
func WithHeaders(hdrs jws.Headers) SignOption {
|
|
return WithJwsHeaders(hdrs)
|
|
}
|
|
|
|
// WithJwsHeaders is passed to `jwt.Sign()` function or
|
|
// "jwt.Serializer".Sign() method, to allow specifying arbitrary
|
|
// header values to be included in the header section of the JWE message
|
|
func WithJwsHeaders(hdrs jws.Headers) SignOption {
|
|
return newSignOption(identJwsHeaders{}, hdrs)
|
|
}
|
|
|
|
// WithJweHeaders is passed to "jwt.Serializer".Encrypt() method to allow
|
|
// specifying arbitrary header values to be included in the protected header
|
|
// of the JWE message
|
|
func WithJweHeaders(hdrs jwe.Headers) EncryptOption {
|
|
return newEncryptOption(identJweHeaders{}, hdrs)
|
|
}
|
|
|
|
// WithValidate is passed to `Parse()` method to denote that the
|
|
// validation of the JWT token should be performed after a successful
|
|
// parsing of the incoming payload.
|
|
func WithValidate(b bool) ParseOption {
|
|
return newParseOption(identValidate{}, b)
|
|
}
|
|
|
|
// WithClock specifies the `Clock` to be used when verifying
|
|
// claims exp and nbf.
|
|
func WithClock(c Clock) ValidateOption {
|
|
return newValidateOption(identClock{}, c)
|
|
}
|
|
|
|
// WithAcceptableSkew specifies the duration in which exp and nbf
|
|
// claims may differ by. This value should be positive
|
|
func WithAcceptableSkew(dur time.Duration) ValidateOption {
|
|
return newValidateOption(identAcceptableSkew{}, dur)
|
|
}
|
|
|
|
// WithIssuer specifies that expected issuer value. If not specified,
|
|
// the value of issuer is not verified at all.
|
|
func WithIssuer(s string) ValidateOption {
|
|
return WithValidator(ClaimValueIs(IssuerKey, s))
|
|
}
|
|
|
|
// WithSubject specifies that expected subject value. If not specified,
|
|
// the value of subject is not verified at all.
|
|
func WithSubject(s string) ValidateOption {
|
|
return WithValidator(ClaimValueIs(SubjectKey, s))
|
|
}
|
|
|
|
// WithJwtID specifies that expected jti value. If not specified,
|
|
// the value of jti is not verified at all.
|
|
func WithJwtID(s string) ValidateOption {
|
|
return WithValidator(ClaimValueIs(JwtIDKey, s))
|
|
}
|
|
|
|
// WithAudience specifies that expected audience value.
|
|
// `Validate()` will return true if one of the values in the `aud` element
|
|
// matches this value. If not specified, the value of issuer is not
|
|
// verified at all.
|
|
func WithAudience(s string) ValidateOption {
|
|
return WithValidator(ClaimContainsString(AudienceKey, s))
|
|
}
|
|
|
|
// WithClaimValue specifies the expected value for a given claim
|
|
func WithClaimValue(name string, v interface{}) ValidateOption {
|
|
return WithValidator(ClaimValueIs(name, v))
|
|
}
|
|
|
|
// WithHeaderKey is used to specify header keys to search for tokens.
|
|
//
|
|
// While the type system allows this option to be passed to jwt.Parse() directly,
|
|
// doing so will have no effect. Only use it for HTTP request parsing functions
|
|
func WithHeaderKey(v string) ParseRequestOption {
|
|
return &httpParseOption{newParseOption(identHeaderKey{}, v)}
|
|
}
|
|
|
|
// WithFormKey is used to specify header keys to search for tokens.
|
|
//
|
|
// While the type system allows this option to be passed to jwt.Parse() directly,
|
|
// doing so will have no effect. Only use it for HTTP request parsing functions
|
|
func WithFormKey(v string) ParseRequestOption {
|
|
return &httpParseOption{newParseOption(identFormKey{}, v)}
|
|
}
|
|
|
|
// WithFlattenAudience specifies if the "aud" claim should be flattened
|
|
// to a single string upon the token being serialized to JSON.
|
|
//
|
|
// This is sometimes important when a JWT consumer does not understand that
|
|
// the "aud" claim can actually take the form of an array of strings.
|
|
//
|
|
// The default value is `false`, which means that "aud" claims are always
|
|
// rendered as a arrays of strings. This setting has a global effect,
|
|
// and will change the behavior for all JWT serialization.
|
|
func WithFlattenAudience(v bool) GlobalOption {
|
|
return &globalOption{option.New(identFlattenAudience{}, v)}
|
|
}
|
|
|
|
type claimPair struct {
|
|
Name string
|
|
Value interface{}
|
|
}
|
|
|
|
// WithTypedClaim allows a private claim to be parsed into the object type of
|
|
// your choice. It works much like the RegisterCustomField, but the effect
|
|
// is only applicable to the jwt.Parse function call which receives this option.
|
|
//
|
|
// While this can be extremely useful, this option should be used with caution:
|
|
// There are many caveats that your entire team/user-base needs to be aware of,
|
|
// and therefore in general its use is discouraged. Only use it when you know
|
|
// what you are doing, and you document its use clearly for others.
|
|
//
|
|
// First and foremost, this is a "per-object" option. Meaning that given the same
|
|
// serialized format, it is possible to generate two objects whose internal
|
|
// representations may differ. That is, if you parse one _WITH_ the option,
|
|
// and the other _WITHOUT_, their internal representation may completely differ.
|
|
// This could potentially lead to problems.
|
|
//
|
|
// Second, specifying this option will slightly slow down the decoding process
|
|
// as it needs to consult multiple definitions sources (global and local), so
|
|
// be careful if you are decoding a large number of tokens, as the effects will stack up.
|
|
//
|
|
// Finally, this option will also NOT work unless the tokens themselves support such
|
|
// parsing mechanism. For example, while tokens obtained from `jwt.New()` and
|
|
// `openid.New()` will respect this option, if you provide your own custom
|
|
// token type, it will need to implement the TokenWithDecodeCtx interface.
|
|
func WithTypedClaim(name string, object interface{}) ParseOption {
|
|
return newParseOption(identTypedClaim{}, claimPair{Name: name, Value: object})
|
|
}
|
|
|
|
// WithRequiredClaim specifies that the claim identified the given name
|
|
// must exist in the token. Only the existence of the claim is checked:
|
|
// the actual value associated with that field is not checked.
|
|
func WithRequiredClaim(name string) ValidateOption {
|
|
return WithValidator(IsRequired(name))
|
|
}
|
|
|
|
// WithMaxDelta specifies that given two claims `c1` and `c2` that represent time, the difference in
|
|
// time.Duration must be less than equal to the value specified by `d`. If `c1` or `c2` is the
|
|
// empty string, the current time (as computed by `time.Now` or the object passed via
|
|
// `WithClock()`) is used for the comparison.
|
|
//
|
|
// `c1` and `c2` are also assumed to be required, therefore not providing either claim in the
|
|
// token will result in an error.
|
|
//
|
|
// Because there is no way of reliably knowing how to parse private claims, we currently only
|
|
// support `iat`, `exp`, and `nbf` claims.
|
|
//
|
|
// If the empty string is passed to c1 or c2, then the current time (as calculated by time.Now() or
|
|
// the clock object provided via WithClock()) is used.
|
|
//
|
|
// For example, in order to specify that `exp` - `iat` should be less than 10*time.Second, you would write
|
|
//
|
|
// jwt.Validate(token, jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
|
|
//
|
|
// If AcceptableSkew of 2 second is specified, the above will return valid for any value of
|
|
// `exp` - `iat` between 8 (10-2) and 12 (10+2).
|
|
func WithMaxDelta(dur time.Duration, c1, c2 string) ValidateOption {
|
|
return WithValidator(MaxDeltaIs(c1, c2, dur))
|
|
}
|
|
|
|
// WithMinDelta is almost exactly the same as WithMaxDelta, but force validation to fail if
|
|
// the difference between time claims are less than dur.
|
|
//
|
|
// For example, in order to specify that `exp` - `iat` should be greater than 10*time.Second, you would write
|
|
//
|
|
// jwt.Validate(token, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
|
|
//
|
|
// The validation would fail if the difference is less than 10 seconds.
|
|
//
|
|
func WithMinDelta(dur time.Duration, c1, c2 string) ValidateOption {
|
|
return WithValidator(MinDeltaIs(c1, c2, dur))
|
|
}
|
|
|
|
// WithValidator validates the token with the given Validator.
|
|
//
|
|
// For example, in order to validate tokens that are only valid during August, you would write
|
|
//
|
|
// validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error {
|
|
// if time.Now().Month() != 8 {
|
|
// return fmt.Errorf(`tokens are only valid during August!`)
|
|
// }
|
|
// return nil
|
|
// })
|
|
// err := jwt.Validate(token, jwt.WithValidator(validator))
|
|
//
|
|
func WithValidator(v Validator) ValidateOption {
|
|
return newValidateOption(identValidator{}, v)
|
|
}
|
|
|
|
type decryptParams struct {
|
|
alg jwa.KeyEncryptionAlgorithm
|
|
key interface{}
|
|
}
|
|
|
|
type DecryptParameters interface {
|
|
Algorithm() jwa.KeyEncryptionAlgorithm
|
|
Key() interface{}
|
|
}
|
|
|
|
func (dp *decryptParams) Algorithm() jwa.KeyEncryptionAlgorithm {
|
|
return dp.alg
|
|
}
|
|
|
|
func (dp *decryptParams) Key() interface{} {
|
|
return dp.key
|
|
}
|
|
|
|
// WithDecrypt allows users to specify parameters for decryption using
|
|
// `jwe.Decrypt`. You must specify this if your JWT is encrypted.
|
|
func WithDecrypt(alg jwa.KeyEncryptionAlgorithm, key interface{}) ParseOption {
|
|
return newParseOption(identDecrypt{}, &decryptParams{
|
|
alg: alg,
|
|
key: key,
|
|
})
|
|
}
|
|
|
|
// WithPedantic enables pedantic mode for parsing JWTs. Currently this only
|
|
// applies to checking for the correct `typ` and/or `cty` when necessary.
|
|
func WithPedantic(v bool) ParseOption {
|
|
return newParseOption(identPedantic{}, v)
|
|
}
|
|
|
|
// InferAlgorithmFromKey allows jwt.Parse to guess the signature algorithm
|
|
// passed to `jws.Verify()`, in case the key you provided does not have a proper `alg` header.
|
|
//
|
|
// Compared to providing explicit `alg` from the key this is slower, and in
|
|
// case our heuristics are wrong or outdated, may fail to verify the token.
|
|
// Also, automatic detection of signature verification methods are always
|
|
// more vulnerable for potential attack vectors.
|
|
//
|
|
// It is highly recommended that you fix your key to contain a proper `alg`
|
|
// header field instead of resorting to using this option, but sometimes
|
|
// it just needs to happen.
|
|
//
|
|
// Your JWT still need to have an `alg` field, and it must match one of the
|
|
// candidates that we produce for your key
|
|
func InferAlgorithmFromKey(v bool) ParseOption {
|
|
return newParseOption(identInferAlgorithmFromKey{}, v)
|
|
}
|
|
|
|
// KeySetProvider is an interface for objects that can choose the appropriate
|
|
// jwk.Set to be used when verifying JWTs
|
|
type KeySetProvider interface {
|
|
// KeySetFrom returns the jwk.Set to be used to verify the token.
|
|
// Keep in mind that the token at the point when the method is called is NOT VERIFIED.
|
|
// DO NOT trust the contents of the Token too much. For example, do not take the
|
|
// hint as to which signature algorithm to use from the token itself.
|
|
KeySetFrom(Token) (jwk.Set, error)
|
|
}
|
|
|
|
// KeySetProviderFunc is an implementation of KeySetProvider that is based
|
|
// on a function.
|
|
type KeySetProviderFunc func(Token) (jwk.Set, error)
|
|
|
|
func (fn KeySetProviderFunc) KeySetFrom(t Token) (jwk.Set, error) {
|
|
return fn(t)
|
|
}
|
|
|
|
// WithKeySetProvider allows users to specify an object to choose which
|
|
// jwk.Set to use for verification.
|
|
//
|
|
// If provided with WithKeySet(), WithKeySet() option takes precedence.
|
|
func WithKeySetProvider(p KeySetProvider) ParseOption {
|
|
return newParseOption(identKeySetProvider{}, p)
|
|
}
|
|
|
|
// WithContext allows you to specify a context.Context object to be used
|
|
// with `jwt.Validate()` option.
|
|
//
|
|
// Please be aware that in the next major release of this library,
|
|
// `jwt.Validate()`'s signature will change to include an explicit
|
|
// `context.Context` object.
|
|
func WithContext(ctx context.Context) ValidateOption {
|
|
return newValidateOption(identContext{}, ctx)
|
|
}
|
|
|
|
// WithVerifyAuto specifies that the JWS verification should be performed
|
|
// using `jws.VerifyAuto()`, which in turn attempts to verify the message
|
|
// using values that are stored within the JWS message.
|
|
//
|
|
// Only passing this option to `jwt.Parse()` will not result in a successful
|
|
// verification. Please make sure to carefully read the documentation in
|
|
// `jws.VerifyAuto()`, and provide the necessary Whitelist object via
|
|
// `jwt.WithFetchWhitelist()`
|
|
//
|
|
// You might also consider using a backoff policy by using `jwt.WithFetchBackoff()`
|
|
// to control the number of requests being made.
|
|
func WithVerifyAuto(v bool) ParseOption {
|
|
return newParseOption(identVerifyAuto{}, v)
|
|
}
|
|
|
|
// WithFetchWhitelist specifies the `jwk.Whitelist` object that should be
|
|
// passed to `jws.VerifyAuto()`, which in turn will be passed to `jwk.Fetch()`
|
|
//
|
|
// This is a wrapper over `jws.WithFetchWhitelist()` that can be passed
|
|
// to `jwt.Parse()`, and will be ignored if you spcify `jws.WithJWKSetFetcher()`
|
|
func WithFetchWhitelist(wl jwk.Whitelist) ParseOption {
|
|
return newParseOption(identFetchWhitelist{}, wl)
|
|
}
|
|
|
|
// WithHTTPClient specifies the `*http.Client` object that should be
|
|
// passed to `jws.VerifyAuto()`, which in turn will be passed to `jwk.Fetch()`
|
|
//
|
|
// This is a wrapper over `jws.WithHTTPClient()` that can be passed
|
|
// to `jwt.Parse()`, and will be ignored if you spcify `jws.WithJWKSetFetcher()`
|
|
func WithHTTPClient(httpcl *http.Client) ParseOption {
|
|
return newParseOption(identHTTPClient{}, httpcl)
|
|
}
|
|
|
|
// WithFetchBackoff specifies the `backoff.Policy` object that should be
|
|
// passed to `jws.VerifyAuto()`, which in turn will be passed to `jwk.Fetch()`
|
|
//
|
|
// This is a wrapper over `jws.WithFetchBackoff()` that can be passed
|
|
// to `jwt.Parse()`, and will be ignored if you spcify `jws.WithJWKSetFetcher()`
|
|
func WithFetchBackoff(b backoff.Policy) ParseOption {
|
|
return newParseOption(identFetchBackoff{}, b)
|
|
}
|
|
|
|
// WithJWKSetFetcher specifies the `jws.JWKSetFetcher` object that should be
|
|
// passed to `jws.VerifyAuto()`
|
|
//
|
|
// This is a wrapper over `jws.WithJWKSetFetcher()` that can be passed
|
|
// to `jwt.Parse()`.
|
|
func WithJWKSetFetcher(f jws.JWKSetFetcher) ParseOption {
|
|
return newParseOption(identJWKSetFetcher{}, f)
|
|
}
|