Return JSON error body when /oauth/register is rate limited

What does this MR do and why?

Closes #602891 (closed).

POST /oauth/register (OAuth 2.0 Dynamic Client Registration, RFC 7591) returns a plain-text body when rate limited, while every other non-2xx response on this controller already returns JSON. RFC 6749 section 5.2 and RFC 7591 section 3.2.2 define a JSON error-response shape for OAuth endpoints, so standards-compliant clients fail to parse the body and surface a confusing parse error instead of a clean rate-limit signal — the issue documents this concretely against the MCP TypeScript SDK, which affects Claude Code, Cursor, Duo CLI, and mcp-remote.

Fix

Oauth::DynamicRegistrationsController#check_rate_limit used check_rate_limit!'s default (plain-text) response. This uses the concern's existing block form — already used elsewhere in the codebase (e.g. SessionsController) — to render the same JSON error shape this controller already uses for its 400 responses:

{ "error": "temporarily_unavailable", "error_description": "Rate limit exceeded, retry after 3600 seconds" }

Also adds a Retry-After header (seconds), derived from Gitlab::ApplicationRateLimiter.period_for(:oauth_dynamic_registration) rather than a hardcoded value, so it can't drift from the actual configured rate limit.

Screenshots or screen recordings

N/A — backend-only change.

How to set up and validate locally

bin/rspec spec/requests/oauth/dynamic_registrations_controller_spec.rb

New coverage added for the rate-limited path (JSON body, status, Retry-After header, and that no application is created). Note the controller currently skips rate limiting entirely in test/ development (return if Rails.env.test? || Rails.env.development?), so the new spec stubs Rails.env.test? to false to actually exercise the throttled branch — the same pattern already used elsewhere in the suite (e.g. spec/lib/gitlab/tracking/destinations/snowplow_spec.rb).

Disclosure

This fix was drafted with AI assistance (Claude Code). The issue itself was reported with an unusually thorough root-cause writeup (including the exact RFCs and a suggested fix scoped to this controller); the fix and tests were AI-assisted, verified via a full local run of the controller's spec file (58 examples, 0 failures) rather than taken on faith.

Merge request reports

Loading