Class: Hitch::RegistrationsController
- Inherits:
-
PublicEndpointController
- Object
- PublicEndpointController
- Hitch::RegistrationsController
- Includes:
- CorsSupport, RegistrationAdmission, UriValidation
- Defined in:
- app/controllers/hitch/registrations_controller.rb
Overview
POST /oauth/register — Dynamic Client Registration (RFC 7591). MCP clients register before starting the OAuth flow. The client_name they send ("Claude Code", "ChatGPT", etc.) is attacker-controllable — we persist it for audit fidelity but consent UIs MUST NOT trust it for display (see authorize#new).
Constant Summary
Constants included from RegistrationAdmission
Hitch::RegistrationAdmission::ADMITTED_HEADER
Constants included from RequestAdmission
Hitch::RequestAdmission::MAX_REQUEST_BODY_BYTES
Instance Method Summary collapse
Instance Method Details
#create ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'app/controllers/hitch/registrations_controller.rb', line 14 def create # RegistrationAdmission has already parsed one bounded JSON object # and installed it here — the action never runs otherwise. = request.request_parameters # RFC 7591 §2: the authorization server is responsible for # enforcing its URI policy at registration. Without this, a # client could register `javascript:alert(1)` or # `http://attacker.test/cb` and try to use it at authorize. normalized = () return if performed? candidate_uris = normalized.fetch(:redirect_uris) invalid = candidate_uris.reject { |uri| valid_redirect_uri?(uri) } if invalid.any? return oauth_error( "invalid_redirect_uri", "redirect_uris must contain only https URIs or RFC 8252 loopback http URIs" ) end auth_method = (, "token_endpoint_auth_method") || "none" return if performed? unless Hitch::Client::TOKEN_ENDPOINT_AUTH_METHODS.include?(auth_method) return oauth_error( "invalid_client_metadata", "token_endpoint_auth_method must be none or client_secret_basic" ) end application_type = (, "application_type") return if performed? client, client_secret = register_client( auth_method, client_name: ["client_name"], redirect_uris: candidate_uris, application_type: application_type ) response_body = { client_id: client.client_id, client_id_issued_at: client.created_at.to_i, client_name: client.client_name, redirect_uris: client.redirect_uris, grant_types: [ "authorization_code" ], response_types: [ "code" ], scope: Hitch.configuration.supported_scopes.join(" "), token_endpoint_auth_method: client.token_endpoint_auth_method }.merge( # Echo what was actually STORED, not what was sent — RFC 7591 # §3.2.1 makes the response the authoritative record of registered # metadata, so a client that sent an unrecognized value can tell # it was dropped by diffing its request against this response. # (Not that it reads as an explicit rejection: §2 defaults an # absent application_type to "web", so silence means the default, # not "you declared nothing".) # # Omitted rather than sent as null when undeclared. Scoped to this # one key deliberately: a blanket `.compact` would silently drop # any future nullable field, and §3.2.1 makes some of them — # `client_secret_expires_at` when a secret is issued — REQUIRED. client.application_type ? { application_type: client.application_type } : {} ) if client_secret response_body.merge!( client_secret: client_secret, client_secret_issued_at: client.client_secret_issued_at.to_i, client_secret_expires_at: 0 ) end render json: response_body, status: :created end |