Add configurable OAuth expires_in setting
Related to https://gitlab.com/gitlab-org/gitlab/-/issues/595570
What does this MR do and why?
-
Introduces
oauth_access_token_expires_inas a configurable instance-wide setting (UI & API) so administrators of self-managed and Dedicated instances can reduce the OAuth access-token lifetime below the hard-coded 2-hour default. -
This unblocks customers whose security policies require shorter token TTLs (e.g. 15 mins/900s) for MCP and other OAuth-based integrations.
-
The setting is stored as a key inside a new
oauth_settingsJSONB column onapplication_settings, following the established pattern for grouping related settings without widening the OAuthAccessTokens table with individual columns. -
A
nilvalue means the existing Doorkeeper default of 7200 seconds (2 hours) continues to apply, so there is no change in behaviour for instances that do not configure the setting, avoiding a breaking change. -
The Doorkeeper initializer's
access_token_expires_inis changed from a static value to a lazy block that reads from custom_access_token_expires_in ->Gitlab::CurrentSettingsat request time, falling back to 7200 when unset. The setting applies to all new OAuth access tokens issued by the instance, including those used by MCP clients.
New Allowed range: 300–7200s seconds (5 minutes to 2 hrs).
- Min: The Web IDE has a built-in 5-minute expiry buffer so values below 300s would cause it to treat all tokens as expired immediately.
- Default/Max: Doorkeeper's default of 7200s if nil, doesn't change.
Database Review
- Adds one new JSONB column (
oauth_settings) toapplication_settingswithDEFAULT '{}'andNOT NULL. This is a small, low-traffic table. - A
CHECKconstraint (jsonb_typeof(oauth_settings) = 'object') is addedNOT VALIDand then validated in the same migration to avoid a full table lock. - No changes to the
oauth_access_tokenstable.
bin/rails db:migrate
main: == 20260522183111 AddOauthSettingsToApplicationSettings: migrating ============
main: -- transaction_open?(nil)↵
main: -> 0.0000s↵
main: -- add_column(:application_settings, :oauth_settings, :jsonb, {:default=>{}, :null=>false})
main: -> 0.0019s
main: -- transaction_open?(nil)↵
main: -> 0.0000s↵
main: -- transaction_open?(nil)↵
main: -> 0.0000s↵
main: -- execute("ALTER TABLE application_settings\n ADD CONSTRAINT check_application_settings_oauth_settings_is_hash\n CHECK ( (jsonb_typeof(oauth_settings) = 'object') )\n NOT VALID;\n")
main: -> 0.0006s
main: -- execute("SET statement_timeout TO 0")↵
main: -> 0.0001s↵
main: -- execute("ALTER TABLE application_settings VALIDATE CONSTRAINT check_application_settings_oauth_settings_is_hash;")
main: -> 0.0023s
main: -- execute("RESET statement_timeout")↵
main: -> 0.0002s↵
main: == 20260522183111 AddOauthSettingsToApplicationSettings: migrated (0.0146s) ===
main: == [advisory_lock_connection] object_id: 151220, pg_backend_pid: 67983↵
ci: == [advisory_lock_connection] object_id: 151220, pg_backend_pid: 67985↵
ci: == 20260522183111 AddOauthSettingsToApplicationSettings: migrating ============↵
ci: -- transaction_open?(nil)↵
ci: -> 0.0000s↵
ci: -- add_column(:application_settings, :oauth_settings, :jsonb, {:default=>{}, :null=>false})↵
ci: -> 0.0028s↵
ci: -- transaction_open?(nil)↵
ci: -> 0.0000s↵
ci: -- transaction_open?(nil)↵
ci: -> 0.0000s↵
ci: -- execute("ALTER TABLE application_settings\nADD CONSTRAINT check_application_settings_oauth_settings_is_hash\nCHECK ( (jsonb_typeof(oauth_settings) = 'object') )\nNOT VALID;\n")↵
ci: -> 0.0006s↵
ci: -- execute("SET statement_timeout TO 0")↵
ci: -> 0.0001s↵
ci: -- execute("ALTER TABLE application_settings VALIDATE CONSTRAINT check_application_settings_oauth_settings_is_hash;")↵
ci: -> 0.0008s↵
ci: -- execute("RESET statement_timeout")↵
ci: -> 0.0002s↵
ci: == 20260522183111 AddOauthSettingsToApplicationSettings: migrated (0.0158s) ===↵Documentation Review
See !237363 (merged)
Screenshots & Recordings
| Before | After | After (api) |
|---|---|---|
Steps to validate
- Run the migrations:
bundle exec rails db:migrate - In the Admin area, go to Settings > General > Account and limit.
- Confirm the default 7200s default.
- Set OAuth access token expiration to
900(15 minutes) and save. - Obtain a new OAuth access token via the authorization code flow and confirm
expires_inin the response is900s. - Clear the field and save — confirm tokens revert to the 7200-second default.
- Via the API:
GET /api/v4/application/settingsand verifyoauth_access_token_expires_in: 7200 - Via the API:
PUT /api/v4/application/settingswithoauth_access_token_expires_in=299— confirm a400 Bad Requestresponse. - Via the API:
PUT /api/v4/application/settingswithoauth_access_token_expires_in=7201— confirm a400 Bad Requestresponse.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.