Class: Mcp::Auth::Services::AuthorizationService

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp/auth/services/authorization_service.rb

Class Method Summary collapse

Class Method Details

.consume_authorization_code(code) ⇒ Object

Consume authorization code (one-time use).

OAuth 2.1 §4.1.2: an authorization code MUST be single-use. The delete is done as a single atomic DELETE … WHERE that reports how many rows it removed, so when two requests race to redeem the same code exactly ONE sees ‘deleted == 1` and proceeds; the loser sees 0 and gets nil. Returns the code’s data on success, nil if the code was already consumed (or never existed).



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/mcp/auth/services/authorization_service.rb', line 63

def consume_authorization_code(code)
  authorization_code = Mcp::Auth::AuthorizationCode.find_by(code: code)
  return nil unless authorization_code

  code_data = {
    client_id: authorization_code.client_id,
    redirect_uri: authorization_code.redirect_uri,
    code_challenge: authorization_code.code_challenge,
    code_challenge_method: authorization_code.code_challenge_method,
    resource: authorization_code.resource,
    scope: authorization_code.scope,
    user_id: authorization_code.user_id,
    org_id: authorization_code.org_id,
    created_at: authorization_code.created_at.to_i
  }

  deleted = Mcp::Auth::AuthorizationCode.where(id: authorization_code.id).delete_all
  return nil unless deleted == 1

  Rails.logger.info '[AuthorizationService] Authorization code consumed'
  code_data
end

.generate_authorization_code(params, user:, org:) ⇒ Object

Generate authorization code with PKCE support



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/mcp/auth/services/authorization_service.rb', line 9

def generate_authorization_code(params, user:, org:)
  code = SecureRandom.hex(32)

  # Use provided scope or default to all registered scopes
  scope = params[:scope].presence || Mcp::Auth::ScopeRegistry.default_scope_string

  authorization_code = Mcp::Auth::AuthorizationCode.create!(
    code: code,
    client_id: params[:client_id],
    redirect_uri: params[:redirect_uri],
    code_challenge: params[:code_challenge],
    code_challenge_method: params[:code_challenge_method],
    resource: params[:resource],
    scope: scope,
    user: user,
    org: org,
    expires_at: authorization_code_lifetime.minutes.from_now
  )

  Rails.logger.info "[AuthorizationService] Authorization code generated for user #{user.id}"
  authorization_code.code
rescue ActiveRecord::RecordInvalid => e
  Rails.logger.error "[AuthorizationService] Failed to create authorization code: #{e.message}"
  nil
end

.validate_authorization_code(code) ⇒ Object

Validate authorization code without consuming it



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/mcp/auth/services/authorization_service.rb', line 36

def validate_authorization_code(code)
  return nil if code.blank?

  authorization_code = Mcp::Auth::AuthorizationCode.active.find_by(code: code)
  return nil unless authorization_code

  {
    client_id: authorization_code.client_id,
    redirect_uri: authorization_code.redirect_uri,
    code_challenge: authorization_code.code_challenge,
    code_challenge_method: authorization_code.code_challenge_method,
    resource: authorization_code.resource,
    scope: authorization_code.scope,
    user_id: authorization_code.user_id,
    org_id: authorization_code.org_id,
    created_at: authorization_code.created_at.to_i
  }
end

.validate_pkce?(code_challenge, code_verifier) ⇒ Boolean

Validate PKCE challenge (RFC 7636)

Returns:

  • (Boolean)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/mcp/auth/services/authorization_service.rb', line 87

def validate_pkce?(code_challenge, code_verifier)
  return false if code_verifier.blank? || code_challenge.blank?

  # S256 method: BASE64URL(SHA256(code_verifier))
  computed_challenge = Base64.urlsafe_encode64(
    Digest::SHA256.digest(code_verifier),
    padding: false
  )

  ActiveSupport::SecurityUtils.secure_compare(computed_challenge, code_challenge)
rescue StandardError => e
  Rails.logger.error "[AuthorizationService] PKCE validation error: #{e.message}"
  false
end