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, )