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_in as 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_settings JSONB column on application_settings, following the established pattern for grouping related settings without widening the OAuthAccessTokens table with individual columns.

  • A nil value 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_in is changed from a static value to a lazy block that reads from custom_access_token_expires_in -> Gitlab::CurrentSettings at 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) to application_settings with DEFAULT '{}' and NOT NULL. This is a small, low-traffic table.
  • A CHECK constraint (jsonb_typeof(oauth_settings) = 'object') is added NOT VALID and then validated in the same migration to avoid a full table lock.
  • No changes to the oauth_access_tokens table.
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

  1. Run the migrations: bundle exec rails db:migrate
  2. In the Admin area, go to Settings > General > Account and limit.
  3. Confirm the default 7200s default.
  4. Set OAuth access token expiration to 900 (15 minutes) and save.
  5. Obtain a new OAuth access token via the authorization code flow and confirm expires_in in the response is 900s.
  6. Clear the field and save — confirm tokens revert to the 7200-second default.
  7. Via the API: GET /api/v4/application/settings and verify oauth_access_token_expires_in: 7200
  8. Via the API: PUT /api/v4/application/settings with oauth_access_token_expires_in=299 — confirm a 400 Bad Request response.
  9. Via the API: PUT /api/v4/application/settings with oauth_access_token_expires_in=7201 — confirm a 400 Bad Request response.

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.

Edited by Hakeem Abdul-Razak

Merge request reports

Loading