diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 6d03eb15dbc..46c5d85a5d2 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -429,7 +429,32 @@ "additionalProperties": false, "description": "Configures OpenID Connect Discovery (/.well-known/openid-configuration).", "properties": { + "jwks_url": { + "type": "string", + "description": "Overwrites the JWKS URL", + "format": "uri", + "examples": [ + "https://my-service.com/.well-known/jwks.json" + ] + }, + "token_url": { + "type": "string", + "description": "Overwrites the OAuth2 Token URL", + "format": "uri", + "examples": [ + "https://my-service.com/oauth2/token" + ] + }, + "auth_url": { + "type": "string", + "description": "Overwrites the OAuth2 Auth URL", + "format": "uri", + "examples": [ + "https://my-service.com/oauth2/auth" + ] + }, "client_registration_url": { + "description": "Sets the OpenID Connect Dynamic Client Registration Endpoint", "type": "string", "format": "uri", "examples": [ diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 118e6b8ab8f..a333cb43121 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -233,7 +233,7 @@ func (s *DefaultStrategy) forwardAuthenticationRequest(w http.ResponseWriter, r csrf := strings.Replace(uuid.New(), "-", "", -1) // Generate the request URL - iu := urlx.AppendPaths(s.c.IssuerURL(), s.c.OAuth2AuthURL()) + iu := s.c.OAuth2AuthURL() iu.RawQuery = r.URL.RawQuery var idTokenHintClaims jwtgo.MapClaims diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 45e02eb0852..3fcb38bb7d7 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -27,8 +27,10 @@ type Provider interface { ErrorURL() *url.URL PublicURL() *url.URL IssuerURL() *url.URL - OAuth2AuthURL() string + OAuth2AuthURL() *url.URL + OAuth2TokenURL() *url.URL OAuth2ClientRegistrationURL() *url.URL + JWKSURL() *url.URL AllowTLSTerminationFrom() []string AccessTokenStrategy() string SubjectIdentifierAlgorithmSalt() string diff --git a/driver/configuration/provider_viper.go b/driver/configuration/provider_viper.go index 52fd27597ca..42c7849808c 100644 --- a/driver/configuration/provider_viper.go +++ b/driver/configuration/provider_viper.go @@ -37,6 +37,9 @@ type ViperProvider struct { const ( ViperKeyWellKnownKeys = "webfinger.jwks.broadcast_keys" ViperKeyOAuth2ClientRegistrationURL = "webfinger.oidc_discovery.client_registration_url" + ViperKLeyOAuth2TokenURL = "webfinger.oidc_discovery.token_url" + ViperKLeyOAuth2AuthURL = "webfinger.oidc_discovery.auth_url" + ViperKeyJWKSURL = "webfinger.oidc_discovery.jwks_url" ViperKeyOIDCDiscoverySupportedClaims = "webfinger.oidc_discovery.supported_claims" ViperKeyOIDCDiscoverySupportedScope = "webfinger.oidc_discovery.supported_scope" ViperKeyOIDCDiscoveryUserinfoEndpoint = "webfinger.oidc_discovery.userinfo_url" @@ -446,14 +449,22 @@ func (v *ViperProvider) IssuerURL() *url.URL { return urlRoot(urlx.ParseOrFatal(v.l, strings.TrimRight(viperx.GetString(v.l, ViperKeyIssuerURL, v.fallbackURL("/", v.publicHost(), v.publicPort()), "OAUTH2_ISSUER_URL", "ISSUER", "ISSUER_URL"), "/")+"/")) } -func (v *ViperProvider) OAuth2AuthURL() string { - return "/oauth2/auth" // this should not have the host etc prepended... -} - func (v *ViperProvider) OAuth2ClientRegistrationURL() *url.URL { return urlx.ParseOrFatal(v.l, viperx.GetString(v.l, ViperKeyOAuth2ClientRegistrationURL, "", "OAUTH2_CLIENT_REGISTRATION_URL")) } +func (v *ViperProvider) OAuth2TokenURL() *url.URL { + return urlx.ParseOrFatal(v.l, viperx.GetString(v.l, ViperKLeyOAuth2TokenURL, urlx.AppendPaths(v.IssuerURL(), "/oauth2/token").String())) +} + +func (v *ViperProvider) OAuth2AuthURL() *url.URL { + return urlx.ParseOrFatal(v.l, viperx.GetString(v.l, ViperKLeyOAuth2AuthURL, urlx.AppendPaths(v.IssuerURL(), "/oauth2/auth").String())) +} + +func (v *ViperProvider) JWKSURL() *url.URL { + return urlx.ParseOrFatal(v.l, viperx.GetString(v.l, ViperKeyJWKSURL, urlx.AppendPaths(v.IssuerURL(), "/.well-known/jwks.json").String())) +} + func (v *ViperProvider) AllowTLSTerminationFrom() []string { return viperx.GetStringSlice(v.l, ViperKeyAllowTLSTerminationFrom, []string{}, "HTTPS_ALLOW_TERMINATION_FROM") } diff --git a/driver/configuration/provider_viper_test.go b/driver/configuration/provider_viper_test.go index be12b51c0d3..8e8781a3c89 100644 --- a/driver/configuration/provider_viper_test.go +++ b/driver/configuration/provider_viper_test.go @@ -212,6 +212,9 @@ func TestViperProviderValidates(t *testing.T) { // webfinger assert.Equal(t, []string{"hydra.openid.id-token"}, c.WellKnownKeys()) assert.Equal(t, urlx.ParseOrPanic("https://example.com"), c.OAuth2ClientRegistrationURL()) + assert.Equal(t, urlx.ParseOrPanic("https://example.com/jwks.json"), c.JWKSURL()) + assert.Equal(t, urlx.ParseOrPanic("https://example.com/auth"), c.OAuth2AuthURL()) + assert.Equal(t, urlx.ParseOrPanic("https://example.com/token"), c.OAuth2TokenURL()) assert.Equal(t, []string{"sub", "username"}, c.OIDCDiscoverySupportedClaims()) assert.Equal(t, []string{"offline_access", "offline", "openid", "whatever"}, c.OIDCDiscoverySupportedScope()) assert.Equal(t, "https://example.com", c.OIDCDiscoveryUserinfoEndpoint()) diff --git a/internal/.hydra.yaml b/internal/.hydra.yaml index ad915903965..d713ddc397e 100644 --- a/internal/.hydra.yaml +++ b/internal/.hydra.yaml @@ -69,6 +69,9 @@ webfinger: broadcast_keys: - hydra.openid.id-token oidc_discovery: + jwks_url: https://example.com/jwks.json + auth_url: https://example.com/auth + token_url: https://example.com/token client_registration_url: https://example.com supported_claims: - username diff --git a/oauth2/handler.go b/oauth2/handler.go index c5caab78cd2..e2c8529f976 100644 --- a/oauth2/handler.go +++ b/oauth2/handler.go @@ -84,9 +84,9 @@ func (h *Handler) SetRoutes(admin *x.RouterAdmin, public *x.RouterPublic, corsMi public.GET(LogoutPath, h.LogoutHandler) public.POST(LogoutPath, h.LogoutHandler) - public.GET(DefaultLoginPath, h.fallbackHandler("", "", http.StatusInternalServerError, configuration.ViperKeyLoginURL)) - public.GET(DefaultConsentPath, h.fallbackHandler("", "", http.StatusInternalServerError, configuration.ViperKeyConsentURL)) - public.GET(DefaultLogoutPath, h.fallbackHandler("", "", http.StatusInternalServerError, configuration.ViperKeyLogoutURL)) + public.GET(DefaultLoginPath, h.fallbackHandler("", "", http.StatusOK, configuration.ViperKeyLoginURL)) + public.GET(DefaultConsentPath, h.fallbackHandler("", "", http.StatusOK, configuration.ViperKeyConsentURL)) + public.GET(DefaultLogoutPath, h.fallbackHandler("", "", http.StatusOK, configuration.ViperKeyLogoutURL)) public.GET(DefaultPostLogoutPath, h.fallbackHandler( "You logged out successfully!", "The Default Post Logout URL is not set which is why you are seeing this fallback page. Your log out request however succeeded.", @@ -224,9 +224,9 @@ func (h *Handler) LogoutHandler(w http.ResponseWriter, r *http.Request, ps httpr func (h *Handler) WellKnownHandler(w http.ResponseWriter, r *http.Request) { h.r.Writer().Write(w, r, &WellKnown{ Issuer: strings.TrimRight(h.c.IssuerURL().String(), "/") + "/", - AuthURL: urlx.AppendPaths(h.c.IssuerURL(), AuthPath).String(), - TokenURL: urlx.AppendPaths(h.c.IssuerURL(), TokenPath).String(), - JWKsURI: urlx.AppendPaths(h.c.IssuerURL(), JWKPath).String(), + AuthURL: h.c.OAuth2AuthURL().String(), + TokenURL: h.c.OAuth2TokenURL().String(), + JWKsURI: h.c.JWKSURL().String(), RevocationEndpoint: urlx.AppendPaths(h.c.IssuerURL(), RevocationPath).String(), RegistrationEndpoint: h.c.OAuth2ClientRegistrationURL().String(), SubjectTypes: h.c.SubjectTypesSupported(), @@ -277,7 +277,7 @@ func (h *Handler) UserinfoHandler(w http.ResponseWriter, r *http.Request) { if err != nil { rfcerr := fosite.ErrorToRFC6749Error(err) if rfcerr.StatusCode() == http.StatusUnauthorized { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("error=%s,error_description=%s,error_hint=%s", rfcerr.Name, rfcerr.Description, rfcerr.Hint)) + w.Header().Set("WWW-Authenticate", fmt.Sprintf("error=%s,error_description=%s,error_hint=%s", rfcerr.ErrorField, rfcerr.DescriptionField, rfcerr.HintField)) } h.r.Writer().WriteError(w, r, err) return @@ -292,7 +292,7 @@ func (h *Handler) UserinfoHandler(w http.ResponseWriter, r *http.Request) { c, ok := ar.GetClient().(*client.Client) if !ok { - h.r.Writer().WriteError(w, r, errors.WithStack(fosite.ErrServerError.WithHint("Unable to type assert to *client.Client"))) + h.r.Writer().WriteError(w, r, errors.WithStack(fosite.ErrServerError.WithHint("Unable to type assert to *client.Client."))) return } @@ -341,7 +341,7 @@ func (h *Handler) UserinfoHandler(w http.ResponseWriter, r *http.Request) { h.r.Writer().Write(w, r, interim) } else { - h.r.Writer().WriteError(w, r, errors.WithStack(fosite.ErrServerError.WithHint(fmt.Sprintf("Unsupported userinfo signing algorithm \"%s\"", c.UserinfoSignedResponseAlg)))) + h.r.Writer().WriteError(w, r, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported userinfo signing algorithm '%s'.", c.UserinfoSignedResponseAlg))) return } } @@ -437,7 +437,8 @@ func (h *Handler) IntrospectHandler(w http.ResponseWriter, r *http.Request, _ ht resp := &fosite.IntrospectionResponse{ Active: true, AccessRequester: ar, - TokenType: tt, + TokenUse: tt, + AccessTokenType: "Bearer", } exp := resp.GetAccessRequester().GetSession().GetExpiresAt(tt) @@ -475,7 +476,8 @@ func (h *Handler) IntrospectHandler(w http.ResponseWriter, r *http.Request, _ ht Audience: resp.GetAccessRequester().GetGrantedAudience(), Issuer: strings.TrimRight(h.c.IssuerURL().String(), "/") + "/", ObfuscatedSubject: obfuscated, - TokenType: string(resp.GetTokenType()), + TokenType: resp.GetAccessTokenType(), + TokenUse: string(resp.GetTokenUse()), }); err != nil { x.LogError(r, errors.WithStack(err), h.r.Logger()) } @@ -739,10 +741,10 @@ func (h *Handler) writeAuthorizeError(w http.ResponseWriter, r *http.Request, ar func (h *Handler) forwardError(w http.ResponseWriter, r *http.Request, err error) { rfErr := fosite.ErrorToRFC6749Error(err) - query := url.Values{"error": {rfErr.Name}, "error_description": {rfErr.Description}, "error_hint": {rfErr.Hint}} + query := url.Values{"error": {rfErr.ErrorField}, "error_description": {rfErr.DescriptionField}, "error_hint": {rfErr.HintField}} if h.c.ShareOAuth2Debug() { - query.Add("error_debug", rfErr.Debug) + query.Add("error_debug", rfErr.DebugField) } http.Redirect(w, r, urlx.CopyWithQuery(h.c.ErrorURL(), query).String(), http.StatusFound) @@ -767,7 +769,7 @@ func (h *Handler) DeleteHandler(w http.ResponseWriter, r *http.Request, _ httpro client := r.URL.Query().Get("client_id") if client == "" { - h.r.Writer().WriteError(w, r, errors.WithStack(fosite.ErrInvalidRequest.WithHint(`Query parameter "client" is not defined but it should have been.`))) + h.r.Writer().WriteError(w, r, errors.WithStack(fosite.ErrInvalidRequest.WithHint(`Query parameter 'client' is not defined but it should have been.`))) return } diff --git a/oauth2/handler_test.go b/oauth2/handler_test.go index 7b1e9b2f49e..35e2f668d9b 100644 --- a/oauth2/handler_test.go +++ b/oauth2/handler_test.go @@ -447,9 +447,9 @@ func TestHandlerWellKnown(t *testing.T) { trueConfig := oauth2.WellKnown{ Issuer: strings.TrimRight(conf.IssuerURL().String(), "/") + "/", - AuthURL: urlx.AppendPaths(conf.IssuerURL(), oauth2.AuthPath).String(), - TokenURL: urlx.AppendPaths(conf.IssuerURL(), oauth2.TokenPath).String(), - JWKsURI: urlx.AppendPaths(conf.IssuerURL(), oauth2.JWKPath).String(), + AuthURL: conf.OAuth2AuthURL().String(), + TokenURL: conf.OAuth2TokenURL().String(), + JWKsURI: conf.JWKSURL().String(), RevocationEndpoint: urlx.AppendPaths(conf.IssuerURL(), oauth2.RevocationPath).String(), RegistrationEndpoint: conf.OAuth2ClientRegistrationURL().String(), SubjectTypes: []string{"pairwise", "public"},