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 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 27 def approve unless user_signed_in? return redirect_to main_app.new_user_session_path end unless return render_error('invalid_request', 'Missing required parameters') end 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) # Generate authorization code with ONLY approved scopes approved_scope_string = approved_scopes.join(' ') 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: #{params.inspect}" unless return render_error('invalid_request', 'Missing or invalid required parameters') end if 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.
124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 124 def introspect client = authenticate_client return render_error('invalid_client', 'Client authentication failed', status: :unauthorized) unless client token = params[:token] if token.blank? return render json: { active: false }, content_type: 'application/json' end response = introspect_token_for_client(token, client) render json: response, content_type: 'application/json' end |
#register ⇒ Object
RFC 7591: Dynamic Client Registration
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 82 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).
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 105 def revoke client = authenticate_client return render_error('invalid_client', 'Client authentication failed', status: :unauthorized) unless client token = params[:token] if token.blank? return render_error('invalid_request', 'Token parameter is required') end 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
70 71 72 73 74 75 76 77 78 79 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 70 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
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'app/controllers/mcp/auth/oauth_controller.rb', line 138 def userinfo auth_header = request.headers['Authorization'] unless auth_header&.start_with?('Bearer ') return render json: { error: 'invalid_token' }, status: :unauthorized end token = auth_header.split(' ', 2).last payload = Services::TokenService.validate_access_token(token) unless payload return render json: { error: 'invalid_token' }, status: :unauthorized end 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 |