Skip to content

Instantly share code, notes, and snippets.

@avosalmon
Last active March 4, 2024 06:54
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avosalmon/965fdcbac9568ead2a24eb3cfc388204 to your computer and use it in GitHub Desktop.
Save avosalmon/965fdcbac9568ead2a24eb3cfc388204 to your computer and use it in GitHub Desktop.
Verify Firebase auth JWT token
require 'base64'
require 'httparty'
require 'jwt'
class FirebaseToken
JWT_ALGORITHM = 'RS256'.freeze
PUBLIC_KEY_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'.freeze
def initialize(token)
@token = token
end
def verify(firebase_project_id)
header = decode_header
alg = header['alg']
kid = header['kid']
if alg != JWT_ALGORITHM
raise "Invalid token 'alg' header (#{alg}). Must be '#{JWT_ALGORITHM}'."
end
public_key = get_public_key(kid)
verify_jwt(public_key, firebase_project_id)
end
private
def decode_header
encoded_header = @token.split('.').first
JSON.parse(Base64.decode64(encoded_header))
end
def get_public_key(kid)
response = HTTParty.get(PUBLIC_KEY_URL)
unless response.success?
raise "Failed to fetch JWT public keys from Google."
end
public_keys = response.parsed_response
# TODO: cache public_keys to avoid downloading it every time.
# Use the cache-control header for TTL.
unless public_keys.include?(kid)
raise "Invalid token 'kid' header, do not correspond to valid public keys."
end
OpenSSL::X509::Certificate.new(public_keys[kid]).public_key
end
def verify_jwt(public_key, firebase_project_id)
options = {
algorithm: JWT_ALGORITHM,
verify_iat: true,
verify_aud: true,
aud: firebase_project_id,
verify_iss: true,
iss: "https://securetoken.google.com/#{firebase_project_id}"
}
JWT.decode(@token, public_key, true, options)
end
end
firebase_project_id = 'your-project-id'
# retrieve the token from HTTP header in the real app
id_token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjJjOGUyYjI5NmM2ZjMyODRlYzMwYjg4NjVkNzI5M2U2MjdmYTJiOGYiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUnl1dGEgSGFtYXNha2kiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EtL0FPaDE0R2p6NUpqLUZqNEo5aWM0TnRtMmZKNmd3dGpnNGJZR19xbVBfbGNleVE9czk2LWMiLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vc3dpcGUtc2VydmljZXMtc3RhZ2luZyIsImF1ZCI6InN3aXBlLXNlcnZpY2VzLXN0YWdpbmciLCJhdXRoX3RpbWUiOjE2MTg4MTE1MTAsInVzZXJfaWQiOiJPSXMxWUQyRFhVTUdrRmxEUGoxajZVcVdlNHkxIiwic3ViIjoiT0lzMVlEMkRYVU1Ha0ZsRFBqMWo2VXFXZTR5MSIsImlhdCI6MTYxODgxNTUxNCwiZXhwIjoxNjE4ODE5MTE0LCJlbWFpbCI6ImF2b3NhbG1vbkBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjExMDc2Mjg3ODQwNzgzMTkzMjU4NCJdLCJlbWFpbCI6WyJhdm9zYWxtb25AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.OcMHEpPtlDwnzjBC4qqrBnXdWk-AF8o_ddAcvTbd8AhKErq3_PjQuXgjxRpctE9R2prG6V_R9Xmsi6_UBTIzGcRGztCa9ciPu18dK3-zIdClrBPkwLW7mjKFuTuyDtdWqY5PXuZpcD8NHNK3joL5nNqJ6xFa5UDRnslDlF0wiNh4A0sU0wjBDCwvq8pqlHWZFn6K_pHwHxjELsI9rmE_YrHFm-s8U142zr_Y8KBdbT_sLY8iPuB3-u6TrnfnwR9Tr9lpaCv0hNM7VVbX9sITBNssAekkFE-bqaW2BvdqZvcjXuDzTWmjgnFZul5MLfo6JtVEa6JR8ZdnMG_yi3yHsA'
token = FirebaseToken.new(id_token)
decoded_token = token.verify(firebase_project_id)
p decoded_token
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment