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 }