Class: Ask::Auth::Providers::OAuth
- Inherits:
-
Object
- Object
- Ask::Auth::Providers::OAuth
- Defined in:
- lib/ask/auth/providers/oauth.rb
Overview
Authorization-code PKCE OAuth flow for interactive credential authorization (e.g. bring-your-own-subscription).
provider = Ask::Auth::Providers::OAuth.new(
client_id: "...", authorize_url: "...", token_url: "...",
redirect_uri: "https://app.example/oauth/callback", scope: "..."
)
url = provider.(user: current_user)
# redirect user to url; the callback receives ?code=...&state=...
tokens = provider.(user: current_user, code: params[:code])
# { token:, refresh_token:, expires_at:, raw: {...} }
The code verifier is generated during #authorize_url. Persist it via a storage object that responds to #store(name, user:, value:) / #fetch(name, user:) — the app implements it (e.g. on its OAuth state table) — or pass code_verifier: back into #authorize! yourself.
Subclasses override #parse_token_response to add provider-specific fields (e.g. an account id from the id_token).
Direct Known Subclasses
Instance Attribute Summary collapse
-
#client_id ⇒ Object
readonly
Returns the value of attribute client_id.
-
#redirect_uri ⇒ Object
readonly
Returns the value of attribute redirect_uri.
Instance Method Summary collapse
-
#authorize!(user:, code:, code_verifier: nil, redirect_uri: nil) ⇒ Object
Exchange an authorization code for tokens.
-
#authorize_url(user:, verifier: nil, state: nil, extra_params: {}) ⇒ Object
Returns the authorization URL to redirect the user to.
-
#call(name, user: nil) ⇒ Object
Returns nil (no automatic resolution) — OAuth requires interactive flow.
-
#generate_code_challenge(verifier) ⇒ Object
Generate a PKCE code challenge (SHA256 base64 digest of verifier).
-
#generate_code_verifier ⇒ Object
Generate a PKCE code verifier (128-char alphanumeric string).
-
#initialize(storage: nil, client_id: nil, authorize_url: nil, token_url: nil, redirect_uri: nil, scope: nil, http: Ask::Auth::OAuth::HTTP) ⇒ OAuth
constructor
A new instance of OAuth.
-
#refresh(refresh_token:) ⇒ Object
Refresh an access token using a refresh token grant.
Constructor Details
#initialize(storage: nil, client_id: nil, authorize_url: nil, token_url: nil, redirect_uri: nil, scope: nil, http: Ask::Auth::OAuth::HTTP) ⇒ OAuth
Returns a new instance of OAuth.
34 35 36 37 38 39 40 41 42 43 |
# File 'lib/ask/auth/providers/oauth.rb', line 34 def initialize(storage: nil, client_id: nil, authorize_url: nil, token_url: nil, redirect_uri: nil, scope: nil, http: Ask::Auth::OAuth::HTTP) @storage = storage @client_id = client_id @authorize_url = @token_url = token_url @redirect_uri = redirect_uri @scope = scope @http = http end |
Instance Attribute Details
#client_id ⇒ Object (readonly)
Returns the value of attribute client_id.
32 33 34 |
# File 'lib/ask/auth/providers/oauth.rb', line 32 def client_id @client_id end |
#redirect_uri ⇒ Object (readonly)
Returns the value of attribute redirect_uri.
32 33 34 |
# File 'lib/ask/auth/providers/oauth.rb', line 32 def redirect_uri @redirect_uri end |
Instance Method Details
#authorize!(user:, code:, code_verifier: nil, redirect_uri: nil) ⇒ Object
Exchange an authorization code for tokens.
Returns { token:, refresh_token:, expires_at:, raw: ... } (plus any subclass-added keys). Raises OAuthError on transport or provider errors.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/ask/auth/providers/oauth.rb', line 93 def (user:, code:, code_verifier: nil, redirect_uri: nil) verifier = code_verifier || fetch_oauth_state(user, :verifier) if verifier.to_s.empty? raise OAuthError, "missing code verifier — pass code_verifier or persist it via a storage object" end body = token_exchange( grant_type: "authorization_code", code: code, redirect_uri: redirect_uri || redirect_uri_value, client_id: @client_id, code_verifier: verifier ) parse_token_response(body) end |
#authorize_url(user:, verifier: nil, state: nil, extra_params: {}) ⇒ Object
Returns the authorization URL to redirect the user to. The code verifier (and state) are persisted via the storage object when one is configured, so the callback can recover them.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/ask/auth/providers/oauth.rb', line 66 def (user:, verifier: nil, state: nil, extra_params: {}) verifier ||= generate_code_verifier state ||= SecureRandom.hex(16) challenge = generate_code_challenge(verifier) persist_oauth_state(user, verifier, state) params = { response_type: "code", client_id: @client_id, redirect_uri: redirect_uri_value, scope: @scope.to_s, state: state, code_challenge: challenge, code_challenge_method: "S256" }.merge(extra_params) uri = URI.parse(@authorize_url || "https://example.com/oauth/authorize") uri.query = URI.encode_www_form(params) uri.to_s end |
#call(name, user: nil) ⇒ Object
Returns nil (no automatic resolution) — OAuth requires interactive flow.
46 47 48 |
# File 'lib/ask/auth/providers/oauth.rb', line 46 def call(name, user: nil) nil end |
#generate_code_challenge(verifier) ⇒ Object
Generate a PKCE code challenge (SHA256 base64 digest of verifier).
56 57 58 59 60 61 |
# File 'lib/ask/auth/providers/oauth.rb', line 56 def generate_code_challenge(verifier) ::Base64.urlsafe_encode64( OpenSSL::Digest.digest("SHA256", verifier), padding: false ) end |
#generate_code_verifier ⇒ Object
Generate a PKCE code verifier (128-char alphanumeric string).
51 52 53 |
# File 'lib/ask/auth/providers/oauth.rb', line 51 def generate_code_verifier SecureRandom.alphanumeric(128) end |
#refresh(refresh_token:) ⇒ Object
Refresh an access token using a refresh token grant.
110 111 112 113 114 115 116 117 |
# File 'lib/ask/auth/providers/oauth.rb', line 110 def refresh(refresh_token:) body = token_exchange( grant_type: "refresh_token", refresh_token: refresh_token, client_id: @client_id ) parse_token_response(body) end |