class FirebaseIdToken::Signature

Deals with verifying if a given Firebase ID Token is signed by one of the Google's x509 certificates that Firebase uses.

Also checks if the resulting JWT payload hash matches with: + `exp` Expiration time + `iat` Issued at time + User's Firebase Project ID + Non-empty UID

## Verifying a Firebase ID Token

*Be sure to configure the gem to set your Firebase Project ID and a Redis server before move any forward.*

**See the README for a complete guide.**

WARNING: Trying to verify a token without any certificate saved in Redis certificates database raises a {Exceptions::NoCertificatesError}.

@example

FirebaseIdToken::Signature.verify(thrusty_token)
=> {"iss"=>"https://securetoken.google.com/your-project-id", [...]}

FirebaseIdToken::Signature.verify(fake_token)
=> nil

@see Signature#verify

Constants

JWT_DEFAULTS

Pre-default JWT algorithm parameters as recommended [here](goo.gl/uOK5Jx).

Attributes

firebase_id_token_certificates[RW]

Public Class Methods

new(jwt_token, raise_error: false) click to toggle source

Loads attributes: `:project_ids` from {FirebaseIdToken::Configuration}, and `:kid`, `:jwt_token` from the related `jwt_token`. @param [String] jwt_token Firebase ID Token

# File lib/firebase_id_token/signature.rb, line 71
def initialize(jwt_token, raise_error: false)
  @raise_error = raise_error
  @project_ids = FirebaseIdToken.configuration.project_ids
  @kid = extract_kid(jwt_token)
  @jwt_token = jwt_token
  @firebase_id_token_certificates = FirebaseIdToken.configuration.certificates
end
verify(jwt_token, raise_error: false) click to toggle source

Returns the decoded JWT hash payload of the Firebase ID Token if the signature in the token matches with one of the certificates downloaded by {FirebaseIdToken::Certificates.request}, returns `nil` otherwise.

It will also return `nil` when it fails in checking if all the required JWT fields are valid, as recommended [here](goo.gl/yOrZZX) by Firebase official documentation.

Note that it will raise a {Exceptions::NoCertificatesError} if the Redis certificates database is empty. Ensure to call {Certificates.request} before, ideally in a background job if you are using Rails.

If you would like this to raise and error, rather than silently failing, you can with the `raise_error` parameter. Example:

FirebaseIdToken::Signature
  .verify(token, raise_error: Rails.env.development?)

@param raise_error [Boolean] default: false @return [nil, Hash]

# File lib/firebase_id_token/signature.rb, line 54
def self.verify(jwt_token, raise_error: false)
  new(jwt_token, raise_error: raise_error).verify
end
verify!(jwt_token) click to toggle source

Equivalent to `.verify(jwt_token, raise_error: true)`.

@see {Signature.verify} @return [Hash]

# File lib/firebase_id_token/signature.rb, line 62
def self.verify!(jwt_token)
  new(jwt_token, raise_error: true).verify
end

Public Instance Methods

verify() click to toggle source

@see Signature.verify

# File lib/firebase_id_token/signature.rb, line 80
def verify
  certificate = firebase_id_token_certificates.find(@kid, raise_error: @raise_error)
  return unless certificate

  payload = decode_jwt_payload(@jwt_token, certificate.public_key)
  authorize payload
end

Private Instance Methods

authorize(payload) click to toggle source
# File lib/firebase_id_token/signature.rb, line 106
def authorize(payload)
  if payload && authorized?(payload)
    payload
  end
end
authorized?(payload) click to toggle source
# File lib/firebase_id_token/signature.rb, line 112
def authorized?(payload)
  still_valid?(payload) &&
  @project_ids.include?(payload['aud']) &&
  issuer_authorized?(payload) &&
  ! payload['sub'].empty?
end
decode_jwt_payload(token, cert_key) click to toggle source
# File lib/firebase_id_token/signature.rb, line 98
def decode_jwt_payload(token, cert_key)
  JWT.decode(token, cert_key, true, JWT_DEFAULTS).first
rescue StandardError
  return nil unless @raise_error

  raise
end
extract_kid(jwt_token) click to toggle source
# File lib/firebase_id_token/signature.rb, line 90
def extract_kid(jwt_token)
  JWT.decode(jwt_token, nil, false).last['kid']
rescue StandardError
  return 'none' unless @raise_error

  raise
end
issuer_authorized?(payload) click to toggle source
# File lib/firebase_id_token/signature.rb, line 124
def issuer_authorized?(payload)
  issuers = @project_ids.map { |i| "https://securetoken.google.com/#{i}" }
  issuers.include? payload['iss']
end
still_valid?(payload) click to toggle source
# File lib/firebase_id_token/signature.rb, line 119
def still_valid?(payload)
  payload['exp'].to_i > Time.now.to_i &&
  payload['iat'].to_i <= Time.now.to_i
end