Class: OmniAuth::Strategies::GoogleOauth2

Inherits:
OAuth2
  • Object
show all
Defined in:
lib/omniauth/strategies/google_oauth2.rb

Overview

Main class for Google OAuth2 strategy.

Constant Summary collapse

ALLOWED_ISSUERS =
['accounts.google.com', 'https://accounts.google.com'].freeze
BASE_SCOPE_URL =
'https://www.googleapis.com/auth/'
BASE_SCOPES =
%w[profile email openid].freeze
DEFAULT_SCOPE =
'email,profile'
USER_INFO_URL =
'https://www.googleapis.com/oauth2/v3/userinfo'
JWKS_URL =
'https://www.googleapis.com/oauth2/v3/certs'
JWKS_CACHE_TTL =
3600
JWKS_RETRY_INTERVAL =
60
LOG_MESSAGE_LIMIT =
200
REQUIRED_ID_TOKEN_CLAIMS =
%w[iss aud exp sub].freeze
AUTHORIZE_OPTIONS =
%i[access_type hd login_hint prompt request_visible_actions scope state redirect_uri include_granted_scopes enable_granular_consent openid_realm device_id device_name]
JwksUnavailable =
Class.new(StandardError)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cached_jwks(force: false, &fetch) ⇒ Object

Google's signing keys rotate, so the key set is shared across requests and refetched on expiry or when a token names a kid we do not hold. The fetch deliberately happens under the lock: concurrent callers wait for one request rather than each issuing their own. The keys belong to Google, not to any one strategy class, so a subclass shares this cache rather than keeping a second one and refetching the same data. Delegating also keeps it from reaching for a mutex it does not own, which is only defined here.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/omniauth/strategies/google_oauth2.rb', line 39

def cached_jwks(force: false, &fetch)
  return GoogleOauth2.cached_jwks(force: force, &fetch) unless equal?(GoogleOauth2)

  @jwks_mutex.synchronize do
    now = ::Time.now.to_i

    # A forced refresh means some token named a kid we do not hold, which
    # the sender chooses freely. Honour it no more often than the retry
    # interval, or it becomes a way to drive unlimited fetches, each one
    # holding this lock while every other login waits.
    force &&= @jwks_forced_at.nil? || now >= @jwks_forced_at + JWKS_RETRY_INTERVAL

    if force || @jwks.nil? || now >= @jwks_expires_at.to_i
      # Back off even with nothing cached. Otherwise an unreachable
      # endpoint queues every waiting caller behind its own timeout,
      # since the lock serializes them and no expiry has been recorded.
      raise JwksUnavailable, 'within JWKS retry backoff' if @jwks.nil? && now < @jwks_retry_at.to_i

      @jwks_forced_at = now if force

      begin
        @jwks = yield
        @jwks_expires_at = ::Time.now.to_i + JWKS_CACHE_TTL
      rescue StandardError
        retry_at = ::Time.now.to_i + JWKS_RETRY_INTERVAL
        @jwks_retry_at = retry_at
        raise if @jwks.nil?

        # Google's keys outlive this cache by a wide margin, so serve the
        # stale set through a short outage rather than failing every
        # login, and back off instead of refetching on each request.
        @jwks_expires_at = retry_at
      end
    end
    @jwks
  end
end

.reset_jwks_cache! ⇒ Object



77
78
79
80
81
82
83
84
85
86
# File 'lib/omniauth/strategies/google_oauth2.rb', line 77

def reset_jwks_cache!
  return GoogleOauth2.reset_jwks_cache! unless equal?(GoogleOauth2)

  @jwks_mutex.synchronize do
    @jwks = nil
    @jwks_expires_at = nil
    @jwks_retry_at = nil
    @jwks_forced_at = nil
  end
end

Instance Method Details

#authorize_params ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/omniauth/strategies/google_oauth2.rb', line 101

def authorize_params
  super.tap do |params|
    (options[:authorize_options] & options[:overridable_authorize_options]).each do |k|
      params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
    end

    params[:scope] = get_scope(params)
    params[:access_type] = 'offline' if params[:access_type].nil?
    params['openid.realm'] = params.delete(:openid_realm) unless params[:openid_realm].nil?

    session['omniauth.state'] = params[:state] if params[:state]
  end
end

#custom_build_access_token ⇒ Object Also known as: build_access_token

Raises:

  • (CallbackError)


150
151
152
153
154
155
156
157
158
159
160
# File 'lib/omniauth/strategies/google_oauth2.rb', line 150

def custom_build_access_token
  access_token = get_access_token(request)

  # Nothing in the request produced a usable credential. Raising here gives
  # the caller an ordinary auth failure; the alternative is omniauth-oauth2
  # calling #expired? on nil and the request dying with a NoMethodError.
  raise CallbackError.new(:invalid_credentials, 'No valid credentials were supplied in the callback request') if access_token.nil?

  verify_hd(access_token)
  access_token
end

#raw_info ⇒ Object



146
147
148
# File 'lib/omniauth/strategies/google_oauth2.rb', line 146

def raw_info
  @raw_info ||= access_token.get(USER_INFO_URL).parsed
end