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 |
Any update on this??
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
FYI certificate map can be looked up with
kid
from the JWT header.To utilize the optimization you have to decode the token without verification once for the
kid
.Here my enhanced version
https://gist.github.com/MidnightWonderer/c40b8c46dc42cb560ccbdcd4a79f52c9