Last active
August 22, 2024 02:52
-
-
Save serradura/40a4f05f424262a94f44997681f02d26 to your computer and use it in GitHub Desktop.
FirebaseAdmin::Auth.verify_id_token | Ruby solution for https://firebase.google.com/docs/auth/admin/verify-id-tokens
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Usage: | |
# ======== | |
# FirebaseAdmin::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 'httparty', '~> 0.14.0' | |
# gem 'jwt', '~> 1.5', '>= 1.5.6' | |
# require 'jwt' | |
# require 'httparty' | |
# require 'active_support/core_ext/module/delegation' | |
# | |
# require 'openssl' | |
# require 'singleton' | |
# require 'ostruct' | |
module FirebaseAdmin | |
class PublicKeys | |
URL = 'https://www.googleapis.com/robot/v1/metadata/x509/[email protected]' | |
EXPIRES_HEADER = 'expires' | |
attr_reader :response, :data | |
delegate :keys, :values, to: :data | |
def initialize | |
@response = fetch | |
end | |
def valid? | |
Time.now.utc < time_to_expire | |
end | |
def data | |
@response.as_json | |
end | |
private | |
def time_to_expire | |
@time_to_expire ||= Time.parse( | |
response.headers[EXPIRES_HEADER] | |
) | |
end | |
def fetch | |
HTTParty.get(URL) | |
end | |
end | |
class IDTokenVerifier | |
JWT_OPTIONS = { algorithm: 'RS256', verify_iat: true } | |
attr_reader :certificates | |
def initialize(public_keys) | |
@public_keys = public_keys | |
@certificates = map_certificates | |
end | |
def verify(id_token) | |
result = nil | |
certificates.each do |x509| | |
result = decode_jwt(id_token, x509) | |
break if result | |
end | |
result | |
end | |
private | |
def decode_jwt(id_token, x509) | |
JWT.decode(id_token, x509.public_key, true, JWT_OPTIONS) | |
rescue JWT::VerificationError | |
nil | |
end | |
def map_certificates | |
@public_keys.values.map do |credential| | |
OpenSSL::X509::Certificate.new(credential) | |
end | |
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 |
Even after all those years, not much has changed. My link is dead, but the original example is still solid.
This example has a workable solution but is not complete.
To complete your implementation, you may want to read the official document. It describes all fields that need validation and the kid
trick I mentioned.
This gist is a stripped-down version for newcomers to JWT, so it is easier to grasp. It is a good start, but more needs to be added to the production code. You can't be wrong with the official document.
P.S. I still use Firebase Auth in Ruby projects. It is one of the most trusted, free solutions around.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Any update on this??