class Google::Auth::UserAuthorizer

Handles an interactive 3-Legged-OAuth2 (3LO) user consent authorization.

Example usage for a simple command line app:

credentials = authorizer.get_credentials(user_id)
if credentials.nil?
  url = authorizer.get_authorization_url(
    base_url: OOB_URI)
  puts "Open the following URL in the browser and enter the " +
       "resulting code after authorization"
  puts url
  code = gets
  credentials = authorizer.get_and_store_credentials_from_code(
    user_id: user_id, code: code, base_url: OOB_URI)
end
# Credentials ready to use, call APIs
...

Constants

MISMATCHED_CLIENT_ID_ERROR
MISSING_ABSOLUTE_URL_ERROR
NIL_CLIENT_ID_ERROR
NIL_SCOPE_ERROR
NIL_TOKEN_STORE_ERROR
NIL_USER_ID_ERROR

Public Class Methods

generate_code_verifier() click to toggle source

Generate the code verifier needed to be sent while fetching authorization URL.

# File lib/googleauth/user_authorizer.rb, line 267
def self.generate_code_verifier
  random_number = rand 32..96
  SecureRandom.alphanumeric random_number
end
new(client_id, scope, token_store, legacy_callback_uri = nil, callback_uri: nil, code_verifier: nil) click to toggle source

Initialize the authorizer

@param [Google::Auth::ClientID] client_id

Configured ID & secret for this application

@param [String, Array<String>] scope

Authorization scope to request

@param [Google::Auth::Stores::TokenStore] token_store

Backing storage for persisting user credentials

@param [String] legacy_callback_uri

URL (either absolute or relative) of the auth callback.
Defaults to '/oauth2callback'.
@deprecated This field is deprecated. Instead, use the keyword
 argument callback_uri.

@param [String] code_verifier

Random string of 43-128 chars used to verify the key exchange using
PKCE.
# File lib/googleauth/user_authorizer.rb, line 66
def initialize client_id, scope, token_store,
               legacy_callback_uri = nil,
               callback_uri: nil,
               code_verifier: nil
  raise NIL_CLIENT_ID_ERROR if client_id.nil?
  raise NIL_SCOPE_ERROR if scope.nil?

  @client_id = client_id
  @scope = Array(scope)
  @token_store = token_store
  @callback_uri = legacy_callback_uri || callback_uri || "/oauth2callback"
  @code_verifier = code_verifier
end

Public Instance Methods

code_verifier=(new_code_verifier) click to toggle source

The code verifier for PKCE for OAuth 2.0. When set, the authorization URI will contain the Code Challenge and Code Challenge Method querystring parameters, and the token URI will contain the Code Verifier parameter.

@param [String|nil] new_code_erifier

# File lib/googleauth/user_authorizer.rb, line 261
def code_verifier= new_code_verifier
  @code_verifier = new_code_verifier
end
get_and_store_credentials_from_code(options = {}) click to toggle source

Exchanges an authorization code returned in the oauth callback. Additionally, stores the resulting credentials in the token store if the exchange is successful.

@param [String] user_id

Unique ID of the user for loading/storing credentials.

@param [String] code

The authorization code from the OAuth callback

@param [String, Array<String>] scope

Authorization scope requested. Overrides the instance
scopes if not nil.

@param [String] base_url

Absolute URL to resolve the configured callback uri against.
Required if the configured
callback uri is a relative.

@return [Google::Auth::UserRefreshCredentials]

Credentials if exchange is successful
# File lib/googleauth/user_authorizer.rb, line 213
def get_and_store_credentials_from_code options = {}
  credentials = get_credentials_from_code options
  store_credentials options[:user_id], credentials
end
get_authorization_url(options = {}) click to toggle source

Build the URL for requesting authorization.

@param [String] login_hint

Login hint if need to authorize a specific account. Should be a
user's email address or unique profile ID.

@param [String] state

Opaque state value to be returned to the oauth callback.

@param [String] base_url

Absolute URL to resolve the configured callback uri against. Required
if the configured callback uri is a relative.

@param [String, Array<String>] scope

Authorization scope to request. Overrides the instance scopes if not
nil.

@param [Hash] additional_parameters

Additional query parameters to be added to the authorization URL.

@return [String]

Authorization url
# File lib/googleauth/user_authorizer.rb, line 97
def get_authorization_url options = {}
  scope = options[:scope] || @scope

  options[:additional_parameters] ||= {}

  if @code_verifier
    options[:additional_parameters].merge!(
      {
        code_challenge: generate_code_challenge(@code_verifier),
        code_challenge_method: code_challenge_method
      }
    )
  end

  credentials = UserRefreshCredentials.new(
    client_id:     @client_id.id,
    client_secret: @client_id.secret,
    scope:         scope,
    additional_parameters: options[:additional_parameters]
  )
  redirect_uri = redirect_uri_for options[:base_url]
  url = credentials.authorization_uri(access_type:            "offline",
                                      redirect_uri:           redirect_uri,
                                      approval_prompt:        "force",
                                      state:                  options[:state],
                                      include_granted_scopes: true,
                                      login_hint:             options[:login_hint])
  url.to_s
end
get_credentials(user_id, scope = nil) click to toggle source

Fetch stored credentials for the user.

@param [String] user_id

Unique ID of the user for loading/storing credentials.

@param [Array<String>, String] scope

If specified, only returns credentials that have all
the requested scopes

@return [Google::Auth::UserRefreshCredentials]

Stored credentials, nil if none present
# File lib/googleauth/user_authorizer.rb, line 136
def get_credentials user_id, scope = nil
  saved_token = stored_token user_id
  return nil if saved_token.nil?
  data = MultiJson.load saved_token

  if data.fetch("client_id", @client_id.id) != @client_id.id
    raise format(MISMATCHED_CLIENT_ID_ERROR,
                 data["client_id"], @client_id.id)
  end

  credentials = UserRefreshCredentials.new(
    client_id:     @client_id.id,
    client_secret: @client_id.secret,
    scope:         data["scope"] || @scope,
    access_token:  data["access_token"],
    refresh_token: data["refresh_token"],
    expires_at:    data.fetch("expiration_time_millis", 0) / 1000
  )
  scope ||= @scope
  return monitor_credentials user_id, credentials if credentials.includes_scope? scope
  nil
end
get_credentials_from_code(options = {}) click to toggle source

Exchanges an authorization code returned in the oauth callback

@param [String] user_id

Unique ID of the user for loading/storing credentials.

@param [String] code

The authorization code from the OAuth callback

@param [String, Array<String>] scope

Authorization scope requested. Overrides the instance
scopes if not nil.

@param [String] base_url

Absolute URL to resolve the configured callback uri against.
Required if the configured
callback uri is a relative.

@param [Hash] additional_parameters

Additional parameters to be added to the post body of token
endpoint request.

@return [Google::Auth::UserRefreshCredentials]

Credentials if exchange is successful
# File lib/googleauth/user_authorizer.rb, line 177
def get_credentials_from_code options = {}
  user_id = options[:user_id]
  code = options[:code]
  scope = options[:scope] || @scope
  base_url = options[:base_url]
  options[:additional_parameters] ||= {}
  options[:additional_parameters].merge!({ code_verifier: @code_verifier })
  credentials = UserRefreshCredentials.new(
    client_id:             @client_id.id,
    client_secret:         @client_id.secret,
    redirect_uri:          redirect_uri_for(base_url),
    scope:                 scope,
    additional_parameters: options[:additional_parameters]
  )
  credentials.code = code
  credentials.fetch_access_token!({})
  monitor_credentials user_id, credentials
end
revoke_authorization(user_id) click to toggle source

Revokes a user’s credentials. This both revokes the actual grant as well as removes the token from the token store.

@param [String] user_id

Unique ID of the user for loading/storing credentials.
# File lib/googleauth/user_authorizer.rb, line 223
def revoke_authorization user_id
  credentials = get_credentials user_id
  if credentials
    begin
      @token_store.delete user_id
    ensure
      credentials.revoke!
    end
  end
  nil
end
store_credentials(user_id, credentials) click to toggle source

Store credentials for a user. Generally not required to be called directly, but may be used to migrate tokens from one store to another.

@param [String] user_id

Unique ID of the user for loading/storing credentials.

@param [Google::Auth::UserRefreshCredentials] credentials

Credentials to store.
# File lib/googleauth/user_authorizer.rb, line 243
def store_credentials user_id, credentials
  json = MultiJson.dump(
    client_id:              credentials.client_id,
    access_token:           credentials.access_token,
    refresh_token:          credentials.refresh_token,
    scope:                  credentials.scope,
    expiration_time_millis: credentials.expires_at.to_i * 1000
  )
  @token_store.store user_id, json
  credentials
end

Private Instance Methods

code_challenge_method() click to toggle source
# File lib/googleauth/user_authorizer.rb, line 322
def code_challenge_method
  "S256"
end
generate_code_challenge(code_verifier) click to toggle source
# File lib/googleauth/user_authorizer.rb, line 317
def generate_code_challenge code_verifier
  digest = Digest::SHA256.digest code_verifier
  Base64.urlsafe_encode64 digest, padding: false
end
monitor_credentials(user_id, credentials) click to toggle source

Begin watching a credential for refreshes so the access token can be saved.

@param [String] user_id

Unique ID of the user for loading/storing credentials.

@param [Google::Auth::UserRefreshCredentials] credentials

Credentials to store.
# File lib/googleauth/user_authorizer.rb, line 293
def monitor_credentials user_id, credentials
  credentials.on_refresh do |cred|
    store_credentials user_id, cred
  end
  credentials
end
redirect_uri_for(base_url) click to toggle source

Resolve the redirect uri against a base.

@param [String] base_url

Absolute URL to resolve the callback against if necessary.

@return [String]

Redirect URI
# File lib/googleauth/user_authorizer.rb, line 306
def redirect_uri_for base_url
  return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
  raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
  URI.join(base_url, @callback_uri).to_s
end
stored_token(user_id) click to toggle source

@private Fetch stored token with given user_id

@param [String] user_id

Unique ID of the user for loading/storing credentials.

@return [String] The saved token from @token_store

# File lib/googleauth/user_authorizer.rb, line 279
def stored_token user_id
  raise NIL_USER_ID_ERROR if user_id.nil?
  raise NIL_TOKEN_STORE_ERROR if @token_store.nil?

  @token_store.load user_id
end
uri_is_postmessage?(uri) click to toggle source

Check if URI is Google’s postmessage flow (not a valid redirect_uri by spec, but allowed)

# File lib/googleauth/user_authorizer.rb, line 313
def uri_is_postmessage? uri
  uri.to_s.casecmp("postmessage").zero?
end