Class: Mcp::Auth::OauthController

Inherits:
ApplicationController
  • Object
show all
Defined in:
app/controllers/mcp/auth/oauth_controller.rb

Instance Method Summary collapse

Instance Method Details

#approveObject

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 valid_authorization_params?
    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

#authorizeObject

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 authorize
  Rails.logger.info "[OAuth] Authorization request: #{params.inspect}"

  unless valid_authorization_params?
    return render_error('invalid_request', 'Missing or invalid required parameters')
  end

  if user_signed_in?
    handle_signed_in_user
  else
    
  end
end

#introspectObject

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

#registerObject

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.message)
  rescue ArgumentError => e
    render_error('invalid_request', e.message)
  rescue StandardError => e
    Rails.logger.error "[OAuth] Registration error: #{e.message}"
    render_error('server_error', 'An unexpected error occurred')
  end
end

#revokeObject

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

#tokenObject

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'
    handle_authorization_code_grant
  when 'refresh_token'
    handle_refresh_token_grant
  else
    render_error('unsupported_grant_type', 'Grant type not supported')
  end
end

#userinfoObject

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

   = {
    sub: payload[:sub],
    email: payload[:email],
    email_verified: true,
    name: payload[:email],
    preferred_username: payload[:email]
  }
  [:org] = payload[:org] if payload[:org]

  render json: , content_type: 'application/json'
end