Class: Mcp::Auth::Services::AuthorizationService
- Inherits:
-
Object
- Object
- Mcp::Auth::Services::AuthorizationService
- Defined in:
- lib/mcp/auth/services/authorization_service.rb
Class Method Summary collapse
-
.consume_authorization_code(code) ⇒ Object
Consume authorization code (one-time use).
-
.generate_authorization_code(params, user:, org:) ⇒ Object
Generate authorization code with PKCE support.
-
.validate_authorization_code(code) ⇒ Object
Validate authorization code without consuming it.
-
.validate_pkce?(code_challenge, code_verifier) ⇒ Boolean
Validate PKCE challenge (RFC 7636).
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 (code) = Mcp::Auth::AuthorizationCode.find_by(code: code) return nil unless code_data = { client_id: .client_id, redirect_uri: .redirect_uri, code_challenge: .code_challenge, code_challenge_method: .code_challenge_method, resource: .resource, scope: .scope, user_id: .user_id, org_id: .org_id, created_at: .created_at.to_i } deleted = Mcp::Auth::AuthorizationCode.where(id: .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 (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 = 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: .minutes.from_now ) Rails.logger.info "[AuthorizationService] Authorization code generated for user #{user.id}" .code rescue ActiveRecord::RecordInvalid => e Rails.logger.error "[AuthorizationService] Failed to create authorization code: #{e.}" 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 (code) return nil if code.blank? = Mcp::Auth::AuthorizationCode.active.find_by(code: code) return nil unless { client_id: .client_id, redirect_uri: .redirect_uri, code_challenge: .code_challenge, code_challenge_method: .code_challenge_method, resource: .resource, scope: .scope, user_id: .user_id, org_id: .org_id, created_at: .created_at.to_i } end |
.validate_pkce?(code_challenge, code_verifier) ⇒ Boolean
Validate PKCE challenge (RFC 7636)
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.}" false end |