class FirebaseIDToken::Validator

Constants

FIREBASE_CERTS_EXPIRY
FIREBASE_CERTS_URI
FIREBASE_ISSUERS_PREFIX

Public Class Methods

new(keyopts = {}) click to toggle source
# File lib/firebase-id-token.rb, line 46
def initialize(keyopts = {})
  if keyopts[:x509_cert]
    @certs_mode = :literal
    @certs = { :_ => keyopts[:x509_cert] }
  # elsif keyopts[:jwk_uri]  # TODO
  #   @certs_mode = :jwk
  #   @certs = {}
  else
    @certs_mode = :old_skool
    @certs = {}
  end

  @certs_expiry = keyopts.fetch(:expiry, FIREBASE_CERTS_EXPIRY)
end

Public Instance Methods

check(token, aud) 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.
Furthermore the tokens field "iss" must be
"https://securetoken.google.com/<aud>"

If something fails, raises an error

@param [String] token

The string form of the token

@param [String] aud

The required audience value

@return [Hash] The decoded ID token

# File lib/firebase-id-token.rb, line 76
def check(token, aud)
  payload = check_cached_certs(token, aud)

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

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

  payload
end

Private Instance Methods

certs_cache_expired?() click to toggle source
# File lib/firebase-id-token.rb, line 162
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) 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/firebase-id-token.rb, line 100
def check_cached_certs(token, aud)
  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, true, { :algorithm => 'RS256' })
      payload = decoded_token.first

      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 FIREBASE_ISSUERS_PREFIX + aud != payload['iss']
      raise InvalidIssuerError, 'Token issuer mismatch'
    end
    payload
  else
    nil
  end
end
old_skool_refresh_certs() click to toggle source
# File lib/firebase-id-token.rb, line 141
def old_skool_refresh_certs
  return true unless certs_cache_expired?

  uri = URI(FIREBASE_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[MultiJson.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/firebase-id-token.rb, line 132
def refresh_certs
  case @certs_mode
  when :literal
    true # no-op
  when :old_skool
    old_skool_refresh_certs
  end
end