Class: Mcp::Auth::OauthController
- Inherits:
-
ApplicationController
- Object
- ApplicationController
- Mcp::Auth::OauthController
- Defined in:
- app/controllers/mcp/auth/oauth_controller.rb
Instance Method Summary collapse
-
#approve ⇒ Object
Consent approval endpoint.
-
#authorize ⇒ Object
OAuth 2.1 Authorization endpoint (GET/POST).
-
#introspect ⇒ Object
RFC 7662: Token Introspection Requires client authentication.
-
#register ⇒ Object
RFC 7591: Dynamic Client Registration.
-
#revoke ⇒ Object
RFC 7009: Token Revocation Requires client authentication; only revokes tokens that belong to the authenticated client (otherwise still returns 200 to avoid leaking which tokens exist — per RFC 7009 §2.2).
-
#token ⇒ Object
OAuth 2.1 Token endpoint.
-
#userinfo ⇒ Object
OpenID Connect UserInfo Endpoint.
Instance Method Details
#approve ⇒ Object
Consent approval endpoint
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 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 27 def approve return redirect_to main_app.new_user_session_path unless mcp_user_signed_in? return render_error('invalid_request', 'Missing required parameters') unless if params[:approved] == 'true' # Get selected scopes from checkboxes selected_scopes = Array(params[:scopes]).compact.reject(&:blank?) Rails.logger.info "[OAuth] User selected scopes: #{selected_scopes.inspect}" # Validate selected scopes if selected_scopes.blank? Rails.logger.warn '[OAuth] No scopes selected' return render_error('invalid_request', 'At least one scope must be selected') end # Get originally requested scopes requested_scopes = params[:scope]&.split || [] # Get required scopes from the requested list required_scopes = get_required_scopes(requested_scopes) # Check all required scopes are selected missing_required = required_scopes - selected_scopes if missing_required.any? Rails.logger.warn "[OAuth] Missing required scopes: #{missing_required.join(', ')}" return render_error('invalid_request', 'Required scopes must be selected') end approved_scopes = Mcp::Auth::ScopeRegistry.validate_scopes(selected_scopes) # Preserve standard OpenID Connect scopes that were originally requested. # They gate identity claims (already governed by the userinfo/id_token # endpoints) rather than application resources, so they are not rendered # as individual consent checkboxes but must survive the approval step. oidc_scopes = requested_scopes & Mcp::Auth::ScopeRegistry::STANDARD_OIDC_SCOPES approved_scope_string = (approved_scopes + oidc_scopes).uniq.join(' ') # Generate authorization code with ONLY approved scopes generate_and_redirect_with_code(approved_scope_string) else redirect_with_error('access_denied', 'User denied the request') end end |
#authorize ⇒ Object
OAuth 2.1 Authorization endpoint (GET/POST)
12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 12 def Rails.logger.info "[OAuth] Authorization request for client=#{params[:client_id]} scope=#{params[:scope]}" unless return render_error('invalid_request', 'Missing or invalid required parameters') end if mcp_user_signed_in? handle_signed_in_user else redirect_to_login end end |
#introspect ⇒ Object
RFC 7662: Token Introspection Requires client authentication. Tokens not owned by the authenticated client are reported as ‘false` to prevent token-scanning.
125 126 127 128 129 130 131 132 133 134 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 125 def introspect client = authenticate_client return render_error('invalid_client', 'Client authentication failed', status: :unauthorized) unless client token = params[:token] return render json: { active: false }, content_type: 'application/json' if token.blank? response = introspect_token_for_client(token, client) render json: response, content_type: 'application/json' end |
#register ⇒ Object
RFC 7591: Dynamic Client Registration
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 85 def register Rails.logger.info '[OAuth] Client registration request' begin client_data = build_client_registration oauth_client = Mcp::Auth::OauthClient.create!(client_data) Rails.logger.info "[OAuth] Client registered: #{oauth_client.client_id}" render json: format_client_response(oauth_client), content_type: 'application/json' rescue ActiveRecord::RecordInvalid => e render_error('invalid_client_metadata', e.) rescue ArgumentError => e render_error('invalid_request', e.) rescue StandardError => e Rails.logger.error "[OAuth] Registration error: #{e.}" render_error('server_error', 'An unexpected error occurred') end end |
#revoke ⇒ Object
RFC 7009: Token Revocation Requires client authentication; only revokes tokens that belong to the authenticated client (otherwise still returns 200 to avoid leaking which tokens exist — per RFC 7009 §2.2).
108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 108 def revoke client = authenticate_client return render_error('invalid_client', 'Client authentication failed', status: :unauthorized) unless client token = params[:token] return render_error('invalid_request', 'Token parameter is required') if token.blank? revoked = revoke_token_for_client(token, client, hint: params[:token_type_hint]) # RFC 7009: Always return 200 OK regardless of whether the token was found. Rails.logger.info "[OAuth] Token revocation by client=#{client.client_id}: #{revoked ? 'success' : 'not found / not owned'}" head :ok end |
#token ⇒ Object
OAuth 2.1 Token endpoint
73 74 75 76 77 78 79 80 81 82 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 73 def token case params[:grant_type] when 'authorization_code' when 'refresh_token' handle_refresh_token_grant else render_error('unsupported_grant_type', 'Grant type not supported') end end |
#userinfo ⇒ Object
OpenID Connect UserInfo Endpoint
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 137 def userinfo auth_header = request.headers['Authorization'] return render json: { error: 'invalid_token' }, status: :unauthorized unless auth_header&.start_with?('Bearer ') token = auth_header.split(' ', 2).last payload = Services::TokenService.validate_access_token(token) return render json: { error: 'invalid_token' }, status: :unauthorized unless payload user_info = { sub: payload[:sub], email: payload[:email], email_verified: true, name: payload[:email], preferred_username: payload[:email] } user_info[:org] = payload[:org] if payload[:org] render json: user_info, content_type: 'application/json' end |