From d13014349c063cc358c0369a0aa9557f6a652167 Mon Sep 17 00:00:00 2001 From: Henning Perl Date: Wed, 20 Dec 2023 13:22:07 +0100 Subject: [PATCH 1/3] feat: support multiple token URLs (#780) Co-authored-by: Jonas Hungershausen --- .github/workflows/format.yml | 2 +- .github/workflows/licenses.yml | 2 +- .github/workflows/oidc-conformity-master.yml | 2 +- .github/workflows/oidc-conformity.yml | 2 +- .github/workflows/test.yml | 2 +- client_authentication.go | 44 ++++++++++++-------- config.go | 4 +- config_default.go | 4 +- handler/rfc7523/handler.go | 18 +++++--- handler/rfc7523/handler_test.go | 5 ++- 10 files changed, 52 insertions(+), 33 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 80515a617..b59c85d31 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: "1.20" + go-version: "1.21" - run: make format - name: Indicate formatting issues run: git diff HEAD --exit-code --color diff --git a/.github/workflows/licenses.yml b/.github/workflows/licenses.yml index cab996050..8871ccb2c 100644 --- a/.github/workflows/licenses.yml +++ b/.github/workflows/licenses.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/setup-node@v2 with: node-version: "18" diff --git a/.github/workflows/oidc-conformity-master.yml b/.github/workflows/oidc-conformity-master.yml index 370b7fa47..954bb0720 100644 --- a/.github/workflows/oidc-conformity-master.yml +++ b/.github/workflows/oidc-conformity-master.yml @@ -17,7 +17,7 @@ jobs: ref: master - uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - name: Update fosite run: | go mod edit -replace github.com/ory/fosite=github.com/ory/fosite@${{ github.sha }} diff --git a/.github/workflows/oidc-conformity.yml b/.github/workflows/oidc-conformity.yml index 896ac3f36..1c7ecdd05 100644 --- a/.github/workflows/oidc-conformity.yml +++ b/.github/workflows/oidc-conformity.yml @@ -17,7 +17,7 @@ jobs: ref: master - uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - name: Update fosite run: | go mod edit -replace github.com/ory/fosite=github.com/${{ github.event.pull_request.head.repo.full_name }}@${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6c31cb77..24f9fb236 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,5 +11,5 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: "1.20" + go-version: "1.21" - run: make test diff --git a/client_authentication.go b/client_authentication.go index 685e0311d..70e6fa199 100644 --- a/client_authentication.go +++ b/client_authentication.go @@ -11,6 +11,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/ory/x/errorsx" @@ -149,7 +150,7 @@ func (f *Fosite) DefaultClientAuthenticationStrategy(ctx context.Context, r *htt var jti string if !claims.VerifyIssuer(clientID, true) { return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'iss' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client.")) - } else if f.Config.GetTokenURL(ctx) == "" { + } else if len(f.Config.GetTokenURLs(ctx)) == 0 { return nil, errorsx.WithStack(ErrMisconfiguration.WithHint("The authorization server's token endpoint URL has not been set.")) } else if sub, ok := claims["sub"].(string); !ok || sub != clientID { return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'sub' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client.")) @@ -180,22 +181,10 @@ func (f *Fosite) DefaultClientAuthenticationStrategy(ctx context.Context, r *htt return nil, err } - if auds, ok := claims["aud"].([]interface{}); !ok { - if !claims.VerifyAudience(f.Config.GetTokenURL(ctx), true) { - return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.Config.GetTokenURL(ctx))) - } - } else { - var found bool - for _, aud := range auds { - if a, ok := aud.(string); ok && a == f.Config.GetTokenURL(ctx) { - found = true - break - } - } - - if !found { - return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.Config.GetTokenURL(ctx))) - } + if !audienceMatchesTokenURLs(claims, f.Config.GetTokenURLs(ctx)) { + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf( + "Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", + strings.Join(f.Config.GetTokenURLs(ctx), "' or '"))) } return client, nil @@ -235,6 +224,27 @@ func (f *Fosite) DefaultClientAuthenticationStrategy(ctx context.Context, r *htt return client, nil } +func audienceMatchesTokenURLs(claims jwt.MapClaims, tokenURLs []string) bool { + for _, tokenURL := range tokenURLs { + if audienceMatchesTokenURL(claims, tokenURL) { + return true + } + } + return false +} + +func audienceMatchesTokenURL(claims jwt.MapClaims, tokenURL string) bool { + if audiences, ok := claims["aud"].([]interface{}); ok { + for _, aud := range audiences { + if a, ok := aud.(string); ok && a == tokenURL { + return true + } + } + return false + } + return claims.VerifyAudience(tokenURL, true) +} + func (f *Fosite) checkClientSecret(ctx context.Context, client Client, clientSecret []byte) error { var err error err = f.Config.GetSecretsHasher(ctx).Compare(ctx, client.GetHashedSecret(), clientSecret) diff --git a/config.go b/config.go index aeadead7c..165e7b4b0 100644 --- a/config.go +++ b/config.go @@ -247,8 +247,8 @@ type FormPostHTMLTemplateProvider interface { } type TokenURLProvider interface { - // GetTokenURL returns the token URL. - GetTokenURL(ctx context.Context) string + // GetTokenURLs returns the token URL. + GetTokenURLs(ctx context.Context) []string } // AuthorizeEndpointHandlersProvider returns the provider for configuring the authorize endpoint handlers. diff --git a/config_default.go b/config_default.go index 8284e1cc9..a2ae5ccec 100644 --- a/config_default.go +++ b/config_default.go @@ -263,8 +263,8 @@ func (c *Config) GetSecretsHasher(ctx context.Context) Hasher { return c.ClientSecretsHasher } -func (c *Config) GetTokenURL(ctx context.Context) string { - return c.TokenURL +func (c *Config) GetTokenURLs(ctx context.Context) []string { + return []string{c.TokenURL} } func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) *template.Template { diff --git a/handler/rfc7523/handler.go b/handler/rfc7523/handler.go index d09ec19a1..95be7caff 100644 --- a/handler/rfc7523/handler.go +++ b/handler/rfc7523/handler.go @@ -5,6 +5,7 @@ package rfc7523 import ( "context" + "strings" "time" "github.com/ory/fosite/handler/oauth2" @@ -228,13 +229,11 @@ func (c *Handler) validateTokenClaims(ctx context.Context, claims jwt.Claims, ke ) } - if !claims.Audience.Contains(c.Config.GetTokenURL(ctx)) { + if !audienceMatchesTokenURLs(claims, c.Config.GetTokenURLs(ctx)) { return errorsx.WithStack(fosite.ErrInvalidGrant. WithHintf( - "The JWT in \"assertion\" request parameter MUST contain an \"aud\" (audience) claim containing a value \"%s\" that identifies the authorization server as an intended audience.", - c.Config.GetTokenURL(ctx), - ), - ) + `The JWT in "assertion" request parameter MUST contain an "aud" (audience) claim containing a value "%s" that identifies the authorization server as an intended audience.`, + strings.Join(c.Config.GetTokenURLs(ctx), `" or "`))) } if claims.Expiry == nil { @@ -299,6 +298,15 @@ func (c *Handler) validateTokenClaims(ctx context.Context, claims jwt.Claims, ke return nil } +func audienceMatchesTokenURLs(claims jwt.Claims, tokenURLs []string) bool { + for _, tokenURL := range tokenURLs { + if claims.Audience.Contains(tokenURL) { + return true + } + } + return false +} + type extendedSession interface { Session fosite.Session diff --git a/handler/rfc7523/handler_test.go b/handler/rfc7523/handler_test.go index d85bc1c3b..cd7cc1534 100644 --- a/handler/rfc7523/handler_test.go +++ b/handler/rfc7523/handler_test.go @@ -12,6 +12,7 @@ import ( mrand "math/rand" "net/url" "strconv" + "strings" "testing" "time" @@ -331,8 +332,8 @@ func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestNotValidAudienceInAsserti s.EqualError(err, fosite.ErrInvalidGrant.Error(), "expected error, because of invalid audience claim in assertion") s.Equal( fmt.Sprintf( - "The JWT in \"assertion\" request parameter MUST contain an \"aud\" (audience) claim containing a value \"%s\" that identifies the authorization server as an intended audience.", - s.handler.Config.GetTokenURL(ctx), + `The JWT in "assertion" request parameter MUST contain an "aud" (audience) claim containing a value "%s" that identifies the authorization server as an intended audience.`, + strings.Join(s.handler.Config.GetTokenURLs(ctx), `" or "`), ), fosite.ErrorToRFC6749Error(err).HintField, ) From 2c69fd81013a1b99b929955fd8b81311f0b80957 Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Wed, 27 Dec 2023 12:29:19 +0100 Subject: [PATCH 2/3] fix: quick start secret config (#781) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b0a0b2273..785a930ad 100644 --- a/README.md +++ b/README.md @@ -315,10 +315,11 @@ panic("unable to create private key") // check the api docs of fosite.Config for further configuration options var config = &fosite.Config{ AccessTokenLifespan: time.Minute * 30, + GlobalSecret: secret, // ... } -var oauth2Provider = compose.ComposeAllEnabled(config, storage, secret, privateKey) +var oauth2Provider = compose.ComposeAllEnabled(config, storage, privateKey) // The authorize endpoint is usually at "https://mydomain.com/oauth2/auth". func authorizeHandlerFunc(rw http.ResponseWriter, req *http.Request) { From f4114878826c6d26b6751a1ea61f69ebbc25d4f2 Mon Sep 17 00:00:00 2001 From: Suvin Nimnaka Date: Wed, 3 Jan 2024 21:52:02 +0530 Subject: [PATCH 3/3] Fix broken link (#783) --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 4d974c98b..4ed22ced9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,7 +1,7 @@ **THIS DOCUMENT HAS MOVED** This file is no longer being updated and kept for historical reasons. Please -check the [CHANGELOG](changelog.md) instead! +check the [CHANGELOG](CHANGELOG.md) instead!