Skip to content

Instantly share code, notes, and snippets.

@midnight-wonderer
Forked from serradura/firebase_admin.rb
Last active May 1, 2020 07:56
Show Gist options
  • Save midnight-wonderer/c40b8c46dc42cb560ccbdcd4a79f52c9 to your computer and use it in GitHub Desktop.
Save midnight-wonderer/c40b8c46dc42cb560ccbdcd4a79f52c9 to your computer and use it in GitHub Desktop.
FirebaseAuth::Auth.verify_id_token | Ruby solution for https://firebase.google.com/docs/auth/admin/verify-id-tokens
# Usage:
# ========
# FirebaseAuth::Auth.verify_id_token(your_id_token)
#
# The method call follows the same API of the Node.js, JAVA and Python SDKs.
# See https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_the_firebase_admin_sdk
# Dependencies:
# ---------------
# gem 'activesupport'
# gem 'faraday'
# gem 'jwt', '~> 1.5', '>= 1.5.6'
# require 'jwt'
# require 'faraday'
# require 'active_support/core_ext/module/delegation'
#
# require 'openssl'
# require 'singleton'
# require 'ostruct'
module FirebaseAuth
class PublicKeys
URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'
EXPIRES_HEADER = 'expires'
attr_reader :response
delegate :keys, :values, to: :data
def initialize
@response = fetch
end
def valid?
Time.now.utc < time_to_expire
end
def data
@parsed_body ||= JSON.parse(response.body)
end
def look_up(kid)
@certificate_hash ||= Hash[data.map { |k, v| [k, OpenSSL::X509::Certificate.new(v)] }]
@certificate_hash[kid]
end
private
def time_to_expire
@time_to_expire ||= Time.parse(
response.headers[EXPIRES_HEADER]
)
end
def fetch
Faraday.get(URL)
end
end
class IDTokenVerifier
JWT_OPTIONS = { algorithm: 'RS256', verify_iat: true }
def initialize(public_keys)
@public_keys = public_keys
end
def verify(id_token)
kid = JWT.decode(id_token, nil, false).last['kid'] rescue nil
decode_jwt(id_token, @public_keys.look_up(kid))
end
private
def decode_jwt(id_token, x509)
JWT.decode(id_token, x509.public_key, true, JWT_OPTIONS)
rescue JWT::VerificationError
nil
end
end
class Auth
include Singleton
def initialize
refresh
end
def public_keys
resolve { @public_keys }
end
def verify_id_token(id_token)
result = resolve { @id_token_verifier.verify(id_token) }
if result
payload, header = result
[OpenStruct.new(payload), OpenStruct.new(header)]
end
end
class << self
delegate :verify_id_token, :public_keys, to: :instance
end
private
def refresh
@public_keys = PublicKeys.new
@id_token_verifier = IDTokenVerifier.new(@public_keys)
end
def resolve
refresh unless @public_keys.valid?
yield
end
end
end
@JeffreyCheng92
Copy link

Hi, I just had a few questions on the usage of this as I'm implementing it right now since the Firebase Auth sdk for ruby is not out yet.

From what I can see, PublicKeys.new gets all the public keys from the fetch method. The IDTokenVerifier is responsible for comparing the JWT token passed in from the front end into the backend rails application. So I'll lay out my steps below:

  1. token = request.headers["Authorization"] #JWT token is received from request headers
  2. FirebaseAuth::Auth.new.verify_id_token(token) # Supposedly returns [OpenStruct.new(payload), OpenStruct.new(header)]
  3. Is the return object from #verify_id_token the data of the user from firebase? Something like email, uid? Then I can just do something like User.find_by_email(email)?

Thanks for your help in advance!

@midnight-wonderer
Copy link
Author

midnight-wonderer commented Apr 13, 2018

@JeffreyCheng92 sorry I did not see your comment.
It is way too late now but in order to integrate the snippet into Rails project you can use
https://apidock.com/rails/v4.2.7/ActionController/HttpAuthentication/Token/token_and_options
to retrieve the authorization header.

and this is an example of tokens returned from Firebase

      [#<OpenStruct
        iss = "https://securetoken.google.com/firebase-project-name",
        name = "Sanji",
        picture = "https://lh3.googleusercontent.com/-Y1_FYLQpPns/AAAAAAAAAAI/AAAAAAAAAZU/T_xT1bjE-xo/photo.jpg",
        aud = "firebase-project-name",
        auth_time = 1493484250,
        user_id = "anFwD7AhKcUGXkb56j22bzkbj4ql",
        sub = "anFwD7AhKcUGXkb56j22bzkbj4ql",
        iat = 1494164568,
        exp = 1494168168,
        email = "sanji@straw-hat-pirates.onion",
        email_verified = true,
        firebase = {
          "identities" => {
            "google.com" => ["679820163956820153065"],
            "email" => ["sanji@straw-hat-pirates.onion"]
          },
          "sign_in_provider" => "google.com"
        }>,
       #<OpenStruct
         alg = "RS256",
         kid = "da02f3417dbdaa7b93c9e49bbf7128fddd67cb50"
       >]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment