class Google::Auth::WebUserAuthorizer

Varation on {Google::Auth::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 {Google::Auth::Web::AuthCallbackApp} instead.

For rails apps, see {Google::Auth::ControllerHelpers}

@see {Google::Auth::AuthCallbackApp} @see {Google::Auth::ControllerHelpers} @note Requires sessions are enabled

Constants

AUTHORIZATION_ERROR
AUTH_CODE_KEY
CALLBACK_STATE_KEY
CURRENT_URI_KEY
ERROR_CODE_KEY
INVALID_STATE_TOKEN_ERROR
MISSING_AUTH_CODE_ERROR
NIL_REQUEST_ERROR
NIL_SESSION_ERROR
SCOPE_KEY
SESSION_ID_KEY
STATE_PARAM
XSRF_KEY

Attributes

default[RW]

Public Class Methods

extract_callback_state(request) click to toggle source
# File lib/googleauth/web_user_authorizer.rb, line 202
def self.extract_callback_state(request)
  state = MultiJson.load(request[STATE_PARAM] || '{}')
  redirect_uri = state[CURRENT_URI_KEY]
  callback_state = {
    AUTH_CODE_KEY => request[AUTH_CODE_KEY],
    ERROR_CODE_KEY =>  request[ERROR_CODE_KEY],
    SESSION_ID_KEY => state[SESSION_ID_KEY],
    SCOPE_KEY => request[SCOPE_KEY]
  }
  [callback_state, redirect_uri]
end
handle_auth_callback_deferred(request) click to toggle source

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 {Google::Auth::WebUserAuthorizer::CallbackApp} handler for the callback without any additional customization.

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

@param [Rack::Request] request

Current request
# File lib/googleauth/web_user_authorizer.rb, line 99
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
new(client_id, scope, token_store, callback_uri = 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] callback_uri

URL (either absolute or relative) of the auth callback. Defaults
to '/oauth2callback'
Calls superclass method Google::Auth::UserAuthorizer.new
# File lib/googleauth/web_user_authorizer.rb, line 116
def initialize(client_id, scope, token_store, callback_uri = nil)
  super(client_id, scope, token_store, callback_uri)
end
validate_callback_state(state, request) click to toggle source

Verifies the results of an authorization callback

@param [Hash] state

Callback state

@option state [String] AUTH_CODE_KEY

The authorization code

@option state [String] ERROR_CODE_KEY

Error message if failed

@param [Rack::Request] request

Current request
# File lib/googleauth/web_user_authorizer.rb, line 224
def self.validate_callback_state(state, request)
  if state[AUTH_CODE_KEY].nil?
    fail Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR
  elsif state[ERROR_CODE_KEY]
    fail Signet::AuthorizationError,
         sprintf(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
  elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
    fail Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
  end
end

Public Instance Methods

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 [Rack::Request] request

Current request

@param [String] redirect_to

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

@param [String, Array<String>] scope

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

@return [String]

Authorization url
# File lib/googleauth/web_user_authorizer.rb, line 156
def get_authorization_url(options = {})
  options = options.dup
  request = options[:request]
  fail NIL_REQUEST_ERROR if request.nil?
  fail NIL_SESSION_ERROR if request.session.nil?

  redirect_to = options[:redirect_to] || request.url
  request.session[XSRF_KEY] = SecureRandom.base64
  options[:state] = MultiJson.dump(
    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, 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 [Rack::Request] request

Current request

@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

@raise [Signet::AuthorizationError]

May raise an error if an authorization code is present in the session
and exchange of the code fails
# File lib/googleauth/web_user_authorizer.rb, line 185
def get_credentials(user_id, request, 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) click to toggle source

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

@param [String] user_id

Unique ID of the user for loading/storing credentials.

@param [Rack::Request] request

Current request

@return (Google::Auth::UserRefreshCredentials, String)

credentials & next URL to redirect to
# File lib/googleauth/web_user_authorizer.rb, line 129
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