class GoogleIDToken::Validator

Constants

GOOGLE_CERTS_EXPIRY
GOOGLE_CERTS_URI
GOOGLE_ISSUERS

developers.google.com/identity/sign-in/web/backend-auth

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method
# File lib/google-id-token.rb, line 50
def initialize(options = {})
  super()

  if options[:x509_cert]
    @certs_mode = :literal
    @certs = { :_ => options[:x509_cert] }
  # elsif options[:jwk_uri]  # TODO
  #   @certs_mode = :jwk
  #   @certs = {}
  else
    @certs_mode = :old_skool
    @certs = {}
  end

  @certs_expiry = options.fetch(:expiry, GOOGLE_CERTS_EXPIRY)
end

Public Instance Methods

check(token, aud, cid = nil) click to toggle source

If it validates, returns a hash with the JWT payload from the ID Token.

You have to provide an "aud" value, which must match the
token's field with that name, and will similarly check cid if provided.

If something fails, raises an error

@param [String] token

The string form of the token

@param [String] aud

The required audience value

@param [String] cid

The optional client-id ("azp" field) value

@return [Hash] The decoded ID token

# File lib/google-id-token.rb, line 82
def check(token, aud, cid = nil)
  synchronize do
    payload = check_cached_certs(token, aud, cid)

    unless payload
      # no certs worked, might've expired, refresh
      if refresh_certs
        payload = check_cached_certs(token, aud, cid)

        unless payload
          raise SignatureError, 'Token not verified as issued by Google'
        end
      else
        raise CertificateError, 'Unable to retrieve Google public keys'
      end
    end

    payload
  end
end

Private Instance Methods

certs_cache_expired?() click to toggle source
# File lib/google-id-token.rb, line 183
def certs_cache_expired?
  if defined? @certs_last_refresh
    Time.now > @certs_last_refresh + @certs_expiry
  else
    true
  end
end
check_cached_certs(token, aud, cid) click to toggle source

tries to validate the token against each cached cert. Returns the token payload or raises a ValidationError or

nil, which means none of the certs validated.
# File lib/google-id-token.rb, line 108
def check_cached_certs(token, aud, cid)
  payload = nil

  # find first public key that validates this token
  @certs.detect do |key, cert|
    begin
      public_key = cert.public_key
      decoded_token = JWT.decode(token, public_key, !!public_key, { :algorithm => 'RS256' })
      payload = decoded_token.first

      # in Feb 2013, the 'cid' claim became the 'azp' claim per changes
      #  in the OIDC draft. At some future point we can go all-azp, but
      #  this should keep everything running for a while
      if payload['azp']
        payload['cid'] = payload['azp']
      elsif payload['cid']
        payload['azp'] = payload['cid']
      end
      payload
    rescue JWT::ExpiredSignature
      raise ExpiredTokenError, 'Token signature is expired'
    rescue JWT::DecodeError => e
      nil # go on, try the next cert
    end
  end

  if payload
    if !(payload.has_key?('aud') && payload['aud'] == aud)
      raise AudienceMismatchError, 'Token audience mismatch'
    end
    if cid && payload['cid'] != cid
      raise ClientIDMismatchError, 'Token client-id mismatch'
    end
    if !GOOGLE_ISSUERS.include?(payload['iss'])
      raise InvalidIssuerError, 'Token issuer mismatch'
    end
    payload
  else
    nil
  end
end
old_skool_refresh_certs() click to toggle source
# File lib/google-id-token.rb, line 162
def old_skool_refresh_certs
  return true unless certs_cache_expired?

  uri = URI(GOOGLE_CERTS_URI)
  get = Net::HTTP::Get.new uri.request_uri
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  res = http.request(get)

  if res.is_a?(Net::HTTPSuccess)
    new_certs = Hash[JSON.load(res.body).map do |key, cert|
      [key, OpenSSL::X509::Certificate.new(cert)]
    end]
    @certs.merge! new_certs
    @certs_last_refresh = Time.now
    true
  else
    false
  end
end
refresh_certs() click to toggle source

returns false if there was a problem

# File lib/google-id-token.rb, line 151
def refresh_certs
  case @certs_mode
  when :literal
    true # no-op
  when :old_skool
    old_skool_refresh_certs
  # when :jwk          # TODO
  #  jwk_refresh_certs
  end
end