diff --git a/backend/cmd/server/bootstrap/02-server-configurations.yaml b/backend/cmd/server/bootstrap/02-server-configurations.yaml index 4dc88d6c72..af891b91b5 100644 --- a/backend/cmd/server/bootstrap/02-server-configurations.yaml +++ b/backend/cmd/server/bootstrap/02-server-configurations.yaml @@ -3,7 +3,7 @@ name: flow value: authFlow: defaultHandle: default-flow - expirySeconds: 1800 + expirySeconds: 3600 registrationFlow: expirySeconds: 3600 recoveryFlow: diff --git a/backend/cmd/server/config/default.json b/backend/cmd/server/config/default.json index 03a5f498bf..8fb59a1a1f 100644 --- a/backend/cmd/server/config/default.json +++ b/backend/cmd/server/config/default.json @@ -132,6 +132,9 @@ "authorization_code": { "validity_period": 600 }, + "authorization_request": { + "validity_period": 3600 + }, "dcr": { "enabled" : true, "insecure": false diff --git a/backend/internal/flow/flowexec/constants.go b/backend/internal/flow/flowexec/constants.go index 2298bc37f0..c0d9e832f4 100644 --- a/backend/internal/flow/flowexec/constants.go +++ b/backend/internal/flow/flowexec/constants.go @@ -4,7 +4,7 @@ package flowexec const ( - defaultAuthFlowExpiry int64 = 1800 // 30 minutes in seconds + defaultAuthFlowExpiry int64 = 3600 // 60 minutes in seconds defaultRegistrationFlowExpiry int64 = 3600 // 60 minutes in seconds defaultUserOnboardingFlowExpiry int64 = 86400 // 24 hours in seconds defaultRecoveryFlowExpiry int64 = 1800 // 30 minutes in seconds diff --git a/backend/internal/flow/flowexec/service_test.go b/backend/internal/flow/flowexec/service_test.go index f63baa79cd..990abecc50 100644 --- a/backend/internal/flow/flowexec/service_test.go +++ b/backend/internal/flow/flowexec/service_test.go @@ -631,7 +631,7 @@ func TestGetFlowExpirySeconds(t *testing.T) { { name: "Authentication flow", flowType: providers.FlowTypeAuthentication, - expected: 1800, + expected: 3600, }, { name: "Registration flow", @@ -646,7 +646,7 @@ func TestGetFlowExpirySeconds(t *testing.T) { { name: "Unknown flow type (fallback)", flowType: providers.FlowType("UNKNOWN_FLOW"), - expected: 1800, + expected: 3600, }, } @@ -1740,7 +1740,7 @@ func TestInitiateAndExecute_ZeroExpiryUsesDefault(t *testing.T) { mockCrypto.EXPECT().Encrypt(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return([]byte("encrypted"), nil, nil) mockStore.EXPECT().StoreFlowContext(mock.Anything, mock.Anything, - mock.MatchedBy(func(exp int64) bool { return exp == int64(1800) })). + mock.MatchedBy(func(exp int64) bool { return exp == int64(3600) })). Return(nil) mockEngineInner.EXPECT().Execute(mock.Anything). Return(FlowStep{Status: providers.FlowStatusIncomplete}, nil) diff --git a/backend/internal/oauth/oauth2/authz/auth_req_store.go b/backend/internal/oauth/oauth2/authz/auth_req_store.go index 53323041fc..34f32fe95b 100644 --- a/backend/internal/oauth/oauth2/authz/auth_req_store.go +++ b/backend/internal/oauth/oauth2/authz/auth_req_store.go @@ -32,11 +32,18 @@ type authorizationRequestStore struct { validityPeriod time.Duration } -// newAuthorizationRequestStore creates a new instance of authorizationRequestStore with injected dependencies. -func newAuthorizationRequestStore(storeProvider providers.RuntimeStoreProvider) authorizationRequestStoreInterface { +// newAuthorizationRequestStore creates a new instance of authorizationRequestStore with injected +// dependencies. A non-positive validityPeriodSeconds falls back to defaultAuthzRequestValidity. +func newAuthorizationRequestStore( + storeProvider providers.RuntimeStoreProvider, validityPeriodSeconds int64) authorizationRequestStoreInterface { + validityPeriod := defaultAuthzRequestValidity + if validityPeriodSeconds > 0 { + validityPeriod = time.Duration(validityPeriodSeconds) * time.Second + } + return &authorizationRequestStore{ storeProvider: storeProvider, - validityPeriod: 10 * time.Minute, + validityPeriod: validityPeriod, } } diff --git a/backend/internal/oauth/oauth2/authz/auth_req_store_test.go b/backend/internal/oauth/oauth2/authz/auth_req_store_test.go index 7a2798e4b3..2607bf509d 100644 --- a/backend/internal/oauth/oauth2/authz/auth_req_store_test.go +++ b/backend/internal/oauth/oauth2/authz/auth_req_store_test.go @@ -55,9 +55,17 @@ func (suite *AuthorizationRequestStoreTestSuite) SetupTest() { } func (suite *AuthorizationRequestStoreTestSuite) TestNewAuthorizationRequestStore() { - store := newAuthorizationRequestStore(inmemory.Initialize("test-deployment")) + store := newAuthorizationRequestStore(inmemory.Initialize("test-deployment"), 900) assert.NotNil(suite.T(), store) assert.Implements(suite.T(), (*authorizationRequestStoreInterface)(nil), store) + assert.Equal(suite.T(), 15*time.Minute, store.(*authorizationRequestStore).validityPeriod) +} + +func (suite *AuthorizationRequestStoreTestSuite) TestNewAuthorizationRequestStore_NonPositiveUsesDefault() { + for _, configured := range []int64{0, -1} { + store := newAuthorizationRequestStore(inmemory.Initialize("test-deployment"), configured) + assert.Equal(suite.T(), defaultAuthzRequestValidity, store.(*authorizationRequestStore).validityPeriod) + } } // Tests for AddRequest diff --git a/backend/internal/oauth/oauth2/authz/constants.go b/backend/internal/oauth/oauth2/authz/constants.go index 26e1fb29be..c1ed875093 100644 --- a/backend/internal/oauth/oauth2/authz/constants.go +++ b/backend/internal/oauth/oauth2/authz/constants.go @@ -3,6 +3,12 @@ package authz +import "time" + +// defaultAuthzRequestValidity is the authorization request context validity used when +// oauth.authorization_request.validity_period is not configured. +const defaultAuthzRequestValidity = 60 * time.Minute + // Authorization code states. const ( AuthCodeStateActive = "ACTIVE" diff --git a/backend/internal/oauth/oauth2/authz/init.go b/backend/internal/oauth/oauth2/authz/init.go index 276ae7da67..d47169e63b 100644 --- a/backend/internal/oauth/oauth2/authz/init.go +++ b/backend/internal/oauth/oauth2/authz/init.go @@ -28,7 +28,7 @@ func Initialize( transactioner providers.Transactioner, ) (AuthorizeServiceInterface, error) { authzCodeStore := newAuthorizationCodeStore(storeProvider) - authzReqStore := newAuthorizationRequestStore(storeProvider) + authzReqStore := newAuthorizationRequestStore(storeProvider, cfg.OAuth.AuthorizationRequest.ValidityPeriod) authzService := newAuthorizeService( actorProvider, resourceService, jwtService, flowExecService, diff --git a/backend/pkg/thunderidengine/config/config.go b/backend/pkg/thunderidengine/config/config.go index 4faf7376c4..23555e6a35 100644 --- a/backend/pkg/thunderidengine/config/config.go +++ b/backend/pkg/thunderidengine/config/config.go @@ -172,6 +172,13 @@ type AuthorizationCodeConfig struct { ValidityPeriod int64 `yaml:"validity_period" json:"validity_period"` } +// AuthorizationRequestConfig holds the authorization request context configuration details. +type AuthorizationRequestConfig struct { + // ValidityPeriod is how long (in seconds) the authorization request context survives while the + // user completes the login flow at the gate. + ValidityPeriod int64 `yaml:"validity_period" json:"validity_period"` +} + // DCRConfig holds the Dynamic Client Registration configuration. type DCRConfig struct { Enabled *bool `yaml:"enabled" json:"enabled"` @@ -206,15 +213,16 @@ type CIBAConfig struct { // OAuthConfig holds the OAuth configuration details. type OAuthConfig struct { - RefreshToken RefreshTokenConfig `yaml:"refresh_token" json:"refresh_token"` - AuthorizationCode AuthorizationCodeConfig `yaml:"authorization_code" json:"authorization_code"` - DCR DCRConfig `yaml:"dcr" json:"dcr"` - PAR PARConfig `yaml:"par" json:"par"` - DPoP DPoPConfig `yaml:"dpop" json:"dpop"` - AuthClass AuthClassConfig `yaml:"auth_class" json:"auth_class"` - CIBA CIBAConfig `yaml:"ciba" json:"ciba"` - Revocation RevocationConfig `yaml:"revocation" json:"revocation"` - TokenExchange TokenExchangeConfig `yaml:"token_exchange" json:"token_exchange"` + RefreshToken RefreshTokenConfig `yaml:"refresh_token" json:"refresh_token"` + AuthorizationCode AuthorizationCodeConfig `yaml:"authorization_code" json:"authorization_code"` + AuthorizationRequest AuthorizationRequestConfig `yaml:"authorization_request" json:"authorization_request"` + DCR DCRConfig `yaml:"dcr" json:"dcr"` + PAR PARConfig `yaml:"par" json:"par"` + DPoP DPoPConfig `yaml:"dpop" json:"dpop"` + AuthClass AuthClassConfig `yaml:"auth_class" json:"auth_class"` + CIBA CIBAConfig `yaml:"ciba" json:"ciba"` + Revocation RevocationConfig `yaml:"revocation" json:"revocation"` + TokenExchange TokenExchangeConfig `yaml:"token_exchange" json:"token_exchange"` // AllowWildcardRedirectURI enables wildcard pattern matching for redirect URIs. // When false (default), only exact redirect URI matching is performed. AllowWildcardRedirectURI bool `yaml:"allow_wildcard_redirect_uri" json:"allow_wildcard_redirect_uri"` diff --git a/docs/content/deployment/configuration.mdx b/docs/content/deployment/configuration.mdx index 4eb5660a75..169b2334d7 100644 --- a/docs/content/deployment/configuration.mdx +++ b/docs/content/deployment/configuration.mdx @@ -452,6 +452,7 @@ OAuth 2.0 and OpenID Connect settings. | `oauth.refresh_token.revoke_previous_on_renew` | `true` | If `true`, revokes the consumed refresh token after a successful renewal. Applies when `renew_on_grant` is enabled | | `oauth.refresh_token.validity_period` | `86400` | Refresh token validity period in seconds (24 hours) | | `oauth.authorization_code.validity_period` | `600` | Authorization code validity period in seconds (10 minutes) | +| `oauth.authorization_request.validity_period` | `3600` | How long the authorization request context stays valid while the user completes the login flow, in seconds (60 minutes). A non-positive value falls back to the default | | `oauth.dcr.enabled` | `true` | If `true`, enables the Dynamic Client Registration endpoint | | `oauth.dcr.insecure` | `false` | If `true`, allows insecure dynamic client registration (development only) | | `oauth.allowed_auth_methods` | `["client_secret_basic", "client_secret_post", "private_key_jwt", "none"]` | Client token endpoint authentication methods allowed during client registration | @@ -942,7 +943,7 @@ Configure flow defaults in either of these ways: value: authFlow: defaultHandle: default-flow - expirySeconds: 1800 + expirySeconds: 3600 registrationFlow: expirySeconds: 3600 recoveryFlow: @@ -956,7 +957,7 @@ Configure flow defaults in either of these ways: Content-Type: application/json { - "authFlow": { "defaultHandle": "default-flow", "expirySeconds": 1800 }, + "authFlow": { "defaultHandle": "default-flow", "expirySeconds": 3600 }, "registrationFlow": { "expirySeconds": 3600 } } ``` diff --git a/install/helm/README.md b/install/helm/README.md index 3c302d91c3..8d98671505 100644 --- a/install/helm/README.md +++ b/install/helm/README.md @@ -502,6 +502,8 @@ Password fields are available in `configuration.database.config.postgres`, `conf | `configuration.oauth.refreshToken.renewOnGrant` | Renew refresh token on grant | `false` | | `configuration.oauth.refreshToken.revokePreviousOnRenew` | Revoke the consumed refresh token on rotation (single-use); effective only when `renewOnGrant` is `true` | `true` | | `configuration.oauth.refreshToken.validityPeriod` | Refresh token validity period in seconds | `86400` | +| `configuration.oauth.authorizationCode.validityPeriod` | Authorization code validity period in seconds | `600` | +| `configuration.oauth.authorizationRequest.validityPeriod` | How long the authorization request context stays valid while the user completes the login flow, in seconds | `3600` | | `configuration.flow.maxVersionHistory` | Maximum flow version history to retain | `3` | | `configuration.flow.autoInferRegistration` | Enable auto-infer registration flow | `true` | | `configuration.passkey.allowedOrigins` | Passkey allowed origins | `[]` | diff --git a/install/helm/conf/deployment.yaml b/install/helm/conf/deployment.yaml index 5b54811c1d..df27b1ff66 100644 --- a/install/helm/conf/deployment.yaml +++ b/install/helm/conf/deployment.yaml @@ -235,6 +235,8 @@ oauth: validity_period: {{ .Values.configuration.oauth.refreshToken.validityPeriod }} authorization_code: validity_period: {{ .Values.configuration.oauth.authorizationCode.validityPeriod }} + authorization_request: + validity_period: {{ .Values.configuration.oauth.authorizationRequest.validityPeriod }} dcr: enabled: {{ .Values.configuration.oauth.dcr.enabled }} insecure: {{ .Values.configuration.oauth.dcr.insecure }} diff --git a/install/helm/values.yaml b/install/helm/values.yaml index e6c0a7348e..322b6fbf44 100644 --- a/install/helm/values.yaml +++ b/install/helm/values.yaml @@ -407,6 +407,8 @@ configuration: validityPeriod: 86400 authorizationCode: validityPeriod: 600 + authorizationRequest: + validityPeriod: 3600 dcr: enabled: true insecure: false