Skip to content
5 changes: 5 additions & 0 deletions .schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,11 @@
"$ref": "#/definitions/duration"
}
]
},
"copy_assertion_audience": {
"type": "boolean",
"description": "Configures whether the audience from the assertion JWT in the jwt-bearer grant type is copied into the resulting access token. When set to `true` (the default), the audience values from the inbound assertion JWT are granted in the access token. Set to `false` to prevent the assertion audience from being copied into the access token.",
"default": true
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions driver/config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const (
KeyOAuth2GrantJWTIDOptional = "oauth2.grant.jwt.jti_optional"
KeyOAuth2GrantJWTIssuedDateOptional = "oauth2.grant.jwt.iat_optional"
KeyOAuth2GrantJWTMaxDuration = "oauth2.grant.jwt.max_ttl"
KeyOAuth2GrantJWTCopyAssertionAudience = "oauth2.grant.jwt.copy_assertion_audience"
KeyRefreshTokenHook = "oauth2.refresh_token_hook" // #nosec G101
KeyTokenHook = "oauth2.token_hook" // #nosec G101
KeyDevelopmentMode = "dev"
Expand Down Expand Up @@ -724,6 +725,10 @@ func (p *DefaultProvider) GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Co
return p.getProvider(ctx).Bool(KeyOAuth2GrantJWTIssuedDateOptional)
}

func (p *DefaultProvider) GetGrantTypeJWTBearerCopyAssertionAudience(ctx context.Context) bool {
return p.getProvider(ctx).BoolF(KeyOAuth2GrantJWTCopyAssertionAudience, true)
}

func (p *DefaultProvider) GetJWTMaxDuration(ctx context.Context) time.Duration {
return p.getProvider(ctx).DurationF(KeyOAuth2GrantJWTMaxDuration, time.Hour*24*30)
}
Expand Down
3 changes: 3 additions & 0 deletions driver/config/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,18 +502,21 @@ func TestJWTBearer(t *testing.T) {
assert.Equal(t, 1.0, p.GetJWTMaxDuration(ctx).Hours())
assert.Equal(t, false, p.GetGrantTypeJWTBearerIssuedDateOptional(ctx))
assert.Equal(t, false, p.GetGrantTypeJWTBearerIDOptional(ctx))
assert.Equal(t, true, p.GetGrantTypeJWTBearerCopyAssertionAudience(ctx), "should default to true when not set")

p2 := MustNew(t, l)

// p2.MustSet(ctx, KeyOAuth2GrantJWTClientAuthOptional, true)
p2.MustSet(ctx, KeyOAuth2GrantJWTMaxDuration, "24h")
p2.MustSet(ctx, KeyOAuth2GrantJWTIssuedDateOptional, true)
p2.MustSet(ctx, KeyOAuth2GrantJWTIDOptional, true)
p2.MustSet(ctx, KeyOAuth2GrantJWTCopyAssertionAudience, false)

// assert.Equal(t, true, p2.GetGrantTypeJWTBearerCanSkipClientAuth(ctx))
assert.Equal(t, 24.0, p2.GetJWTMaxDuration(ctx).Hours())
assert.Equal(t, true, p2.GetGrantTypeJWTBearerIssuedDateOptional(ctx))
assert.Equal(t, true, p2.GetGrantTypeJWTBearerIDOptional(ctx))
assert.Equal(t, false, p2.GetGrantTypeJWTBearerCopyAssertionAudience(ctx))
}

func TestJWTScopeClaimStrategy(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions fosite/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ type GrantTypeJWTBearerIssuedDateOptionalProvider interface {
GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Context) bool
}

// GrantTypeJWTBearerCopyAssertionAudienceProvider returns the provider for configuring whether the audience from the
// assertion JWT is copied into the resulting access token in the jwt-bearer grant type.
type GrantTypeJWTBearerCopyAssertionAudienceProvider interface {
// GetGrantTypeJWTBearerCopyAssertionAudience returns whether the audience from the assertion JWT should be
// copied into the resulting access token. Defaults to true for backwards compatibility.
GetGrantTypeJWTBearerCopyAssertionAudience(ctx context.Context) bool
}

// GetJWTMaxDurationProvider returns the provider for configuring the JWT max duration.
type GetJWTMaxDurationProvider interface {
// GetJWTMaxDuration returns the JWT max duration.
Expand Down
92 changes: 53 additions & 39 deletions fosite/config_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,45 +26,46 @@ const (
)

var (
_ AuthorizeCodeLifespanProvider = (*Config)(nil)
_ RefreshTokenLifespanProvider = (*Config)(nil)
_ AccessTokenLifespanProvider = (*Config)(nil)
_ ScopeStrategyProvider = (*Config)(nil)
_ AudienceStrategyProvider = (*Config)(nil)
_ RedirectSecureCheckerProvider = (*Config)(nil)
_ RefreshTokenScopesProvider = (*Config)(nil)
_ DisableRefreshTokenValidationProvider = (*Config)(nil)
_ AccessTokenIssuerProvider = (*Config)(nil)
_ JWTScopeFieldProvider = (*Config)(nil)
_ AllowedPromptsProvider = (*Config)(nil)
_ OmitRedirectScopeParamProvider = (*Config)(nil)
_ MinParameterEntropyProvider = (*Config)(nil)
_ SanitationAllowedProvider = (*Config)(nil)
_ EnforcePKCEForPublicClientsProvider = (*Config)(nil)
_ EnablePKCEPlainChallengeMethodProvider = (*Config)(nil)
_ EnforcePKCEProvider = (*Config)(nil)
_ GrantTypeJWTBearerCanSkipClientAuthProvider = (*Config)(nil)
_ GrantTypeJWTBearerIDOptionalProvider = (*Config)(nil)
_ GrantTypeJWTBearerIssuedDateOptionalProvider = (*Config)(nil)
_ GetJWTMaxDurationProvider = (*Config)(nil)
_ IDTokenLifespanProvider = (*Config)(nil)
_ IDTokenIssuerProvider = (*Config)(nil)
_ JWKSFetcherStrategyProvider = (*Config)(nil)
_ ClientAuthenticationStrategyProvider = (*Config)(nil)
_ SendDebugMessagesToClientsProvider = (*Config)(nil)
_ ResponseModeHandlerExtensionProvider = (*Config)(nil)
_ MessageCatalogProvider = (*Config)(nil)
_ FormPostHTMLTemplateProvider = (*Config)(nil)
_ TokenURLProvider = (*Config)(nil)
_ GetSecretsHashingProvider = (*Config)(nil)
_ HTTPClientProvider = (*Config)(nil)
_ HMACHashingProvider = (*Config)(nil)
_ AuthorizeEndpointHandlersProvider = (*Config)(nil)
_ TokenEndpointHandlersProvider = (*Config)(nil)
_ TokenIntrospectionHandlersProvider = (*Config)(nil)
_ RevocationHandlersProvider = (*Config)(nil)
_ PushedAuthorizeRequestHandlersProvider = (*Config)(nil)
_ PushedAuthorizeRequestConfigProvider = (*Config)(nil)
_ AuthorizeCodeLifespanProvider = (*Config)(nil)
_ RefreshTokenLifespanProvider = (*Config)(nil)
_ AccessTokenLifespanProvider = (*Config)(nil)
_ ScopeStrategyProvider = (*Config)(nil)
_ AudienceStrategyProvider = (*Config)(nil)
_ RedirectSecureCheckerProvider = (*Config)(nil)
_ RefreshTokenScopesProvider = (*Config)(nil)
_ DisableRefreshTokenValidationProvider = (*Config)(nil)
_ AccessTokenIssuerProvider = (*Config)(nil)
_ JWTScopeFieldProvider = (*Config)(nil)
_ AllowedPromptsProvider = (*Config)(nil)
_ OmitRedirectScopeParamProvider = (*Config)(nil)
_ MinParameterEntropyProvider = (*Config)(nil)
_ SanitationAllowedProvider = (*Config)(nil)
_ EnforcePKCEForPublicClientsProvider = (*Config)(nil)
_ EnablePKCEPlainChallengeMethodProvider = (*Config)(nil)
_ EnforcePKCEProvider = (*Config)(nil)
_ GrantTypeJWTBearerCanSkipClientAuthProvider = (*Config)(nil)
_ GrantTypeJWTBearerIDOptionalProvider = (*Config)(nil)
_ GrantTypeJWTBearerIssuedDateOptionalProvider = (*Config)(nil)
_ GrantTypeJWTBearerCopyAssertionAudienceProvider = (*Config)(nil)
_ GetJWTMaxDurationProvider = (*Config)(nil)
_ IDTokenLifespanProvider = (*Config)(nil)
_ IDTokenIssuerProvider = (*Config)(nil)
_ JWKSFetcherStrategyProvider = (*Config)(nil)
_ ClientAuthenticationStrategyProvider = (*Config)(nil)
_ SendDebugMessagesToClientsProvider = (*Config)(nil)
_ ResponseModeHandlerExtensionProvider = (*Config)(nil)
_ MessageCatalogProvider = (*Config)(nil)
_ FormPostHTMLTemplateProvider = (*Config)(nil)
_ TokenURLProvider = (*Config)(nil)
_ GetSecretsHashingProvider = (*Config)(nil)
_ HTTPClientProvider = (*Config)(nil)
_ HMACHashingProvider = (*Config)(nil)
_ AuthorizeEndpointHandlersProvider = (*Config)(nil)
_ TokenEndpointHandlersProvider = (*Config)(nil)
_ TokenIntrospectionHandlersProvider = (*Config)(nil)
_ RevocationHandlersProvider = (*Config)(nil)
_ PushedAuthorizeRequestHandlersProvider = (*Config)(nil)
_ PushedAuthorizeRequestConfigProvider = (*Config)(nil)
)

type Config struct {
Expand Down Expand Up @@ -160,6 +161,10 @@ type Config struct {
// GrantTypeJWTBearerIssuedDateOptional indicates, if "iat" (issued at) claim required or not in JWT.
GrantTypeJWTBearerIssuedDateOptional bool

// GrantTypeJWTBearerCopyAssertionAudience indicates whether the audience from the assertion JWT should be
// copied into the resulting access token. Defaults to true for backwards compatibility.
GrantTypeJWTBearerCopyAssertionAudience *bool
Comment thread
elatt marked this conversation as resolved.
Outdated

// GrantTypeJWTBearerMaxDuration sets the maximum time after JWT issued date, during which the JWT is considered valid.
GrantTypeJWTBearerMaxDuration time.Duration

Expand Down Expand Up @@ -323,6 +328,15 @@ func (c *Config) GetGrantTypeJWTBearerIDOptional(ctx context.Context) bool {
return c.GrantTypeJWTBearerIDOptional
}

// GetGrantTypeJWTBearerCopyAssertionAudience returns GrantTypeJWTBearerCopyAssertionAudience.
// Defaults to true if not explicitly set, for backwards compatibility.
func (c *Config) GetGrantTypeJWTBearerCopyAssertionAudience(ctx context.Context) bool {
if c.GrantTypeJWTBearerCopyAssertionAudience == nil {
return true
}
return *c.GrantTypeJWTBearerCopyAssertionAudience
}

// GetGrantTypeJWTBearerCanSkipClientAuth returns the GrantTypeJWTBearerCanSkipClientAuth field.
func (c *Config) GetGrantTypeJWTBearerCanSkipClientAuth(ctx context.Context) bool {
return c.GrantTypeJWTBearerCanSkipClientAuth
Expand Down
1 change: 1 addition & 0 deletions fosite/fosite.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ type Configurator interface {
GrantTypeJWTBearerCanSkipClientAuthProvider
GrantTypeJWTBearerIDOptionalProvider
GrantTypeJWTBearerIssuedDateOptionalProvider
GrantTypeJWTBearerCopyAssertionAudienceProvider
GetJWTMaxDurationProvider
AudienceStrategyProvider
ScopeStrategyProvider
Expand Down
7 changes: 5 additions & 2 deletions fosite/handler/rfc7523/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Handler struct {
fosite.GrantTypeJWTBearerCanSkipClientAuthProvider
fosite.GrantTypeJWTBearerIDOptionalProvider
fosite.GrantTypeJWTBearerIssuedDateOptionalProvider
fosite.GrantTypeJWTBearerCopyAssertionAudienceProvider
fosite.GetJWTMaxDurationProvider
fosite.AudienceStrategyProvider
fosite.ScopeStrategyProvider
Expand Down Expand Up @@ -103,8 +104,10 @@ func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite
request.GrantScope(scope)
}

for _, audience := range claims.Audience {
request.GrantAudience(audience)
if c.Config.GetGrantTypeJWTBearerCopyAssertionAudience(ctx) {
for _, audience := range claims.Audience {
request.GrantAudience(audience)
}
}

session, err := c.getSessionFromRequest(request)
Expand Down
57 changes: 57 additions & 0 deletions fosite/handler/rfc7523/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,63 @@ func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestValidAssertion() {
s.NoError(err, "no error expected, because assertion must be valid")
}

func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestValidAssertionCopiesAudienceByDefault() {
// arrange
ctx := context.Background()
s.accessRequest.GrantTypes = []string{grantTypeJWTBearer}
keyID := "my_key"
pubKey := s.createJWK(s.privateKey.Public(), keyID)
cl := s.createStandardClaim()

s.accessRequest.Form.Add("assertion", s.createTestAssertion(cl, keyID))
s.accessRequest.RequestedScope = []string{"valid_scope"}
s.mockStoreProvider.EXPECT().RFC7523KeyStorage().Return(s.mockStore).Times(4)
s.mockStore.EXPECT().GetPublicKey(ctx, cl.Issuer, cl.Subject, keyID).Return(&pubKey, nil)
s.mockStore.EXPECT().GetPublicKeyScopes(ctx, cl.Issuer, cl.Subject, keyID).Return([]string{"valid_scope", "openid"}, nil)
s.mockStore.EXPECT().IsJWTUsed(ctx, cl.ID).Return(false, nil)
s.mockStore.EXPECT().MarkJWTUsedForTime(ctx, cl.ID, cl.Expiry.Time()).Return(nil)

// act
err := s.handler.HandleTokenEndpointRequest(ctx, s.accessRequest)

// assert
s.NoError(err, "no error expected, because assertion must be valid")
s.ElementsMatch(
[]string{"https://www.example.com/token", "leela", "fry"},
s.accessRequest.GetGrantedAudience(),
"audience from assertion JWT should be copied to the access request by default",
)
}

func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestValidAssertionDoesNotCopyAudienceWhenDisabled() {
// arrange
ctx := context.Background()
s.accessRequest.GrantTypes = []string{grantTypeJWTBearer}
keyID := "my_key"
pubKey := s.createJWK(s.privateKey.Public(), keyID)
cl := s.createStandardClaim()
copyAudience := false
s.handler.Config.(*fosite.Config).GrantTypeJWTBearerCopyAssertionAudience = &copyAudience

s.accessRequest.Form.Add("assertion", s.createTestAssertion(cl, keyID))
s.accessRequest.RequestedScope = []string{"valid_scope"}
s.mockStoreProvider.EXPECT().RFC7523KeyStorage().Return(s.mockStore).Times(4)
s.mockStore.EXPECT().GetPublicKey(ctx, cl.Issuer, cl.Subject, keyID).Return(&pubKey, nil)
s.mockStore.EXPECT().GetPublicKeyScopes(ctx, cl.Issuer, cl.Subject, keyID).Return([]string{"valid_scope", "openid"}, nil)
s.mockStore.EXPECT().IsJWTUsed(ctx, cl.ID).Return(false, nil)
s.mockStore.EXPECT().MarkJWTUsedForTime(ctx, cl.ID, cl.Expiry.Time()).Return(nil)

// act
err := s.handler.HandleTokenEndpointRequest(ctx, s.accessRequest)

// assert
s.NoError(err, "no error expected, because assertion must be valid")
s.Empty(
s.accessRequest.GetGrantedAudience(),
"audience from assertion JWT should NOT be copied when CopyAssertionAudience is false",
)
}

func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestAssertionIsValidWhenNoScopesPassed() {
// arrange
ctx := context.Background()
Expand Down
5 changes: 5 additions & 0 deletions spec/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,11 @@
"$ref": "#/definitions/duration"
}
]
},
"copy_assertion_audience": {
"type": "boolean",
"description": "Configures whether the audience from the assertion JWT in the jwt-bearer grant type is copied into the resulting access token. When set to `true` (the default), the audience values from the inbound assertion JWT are granted in the access token. Set to `false` to prevent the assertion audience from being copied into the access token.",
"default": true
}
}
}
Expand Down
Loading