RubyでFirebase Authenticationトークンを検証するサンプル
認証をFirebase Authenticationに丸投げし、そちらで作ったトークンをサーバーに送信、認可を行う時に有効
FIREBASE_PROJECT_ID = "FIREBASEPROJECTID" CIRTIFICATE_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com' EXP_LEEWAY = 30.seconds VALID_ISS = "https://securetoken.google.com/#{FIREBASE_PROJECT_ID}" CERTIFICATE_MAP = JSON.parse(Net::HTTP.get_response(URI.parse(CIRTIFICATE_URL)).body) TokenVerifyFailed = Class.new(StandardError) InvalidAuthTime = Class.new(TokenVerifyFailed) def from_firebase(id_token) # https://firebase.google.com/docs/auth/admin/verify-id-tokens?hl=ja a, decoded_token_header = JWT.decode(id_token, nil, false) uri = URI.parse(CIRTIFICATE_URL) certificate = CERTIFICATE_MAP.fetch(decoded_token_header["kid"]) public_key = OpenSSL::X509::Certificate.new(certificate).public_key decoded_token_payload, _ = JWT.decode( id_token, public_key, true, exp_leeway: EXP_LEEWAY, # 有効期限の検証をするが、ゆるめに。 EXP_LEEWAY 秒は大目に見る。 verify_iat: true, # 発行時の検証をする aud: FIREBASE_PROJECT_ID, verify_aud: true, # 対象の検証をする iss: VALID_ISS, verify_iss: true, # 発行元の検証をする verify_sub: true, # 件名の存在を検証する algorithm: decoded_token_header["alg"] ) raise InvalidAuthTime.new('Invalid auth_time') unless Time.zone.at(decoded_token_payload['auth_time']).past? # decoded_token_payload = { # "name"=>"Takeuchi Yuichi", # "picture"=>"https://lh3.googleusercontent.com/a-/AAuE7mAZU7Rh7lIFStzfWGe3tC24qDIX4UIoEWR8426flA", # "iss"=>"https://securetoken.google.com/rails-firebase-sample", # "aud"=>"rails-firebase-sample", # "auth_time"=>1580712233, # "user_id"=>"Qgk3sd1HgoPLVbSy8uXAWnRmWmx1", # "sub"=>"Qgk3sd1HgoPLVbSy8uXAWnRmWmx1", # "iat"=>1580712233, # "exp"=>1580715833, # "email"=>"yuichi.takeuchi@takeyuweb.co.jp", # "email_verified"=>true, # "firebase"=>{ # "identities"=>{ # "google.com"=>["100008179958237311525"], # "email"=>["yuichi.takeuchi@takeyuweb.co.jp"] # }, # "sign_in_provider"=>"google.com" # } # } decoded_token_payload end
Railsにおける利用例
例えばこんな感じで、
- Bearer トークンとして受け取って、対応する User モデルを作る/返す
- セッションに記録されたものがあればそれを使う
みたいなことができる
def current_user return @current_user if defined?(@current_user) # fallback if session[:authorization].present? && request.headers['Authorization'].blank? request.headers['Authorization'] = "Bearer #{session[:authorization]}"; end user = authenticate_with_http_token do |id_token, options| User.from_firebase(id_token) rescue JWT::DecodeError, User::TokenVerifyFailed => e Rails.logger.error(e) nil end if user&.persisted? @current_user = user else @current_user = nil end end