Class: Google::Auth::UserAuthorizer

Inherits:
Object
  • Object
show all
Defined in:
lib/googleauth/user_authorizer.rb

Overview

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
...

Direct Known Subclasses

WebUserAuthorizer

Constant Summary collapse

MISMATCHED_CLIENT_ID_ERROR =
"Token client ID of %s does not match configured client id %s".freeze
NIL_CLIENT_ID_ERROR =
"Client id can not be nil.".freeze
NIL_SCOPE_ERROR =
"Scope can not be nil.".freeze
NIL_USER_ID_ERROR =
"User ID can not be nil.".freeze
NIL_TOKEN_STORE_ERROR =
"Can not call method if token store is nil".freeze
MISSING_ABSOLUTE_URL_ERROR =
'Absolute base url required for relative callback url "%s"'.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client_id, scope, token_store, legacy_callback_uri = nil, callback_uri: nil, code_verifier: nil) ⇒ UserAuthorizer

Initialize the authorizer

Parameters:

  • client_id (Google::Auth::ClientID)

    Configured ID & secret for this application

  • scope (String, Array<String>)

    Authorization scope to request

  • token_store (Google::Auth::Stores::TokenStore)

    Backing storage for persisting user credentials

  • legacy_callback_uri (String) (defaults to: nil)

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

  • code_verifier (String) (defaults to: nil)

    Random string of 43-128 chars used to verify the key exchange using PKCE.

Raises:



66
67
68
69
70
71
72
73
74
75
76
77
78
# 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

Class Method Details

.generate_code_verifierObject

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



267
268
269
270
# File 'lib/googleauth/user_authorizer.rb', line 267

def self.generate_code_verifier
  random_number = rand 32..96
  SecureRandom.alphanumeric random_number
end

Instance Method Details

#code_verifier=(new_code_verifier) ⇒ Object

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.

Parameters:

  • new_code_erifier (String|nil)


261
262
263
# 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 = {}) ⇒ Google::Auth::UserRefreshCredentials

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

Parameters:

  • user_id (String)

    Unique ID of the user for loading/storing credentials.

  • code (String)

    The authorization code from the OAuth callback

  • scope (String, Array<String>)

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

  • base_url (String)

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

Returns:



213
214
215
216
# 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 = {}) ⇒ String

Build the URL for requesting authorization.

Parameters:

  • login_hint (String)

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

  • state (String)

    Opaque state value to be returned to the oauth callback.

  • base_url (String)

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

  • scope (String, Array<String>)

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

  • additional_parameters (Hash)

    Additional query parameters to be added to the authorization URL.

Returns:

  • (String)

    Authorization url



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# 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) ⇒ Google::Auth::UserRefreshCredentials

Fetch stored credentials for the user.

Parameters:

  • user_id (String)

    Unique ID of the user for loading/storing credentials.

  • scope (Array<String>, String) (defaults to: nil)

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

Returns:



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# 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 = {}) ⇒ Google::Auth::UserRefreshCredentials

Exchanges an authorization code returned in the oauth callback

Parameters:

  • user_id (String)

    Unique ID of the user for loading/storing credentials.

  • code (String)

    The authorization code from the OAuth callback

  • scope (String, Array<String>)

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

  • base_url (String)

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

  • additional_parameters (Hash)

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

Returns:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# 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) ⇒ Object

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

Parameters:

  • user_id (String)

    Unique ID of the user for loading/storing credentials.



223
224
225
226
227
228
229
230
231
232
233
# 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) ⇒ Object

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

Parameters:



243
244
245
246
247
248
249
250
251
252
253
# 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