Fix OAuth Device Flow verification_uri ignoring relative_url_root

What does this MR do and why?

Closes #602380 (closed).

On an instance configured with a relative_url_root (e.g. external_url 'https://host.example/gitlab/' in gitlab.rb), the OAuth 2.0 Device Authorization Grant (RFC 8628) response's verification_uri and verification_uri_complete omit the /gitlab prefix, pointing device-flow clients at a URL that 404s.

Root cause

The doorkeeper-device_authorization_grant gem's default verification_uri/verification_uri_complete procs (lib/doorkeeper/device_authorization_grant/config.rb) build the URI from a host_name the gem computes itself, purely from the raw request:

# doorkeeper-device_authorization_grant gem
def host_name
  req = server.context.request
  "#{req.scheme}://#{req.host}#{port}"
end

This never accounts for relative_url_root — GitLab's own config/initializers/default_url_options.rb already handles that via script_name: Gitlab.config.gitlab.relative_url_root, but that only applies to actual Rails route URL helpers, which the gem bypasses entirely by string-concatenating host_name with a hardcoded /oauth/device path.

Fix

config/initializers/doorkeeper_device_authorization_grant.rb already had the override procs present but commented out as an example. Enabled them, replacing the gem's raw host-based construction with the actual route: Gitlab::Routing.url_helpers.oauth_device_authorizations_index_url, which resolves through the relative_url_root-aware default_url_options GitLab already sets up everywhere else.

This matches the established convention for every other OAuth discovery/metadata endpoint in this codebase — JwksController#provider (.well-known/oauth-authorization-server) and the Doorkeeper::OpenidConnect::DiscoveryController superclass it inherits from both advertise the canonically-configured URL rather than the request host, and GitLab's own doorkeeper_openid_connect.rb initializer pins issuer Gitlab.config.gitlab.url on the same basis.

Screenshots or screen recordings

N/A — backend-only change.

How to set up and validate locally

Only spec/requests/oauth/flows/device_grant_spec.rb changed for this fix (new relative_url_root context asserting both verification_uri and verification_uri_complete include the configured prefix). spec/controllers/oauth/device_authorizations_controller_spec.rb is included below only as a regression check, since it exercises the same controller family:

bin/rspec spec/requests/oauth/flows/device_grant_spec.rb spec/controllers/oauth/device_authorizations_controller_spec.rb

Manual verification on GDK (no gitlab.yml edit or restart needed — this simulates relative_url_root live in a bin/rails console, the same way the spec above stubs it):

Gitlab::Application.routes.default_url_options =
  Gitlab::Application.routes.default_url_options.merge(script_name: '/gitlab')

verification_uri = Doorkeeper::DeviceAuthorizationGrant.configuration.verification_uri.call(nil)
puts verification_uri
# => http://127.0.0.1:3000/gitlab/oauth/device

device_grant = Doorkeeper::DeviceAuthorizationGrant::DeviceGrant.new(user_code: 'ABCD-1234')
puts Doorkeeper::DeviceAuthorizationGrant.configuration.verification_uri_complete.call(verification_uri, nil, device_grant)
# => http://127.0.0.1:3000/gitlab/oauth/device?user_code=ABCD-1234

Output above is real, captured on this branch on a local GDK.

Disclosure

This fix was drafted with AI assistance (Claude Code). Root cause was traced into the doorkeeper-device_authorization_grant gem's source (installed gem, not vendored) to confirm exactly how host_name is built, then verified against GitLab's own default_url_options initializer. The fix and tests were AI-assisted, verified via a full local run of the affected spec files (25 examples, 0 failures) rather than taken on faith.

Edited by Sergey Pechenko

Merge request reports

Loading
Loading