github.com/lestrrat-go/jwx/v2@v2.0.21/jwt/options.go (about)

     1  package jwt
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/lestrrat-go/jwx/v2/jwa"
     8  	"github.com/lestrrat-go/jwx/v2/jwe"
     9  	"github.com/lestrrat-go/jwx/v2/jwk"
    10  	"github.com/lestrrat-go/jwx/v2/jws"
    11  	"github.com/lestrrat-go/option"
    12  )
    13  
    14  type identInsecureNoSignature struct{}
    15  type identKey struct{}
    16  type identKeySet struct{}
    17  type identTypedClaim struct{}
    18  type identVerifyAuto struct{}
    19  
    20  func toSignOptions(options ...Option) ([]jws.SignOption, error) {
    21  	soptions := make([]jws.SignOption, 0, len(options))
    22  	for _, option := range options {
    23  		//nolint:forcetypeassert
    24  		switch option.Ident() {
    25  		case identInsecureNoSignature{}:
    26  			soptions = append(soptions, jws.WithInsecureNoSignature())
    27  		case identKey{}:
    28  			wk := option.Value().(*withKey) // this always succeeds
    29  			var wksoptions []jws.WithKeySuboption
    30  			for _, subopt := range wk.options {
    31  				wksopt, ok := subopt.(jws.WithKeySuboption)
    32  				if !ok {
    33  					return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySuboption, but got %T`, subopt)
    34  				}
    35  				wksoptions = append(wksoptions, wksopt)
    36  			}
    37  
    38  			soptions = append(soptions, jws.WithKey(wk.alg, wk.key, wksoptions...))
    39  		case identSignOption{}:
    40  			sigOpt := option.Value().(jws.SignOption) // this always succeeds
    41  			soptions = append(soptions, sigOpt)
    42  		}
    43  	}
    44  	return soptions, nil
    45  }
    46  
    47  func toEncryptOptions(options ...Option) ([]jwe.EncryptOption, error) {
    48  	soptions := make([]jwe.EncryptOption, 0, len(options))
    49  	for _, option := range options {
    50  		//nolint:forcetypeassert
    51  		switch option.Ident() {
    52  		case identKey{}:
    53  			wk := option.Value().(*withKey) // this always succeeds
    54  			var wksoptions []jwe.WithKeySuboption
    55  			for _, subopt := range wk.options {
    56  				wksopt, ok := subopt.(jwe.WithKeySuboption)
    57  				if !ok {
    58  					return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jwe.WithKeySuboption, but got %T`, subopt)
    59  				}
    60  				wksoptions = append(wksoptions, wksopt)
    61  			}
    62  
    63  			soptions = append(soptions, jwe.WithKey(wk.alg, wk.key, wksoptions...))
    64  		case identEncryptOption{}:
    65  			encOpt := option.Value().(jwe.EncryptOption) // this always succeeds
    66  			soptions = append(soptions, encOpt)
    67  		}
    68  	}
    69  	return soptions, nil
    70  }
    71  
    72  func toVerifyOptions(options ...Option) ([]jws.VerifyOption, error) {
    73  	voptions := make([]jws.VerifyOption, 0, len(options))
    74  	for _, option := range options {
    75  		//nolint:forcetypeassert
    76  		switch option.Ident() {
    77  		case identKey{}:
    78  			wk := option.Value().(*withKey) // this always succeeds
    79  			var wksoptions []jws.WithKeySuboption
    80  			for _, subopt := range wk.options {
    81  				wksopt, ok := subopt.(jws.WithKeySuboption)
    82  				if !ok {
    83  					return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySuboption, but got %T`, subopt)
    84  				}
    85  				wksoptions = append(wksoptions, wksopt)
    86  			}
    87  
    88  			voptions = append(voptions, jws.WithKey(wk.alg, wk.key, wksoptions...))
    89  		case identKeySet{}:
    90  			wks := option.Value().(*withKeySet) // this always succeeds
    91  			var wkssoptions []jws.WithKeySetSuboption
    92  			for _, subopt := range wks.options {
    93  				wkssopt, ok := subopt.(jws.WithKeySetSuboption)
    94  				if !ok {
    95  					return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySetSuboption, but got %T`, subopt)
    96  				}
    97  				wkssoptions = append(wkssoptions, wkssopt)
    98  			}
    99  
   100  			voptions = append(voptions, jws.WithKeySet(wks.set, wkssoptions...))
   101  		case identVerifyAuto{}:
   102  			// this one doesn't need conversion. just get the stored option
   103  			voptions = append(voptions, option.Value().(jws.VerifyOption))
   104  		case identKeyProvider{}:
   105  			kp, ok := option.Value().(jws.KeyProvider)
   106  			if !ok {
   107  				return nil, fmt.Errorf(`expected jws.KeyProvider, got %T`, option.Value())
   108  			}
   109  			voptions = append(voptions, jws.WithKeyProvider(kp))
   110  		}
   111  	}
   112  	return voptions, nil
   113  }
   114  
   115  type withKey struct {
   116  	alg     jwa.KeyAlgorithm
   117  	key     interface{}
   118  	options []Option
   119  }
   120  
   121  // WithKey is a multi-purpose option. It can be used for either jwt.Sign, jwt.Parse (and
   122  // its siblings), and jwt.Serializer methods. For signatures, please see the documentation
   123  // for `jws.WithKey` for more details. For encryption, please see the documentation
   124  // for `jwe.WithKey`.
   125  //
   126  // It is the caller's responsibility to match the suboptions to the operation that they
   127  // are performing. For example, you are not allowed to do this, because the operation
   128  // is to generate a signature, and yet you are passing options for jwe:
   129  //
   130  //	jwt.Sign(token, jwt.WithKey(alg, key, jweOptions...))
   131  //
   132  // In the above example, the creation of the option via `jwt.WithKey()` will work, but
   133  // when `jwt.Sign()` is called, the fact that you passed JWE suboptions will be
   134  // detected, and it will be an error.
   135  func WithKey(alg jwa.KeyAlgorithm, key interface{}, suboptions ...Option) SignEncryptParseOption {
   136  	return &signEncryptParseOption{option.New(identKey{}, &withKey{
   137  		alg:     alg,
   138  		key:     key,
   139  		options: suboptions,
   140  	})}
   141  }
   142  
   143  type withKeySet struct {
   144  	set     jwk.Set
   145  	options []interface{}
   146  }
   147  
   148  // WithKeySet forces the Parse method to verify the JWT message
   149  // using one of the keys in the given key set.
   150  //
   151  // Key IDs (`kid`) in the JWS message and the JWK in the given `jwk.Set`
   152  // must match in order for the key to be a candidate to be used for
   153  // verification.
   154  //
   155  // This is for security reasons. If you must disable it, you can do so by
   156  // specifying `jws.WithRequireKid(false)` in the suboptions. But we don't
   157  // recommend it unless you know exactly what the security implications are
   158  //
   159  // When using this option, keys MUST have a proper 'alg' field
   160  // set. This is because we need to know the exact algorithm that
   161  // you (the user) wants to use to verify the token. We do NOT
   162  // trust the token's headers, because they can easily be tampered with.
   163  //
   164  // However, there _is_ a workaround if you do understand the risks
   165  // of allowing a library to automatically choose a signature verification strategy,
   166  // and you do not mind the verification process having to possibly
   167  // attempt using multiple times before succeeding to verify. See
   168  // `jws.InferAlgorithmFromKey` option
   169  //
   170  // If you have only one key in the set, and are sure you want to
   171  // use that key, you can use the `jwt.WithDefaultKey` option.
   172  func WithKeySet(set jwk.Set, options ...interface{}) ParseOption {
   173  	return &parseOption{option.New(identKeySet{}, &withKeySet{
   174  		set:     set,
   175  		options: options,
   176  	})}
   177  }
   178  
   179  // WithIssuer specifies that expected issuer value. If not specified,
   180  // the value of issuer is not verified at all.
   181  func WithIssuer(s string) ValidateOption {
   182  	return WithValidator(issuerClaimValueIs(s))
   183  }
   184  
   185  // WithSubject specifies that expected subject value. If not specified,
   186  // the value of subject is not verified at all.
   187  func WithSubject(s string) ValidateOption {
   188  	return WithValidator(ClaimValueIs(SubjectKey, s))
   189  }
   190  
   191  // WithJwtID specifies that expected jti value. If not specified,
   192  // the value of jti is not verified at all.
   193  func WithJwtID(s string) ValidateOption {
   194  	return WithValidator(ClaimValueIs(JwtIDKey, s))
   195  }
   196  
   197  // WithAudience specifies that expected audience value.
   198  // `Validate()` will return true if one of the values in the `aud` element
   199  // matches this value. If not specified, the value of `aud` is not
   200  // verified at all.
   201  func WithAudience(s string) ValidateOption {
   202  	return WithValidator(audienceClaimContainsString(s))
   203  }
   204  
   205  // WithClaimValue specifies the expected value for a given claim
   206  func WithClaimValue(name string, v interface{}) ValidateOption {
   207  	return WithValidator(ClaimValueIs(name, v))
   208  }
   209  
   210  type claimPair struct {
   211  	Name  string
   212  	Value interface{}
   213  }
   214  
   215  // WithTypedClaim allows a private claim to be parsed into the object type of
   216  // your choice. It works much like the RegisterCustomField, but the effect
   217  // is only applicable to the jwt.Parse function call which receives this option.
   218  //
   219  // While this can be extremely useful, this option should be used with caution:
   220  // There are many caveats that your entire team/user-base needs to be aware of,
   221  // and therefore in general its use is discouraged. Only use it when you know
   222  // what you are doing, and you document its use clearly for others.
   223  //
   224  // First and foremost, this is a "per-object" option. Meaning that given the same
   225  // serialized format, it is possible to generate two objects whose internal
   226  // representations may differ. That is, if you parse one _WITH_ the option,
   227  // and the other _WITHOUT_, their internal representation may completely differ.
   228  // This could potentially lead to problems.
   229  //
   230  // Second, specifying this option will slightly slow down the decoding process
   231  // as it needs to consult multiple definitions sources (global and local), so
   232  // be careful if you are decoding a large number of tokens, as the effects will stack up.
   233  //
   234  // Finally, this option will also NOT work unless the tokens themselves support such
   235  // parsing mechanism. For example, while tokens obtained from `jwt.New()` and
   236  // `openid.New()` will respect this option, if you provide your own custom
   237  // token type, it will need to implement the TokenWithDecodeCtx interface.
   238  func WithTypedClaim(name string, object interface{}) ParseOption {
   239  	return &parseOption{option.New(identTypedClaim{}, claimPair{Name: name, Value: object})}
   240  }
   241  
   242  // WithRequiredClaim specifies that the claim identified the given name
   243  // must exist in the token. Only the existence of the claim is checked:
   244  // the actual value associated with that field is not checked.
   245  func WithRequiredClaim(name string) ValidateOption {
   246  	return WithValidator(IsRequired(name))
   247  }
   248  
   249  // WithMaxDelta specifies that given two claims `c1` and `c2` that represent time, the difference in
   250  // time.Duration must be less than equal to the value specified by `d`. If `c1` or `c2` is the
   251  // empty string, the current time (as computed by `time.Now` or the object passed via
   252  // `WithClock()`) is used for the comparison.
   253  //
   254  // `c1` and `c2` are also assumed to be required, therefore not providing either claim in the
   255  // token will result in an error.
   256  //
   257  // Because there is no way of reliably knowing how to parse private claims, we currently only
   258  // support `iat`, `exp`, and `nbf` claims.
   259  //
   260  // If the empty string is passed to c1 or c2, then the current time (as calculated by time.Now() or
   261  // the clock object provided via WithClock()) is used.
   262  //
   263  // For example, in order to specify that `exp` - `iat` should be less than 10*time.Second, you would write
   264  //
   265  //	jwt.Validate(token, jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
   266  //
   267  // If AcceptableSkew of 2 second is specified, the above will return valid for any value of
   268  // `exp` - `iat`  between 8 (10-2) and 12 (10+2).
   269  func WithMaxDelta(dur time.Duration, c1, c2 string) ValidateOption {
   270  	return WithValidator(MaxDeltaIs(c1, c2, dur))
   271  }
   272  
   273  // WithMinDelta is almost exactly the same as WithMaxDelta, but force validation to fail if
   274  // the difference between time claims are less than dur.
   275  //
   276  // For example, in order to specify that `exp` - `iat` should be greater than 10*time.Second, you would write
   277  //
   278  //	jwt.Validate(token, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
   279  //
   280  // The validation would fail if the difference is less than 10 seconds.
   281  func WithMinDelta(dur time.Duration, c1, c2 string) ValidateOption {
   282  	return WithValidator(MinDeltaIs(c1, c2, dur))
   283  }
   284  
   285  // WithVerifyAuto specifies that the JWS verification should be attempted
   286  // by using the data available in the JWS message. Currently only verification
   287  // method available is to use the keys available in the JWKS URL pointed
   288  // in the `jku` field.
   289  //
   290  // The first argument should either be `nil`, or your custom jwk.Fetcher
   291  // object, which tells how the JWKS should be fetched. Leaving it to
   292  // `nil` is equivalent to specifying that `jwk.Fetch` should be used.
   293  //
   294  // You can further pass options to customize the fetching behavior.
   295  //
   296  // One notable difference in the option available via the `jwt`
   297  // package and the `jws.Verify()` or `jwk.Fetch()` functions is that
   298  // by default all fetching is disabled unless you explicitly whitelist urls.
   299  // Therefore, when you use this option you WILL have to specify at least
   300  // the `jwk.WithFetchWhitelist()` suboption: as:
   301  //
   302  //	jwt.Parse(data, jwt.WithVerifyAuto(nil, jwk.WithFetchWhitelist(...)))
   303  //
   304  // See the list of available options that you can pass to `jwk.Fetch()`
   305  // in the `jwk` package
   306  func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) ParseOption {
   307  	return &parseOption{option.New(identVerifyAuto{}, jws.WithVerifyAuto(f, options...))}
   308  }
   309  
   310  func WithInsecureNoSignature() SignOption {
   311  	return &signEncryptParseOption{option.New(identInsecureNoSignature{}, nil)}
   312  }