ExtractOAuth2 is a middleware for automatically redirecting to OAuth URLs, and handling the exchange inbound. Any route that does not have a "code" URL parameter will be redirected. AuthURLOpts are passed to the AuthCodeURL function. If this is nil, the default option oauth2.AccessTypeOffline will b
(config promoauth.OAuth2Config, client *http.Client, cookieCfg codersdk.HTTPCookieConfig, authURLOpts map[string]string, pkceMethods []promoauth.Oauth2PKCEChallengeMethod)
| 46 | // which PKCE methods are supported by the OAuth2 provider. If empty, |
| 47 | // PKCE will not be used. |
| 48 | func ExtractOAuth2(config promoauth.OAuth2Config, client *http.Client, cookieCfg codersdk.HTTPCookieConfig, authURLOpts map[string]string, pkceMethods []promoauth.Oauth2PKCEChallengeMethod) func(http.Handler) http.Handler { |
| 49 | opts := make([]oauth2.AuthCodeOption, 0, len(authURLOpts)+1) |
| 50 | opts = append(opts, oauth2.AccessTypeOffline) |
| 51 | for k, v := range authURLOpts { |
| 52 | opts = append(opts, oauth2.SetAuthURLParam(k, v)) |
| 53 | } |
| 54 | |
| 55 | // Only S256 PKCE is currently supported. |
| 56 | sha256PKCESupported := slices.Contains(pkceMethods, promoauth.PKCEChallengeMethodSha256) |
| 57 | return func(next http.Handler) http.Handler { |
| 58 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { |
| 59 | ctx := r.Context() |
| 60 | if client != nil { |
| 61 | ctx = context.WithValue(ctx, oauth2.HTTPClient, client) |
| 62 | } |
| 63 | |
| 64 | // Interfaces can hold a nil value |
| 65 | if config == nil || reflect.ValueOf(config).IsNil() { |
| 66 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 67 | Message: "The oauth2 method requested is not configured!", |
| 68 | }) |
| 69 | return |
| 70 | } |
| 71 | |
| 72 | // OIDC errors can be returned as query parameters. This can happen |
| 73 | // if for example we are providing and invalid scope. |
| 74 | // We should terminate the OIDC process if we encounter an error. |
| 75 | errorMsg := r.URL.Query().Get("error") |
| 76 | errorDescription := r.URL.Query().Get("error_description") |
| 77 | errorURI := r.URL.Query().Get("error_uri") |
| 78 | if errorMsg != "" { |
| 79 | // Combine the errors into a single string if either is provided. |
| 80 | if errorDescription == "" && errorURI != "" { |
| 81 | errorDescription = fmt.Sprintf("error_uri: %s", errorURI) |
| 82 | } else if errorDescription != "" && errorURI != "" { |
| 83 | errorDescription = fmt.Sprintf("%s, error_uri: %s", errorDescription, errorURI) |
| 84 | } |
| 85 | errorMsg = fmt.Sprintf("Encountered error in oidc process: %s", errorMsg) |
| 86 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 87 | Message: errorMsg, |
| 88 | // This message might be blank. This is ok. |
| 89 | Detail: errorDescription, |
| 90 | }) |
| 91 | return |
| 92 | } |
| 93 | |
| 94 | code := r.URL.Query().Get("code") |
| 95 | state := r.URL.Query().Get("state") |
| 96 | redirect := r.URL.Query().Get("redirect") |
| 97 | if redirect != "" { |
| 98 | // We want to ensure that we're only ever redirecting to the application. |
| 99 | // We could be more strict here and check to see if the host matches |
| 100 | // the host of the AccessURL but ultimately as long as our redirect |
| 101 | // url omits a host we're ensuring that we're routing to a path |
| 102 | // local to the application. |
| 103 | redirect = uriFromURL(redirect) |
| 104 | } |
| 105 |