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/[email protected]'
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
@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 = "[email protected]",
        email_verified = true,
        firebase = {
          "identities" => {
            "google.com" => ["679820163956820153065"],
            "email" => ["[email protected]"]
          },
          "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