Class: Google::Auth::WebUserAuthorizer

Inherits:
UserAuthorizer show all
Defined in:
lib/googleauth/web_user_authorizer.rb

Overview

Note:

Requires sessions are enabled

Varation on UserAuthorizer adapted for Rack based web applications.

Example usage:

get('/') do
  user_id = request.session['user_email']
  credentials = authorizer.get_credentials(user_id, request)
  if credentials.nil?
    redirect authorizer.get_authorization_url(user_id: user_id,
                                              request: request)
  end
  # Credentials are valid, can call APIs
  ...

end

get('/oauth2callback') do url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred( request) redirect url end

Instead of implementing the callback directly, applications are encouraged to use CallbackApp instead.

See Also:

Defined Under Namespace

Classes: CallbackApp

Constant Summary collapse

STATE_PARAM =
"state".freeze
AUTH_CODE_KEY =
"code".freeze
ERROR_CODE_KEY =
"error".freeze
SESSION_ID_KEY =
"session_id".freeze
CALLBACK_STATE_KEY =
"g-auth-callback".freeze
CURRENT_URI_KEY =
"current_uri".freeze
XSRF_KEY =
"g-xsrf-token".freeze
SCOPE_KEY =
"scope".freeze
NIL_REQUEST_ERROR =
"Request is required.".freeze
NIL_SESSION_ERROR =
"Sessions must be enabled".freeze
MISSING_AUTH_CODE_ERROR =
"Missing authorization code in request".freeze
AUTHORIZATION_ERROR =
"Authorization error: %s".freeze
INVALID_STATE_TOKEN_ERROR =
"State token does not match expected value".freeze

Constants inherited from UserAuthorizer

UserAuthorizer::MISMATCHED_CLIENT_ID_ERROR, UserAuthorizer::MISSING_ABSOLUTE_URL_ERROR, UserAuthorizer::NIL_CLIENT_ID_ERROR, UserAuthorizer::NIL_SCOPE_ERROR, UserAuthorizer::NIL_TOKEN_STORE_ERROR, UserAuthorizer::NIL_USER_ID_ERROR

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from UserAuthorizer

#code_verifier=, generate_code_verifier, #get_and_store_credentials_from_code, #get_credentials_from_code, #revoke_authorization, #store_credentials

Constructor Details

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

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.



104
105
106
107
108
109
110
111
112
# File 'lib/googleauth/web_user_authorizer.rb', line 104

def initialize client_id, scope, token_store,
               legacy_callback_uri = nil,
               callback_uri: nil,
               code_verifier: nil
  super client_id, scope, token_store,
        legacy_callback_uri,
        code_verifier: code_verifier,
        callback_uri: callback_uri
end

Class Attribute Details

.defaultObject

Returns the value of attribute default.



68
69
70
# File 'lib/googleauth/web_user_authorizer.rb', line 68

def default
  @default
end

Class Method Details

.extract_callback_state(request) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
# File 'lib/googleauth/web_user_authorizer.rb', line 205

def self.extract_callback_state request
  state = MultiJson.load(request.params[STATE_PARAM] || "{}")
  redirect_uri = state[CURRENT_URI_KEY]
  callback_state = {
    AUTH_CODE_KEY  => request.params[AUTH_CODE_KEY],
    ERROR_CODE_KEY => request.params[ERROR_CODE_KEY],
    SESSION_ID_KEY => state[SESSION_ID_KEY],
    SCOPE_KEY      => request.params[SCOPE_KEY]
  }
  [callback_state, redirect_uri]
end

.handle_auth_callback_deferred(request) ⇒ Object

Handle the result of the oauth callback. This version defers the exchange of the code by temporarily stashing the results in the user's session. This allows apps to use the generic CallbackApp handler for the callback without any additional customization.

Apps that wish to handle the callback directly should use #handle_auth_callback instead.

Parameters:

  • request (Rack::Request)

    Current request



82
83
84
85
86
# File 'lib/googleauth/web_user_authorizer.rb', line 82

def self.handle_auth_callback_deferred request
  callback_state, redirect_uri = extract_callback_state request
  request.session[CALLBACK_STATE_KEY] = MultiJson.dump callback_state
  redirect_uri
end

.validate_callback_state(state, request) ⇒ Object

Verifies the results of an authorization callback

Parameters:

  • state (Hash)

    Callback state

  • request (Rack::Request)

    Current request

Options Hash (state):

  • AUTH_CODE_KEY (String)

    The authorization code

  • ERROR_CODE_KEY (String)

    Error message if failed

Raises:

  • (Signet::AuthorizationError)


227
228
229
230
231
232
233
234
235
# File 'lib/googleauth/web_user_authorizer.rb', line 227

def self.validate_callback_state state, request
  raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[AUTH_CODE_KEY].nil?
  if state[ERROR_CODE_KEY]
    raise Signet::AuthorizationError,
          format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
  elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
    raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
  end
end

Instance Method Details

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

  • request (Rack::Request)

    Current request

  • redirect_to (String)

    Optional URL to proceed to after authorization complete. Defaults to the current URL.

  • scope (String, Array<String>)

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

  • state (Hash)

    Optional key-values to be returned to the oauth callback.

Returns:

  • (String)

    Authorization url

Raises:



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/googleauth/web_user_authorizer.rb', line 154

def get_authorization_url options = {}
  options = options.dup
  request = options[:request]
  raise NIL_REQUEST_ERROR if request.nil?
  raise NIL_SESSION_ERROR if request.session.nil?

  state = options[:state] || {}

  redirect_to = options[:redirect_to] || request.url
  request.session[XSRF_KEY] = SecureRandom.base64
  options[:state] = MultiJson.dump(state.merge(
                                     SESSION_ID_KEY  => request.session[XSRF_KEY],
                                     CURRENT_URI_KEY => redirect_to
                                   ))
  options[:base_url] = request.url
  super options
end

#get_credentials(user_id, request = nil, scope = nil) ⇒ Google::Auth::UserRefreshCredentials

Fetch stored credentials for the user from the given request session.

Parameters:

  • user_id (String)

    Unique ID of the user for loading/storing credentials.

  • request (Rack::Request) (defaults to: nil)

    Current request. Optional. If omitted, this will attempt to fall back on the base class behavior of reading from the token store.

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

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

Returns:

Raises:

  • (Signet::AuthorizationError)

    May raise an error if an authorization code is present in the session and exchange of the code fails



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/googleauth/web_user_authorizer.rb', line 187

def get_credentials user_id, request = nil, scope = nil
  if request&.session&.key? CALLBACK_STATE_KEY
    # Note - in theory, no need to check required scope as this is
    # expected to be called immediately after a return from authorization
    state_json = request.session.delete CALLBACK_STATE_KEY
    callback_state = MultiJson.load state_json
    WebUserAuthorizer.validate_callback_state callback_state, request
    get_and_store_credentials_from_code(
      user_id:  user_id,
      code:     callback_state[AUTH_CODE_KEY],
      scope:    callback_state[SCOPE_KEY],
      base_url: request.url
    )
  else
    super user_id, scope
  end
end

#handle_auth_callback(user_id, request) ⇒ Google::Auth::UserRefreshCredentials, String

Handle the result of the oauth callback. Exchanges the authorization code from the request and persists to storage.

Parameters:

  • user_id (String)

    Unique ID of the user for loading/storing credentials.

  • request (Rack::Request)

    Current request

Returns:



123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/googleauth/web_user_authorizer.rb', line 123

def handle_auth_callback user_id, request
  callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
    request
  )
  WebUserAuthorizer.validate_callback_state callback_state, request
  credentials = get_and_store_credentials_from_code(
    user_id:  user_id,
    code:     callback_state[AUTH_CODE_KEY],
    scope:    callback_state[SCOPE_KEY],
    base_url: request.url
  )
  [credentials, redirect_uri]
end