1 package keyfunc 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "time" 11 ) 12 13 // ErrInvalidHTTPStatusCode indicates that the HTTP status code is invalid. 14 var ErrInvalidHTTPStatusCode = errors.New("invalid HTTP status code") 15 16 // Options represents the configuration options for a JWKS. 17 // 18 // If RefreshInterval and or RefreshUnknownKID is not nil, then a background goroutine will be launched to refresh the 19 // remote JWKS under the specified circumstances. 20 // 21 // When using a background refresh goroutine, make sure to use RefreshRateLimit if paired with RefreshUnknownKID. Also 22 // make sure to end the background refresh goroutine with the JWKS.EndBackground method when it's no longer needed. 23 type Options struct { 24 // Client is the HTTP client used to get the JWKS via HTTP. 25 Client *http.Client 26 27 // Ctx is the context for the keyfunc's background refresh. When the context expires or is canceled, the background 28 // goroutine will end. 29 Ctx context.Context 30 31 // GivenKeys is a map of JWT key IDs, `kid`, to their given keys. If the JWKS has a background refresh goroutine, 32 // these values persist across JWKS refreshes. By default, if the remote JWKS resource contains a key with the same 33 // `kid` any given keys with the same `kid` will be overwritten by the keys from the remote JWKS. Use the 34 // GivenKIDOverride option to flip this behavior. 35 GivenKeys map[string]GivenKey 36 37 // GivenKIDOverride will make a GivenKey override any keys with the same ID (`kid`) in the remote JWKS. The is only 38 // effectual if GivenKeys is provided. 39 GivenKIDOverride bool 40 41 // JWKUseWhitelist is a whitelist of JWK `use` parameter values that will restrict what keys can be returned for 42 // jwt.Keyfunc. The assumption is that jwt.Keyfunc is only used for JWT signature verification. 43 // The default behavior is to only return a JWK if its `use` parameter has the value `"sig"`, an empty string, or if 44 // the parameter was omitted entirely. 45 JWKUseWhitelist []JWKUse 46 47 // JWKUseNoWhitelist overrides the JWKUseWhitelist field and its default behavior. If set to true, all JWKs will be 48 // returned regardless of their `use` parameter value. 49 JWKUseNoWhitelist bool 50 51 // RefreshErrorHandler is a function that consumes errors that happen during a JWKS refresh. This is only effectual 52 // if a background refresh goroutine is active. 53 RefreshErrorHandler ErrorHandler 54 55 // RefreshInterval is the duration to refresh the JWKS in the background via a new HTTP request. If this is not nil, 56 // then a background goroutine will be used to refresh the JWKS once per the given interval. Make sure to call the 57 // JWKS.EndBackground method to end this goroutine when it's no longer needed. 58 RefreshInterval time.Duration 59 60 // RefreshRateLimit limits the rate at which refresh requests are granted. Only one refresh request can be queued 61 // at a time any refresh requests received while there is already a queue are ignored. It does not make sense to 62 // have RefreshInterval's value shorter than this. 63 RefreshRateLimit time.Duration 64 65 // RefreshTimeout is the duration for the context timeout used to create the HTTP request for a refresh of the JWKS. 66 // This defaults to one minute. This is used for the HTTP request and any background goroutine refreshes. 67 RefreshTimeout time.Duration 68 69 // RefreshUnknownKID indicates that the JWKS refresh request will occur every time a kid that isn't cached is seen. 70 // This is done through a background goroutine. Without specifying a RefreshInterval a malicious client could 71 // self-sign X JWTs, send them to this service, then cause potentially high network usage proportional to X. Make 72 // sure to call the JWKS.EndBackground method to end this goroutine when it's no longer needed. 73 RefreshUnknownKID bool 74 75 // RequestFactory creates HTTP requests for the remote JWKS resource located at the given url. For example, an 76 // HTTP header could be added to indicate a User-Agent. 77 RequestFactory func(ctx context.Context, url string) (*http.Request, error) 78 79 // ResponseExtractor consumes a *http.Response and produces the raw JSON for the JWKS. By default, the 80 // ResponseExtractorStatusOK function is used. The default behavior changed in v1.4.0. 81 ResponseExtractor func(ctx context.Context, resp *http.Response) (json.RawMessage, error) 82 } 83 84 // ResponseExtractorStatusOK is meant to be used as the ResponseExtractor field for Options. It confirms that response 85 // status code is 200 OK and returns the raw JSON from the response body. 86 func ResponseExtractorStatusOK(ctx context.Context, resp *http.Response) (json.RawMessage, error) { 87 //goland:noinspection GoUnhandledErrorResult 88 defer resp.Body.Close() 89 if resp.StatusCode != http.StatusOK { 90 return nil, fmt.Errorf("%w: %d", ErrInvalidHTTPStatusCode, resp.StatusCode) 91 } 92 return io.ReadAll(resp.Body) 93 } 94 95 // ResponseExtractorStatusAny is meant to be used as the ResponseExtractor field for Options. It returns the raw JSON 96 // from the response body regardless of the response status code. 97 func ResponseExtractorStatusAny(ctx context.Context, resp *http.Response) (json.RawMessage, error) { 98 //goland:noinspection GoUnhandledErrorResult 99 defer resp.Body.Close() 100 return io.ReadAll(resp.Body) 101 } 102 103 // applyOptions applies the given options to the given JWKS. 104 func applyOptions(jwks *JWKS, options Options) { 105 if options.Ctx != nil { 106 jwks.ctx, jwks.cancel = context.WithCancel(options.Ctx) 107 } 108 109 if options.GivenKeys != nil { 110 jwks.givenKeys = make(map[string]GivenKey) 111 for kid, key := range options.GivenKeys { 112 jwks.givenKeys[kid] = key 113 } 114 } 115 116 if !options.JWKUseNoWhitelist { 117 jwks.jwkUseWhitelist = make(map[JWKUse]struct{}) 118 for _, use := range options.JWKUseWhitelist { 119 jwks.jwkUseWhitelist[use] = struct{}{} 120 } 121 } 122 123 jwks.client = options.Client 124 jwks.givenKIDOverride = options.GivenKIDOverride 125 jwks.refreshErrorHandler = options.RefreshErrorHandler 126 jwks.refreshInterval = options.RefreshInterval 127 jwks.refreshRateLimit = options.RefreshRateLimit 128 jwks.refreshTimeout = options.RefreshTimeout 129 jwks.refreshUnknownKID = options.RefreshUnknownKID 130 jwks.requestFactory = options.RequestFactory 131 jwks.responseExtractor = options.ResponseExtractor 132 } 133