上記URLにやり方が記載されているのですが、Rubyに関してはSDKが存在しないため自前で実装する必要があります。
https://github.com/fschuindt/firebase_id_token
こういうgemもあるのですが、Firebaseが公開している証明書をキャッシュするためだけにRedisが必要なので、Redisを使っていないプロジェクトではこのためだけにRedisを用意するのは過剰です。
JWTの検証に関してはこの記事が非常に参考になりますが、証明書のキャッシュ部分だけはRials.cacheに依存していてイマイチ。またHTTPクライアントはFaradayを利用したかったこともありFaradayを使ってなんとかいい感じにできないかと考えていたところ、いい感じのFaraday Middlewareがあったのでその紹介をします(本当はJWTの検証部分も解説しようと思ったんですが、ぐぐってみたら上記の記事を発見して書きたいことが書いてあったのでやめました)
faraday-http-cache
HTTPのレスポンスのCache-Controlヘッダーに含まれるmax-ageの値を利用していい感じにキャッシュしてくれるMiddlewareです。キャッシュの保存先としてキャッシュ用のクラスのインスタンスを渡すことができ、任意の場所にキャッシュできます。サンプルではRails.cacheを渡していますが、ファイルやDBやRedisなどに保存したい場合は同じインターフェースをもつクラスのインスタンスを作成して渡すこともできます(もちろんRails.cacheのストレージをを変更しても良いです)
証明書や公開鍵に関しては、Cache-Controlヘッダーに含まれるmax-ageの値を利用してキャッシュすることが推奨されているので、このgemはうってつけです。
connection = Faraday.new do |builder|
builder.use :http_cache, store: Rails.cache, logger: Rails.logger
builder.request :url_encoded
builder.response :json, parser_options: { symbolize_names: true }, content_type: 'application/json'
builder.adapter Faraday.default_adapter
end
connection.get('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com')
使い方としては上記のような形です。
特段変わったことをせず普通にFaradayを使っていればキャッシュされるようになるので非常に便利です。
またloggerを設定することで、キャッシュから取得しているのかどうかがわかるようになるので、loggerも合わせて設定することをおすすめします。ログレベルはdebugなので、本番環境でやたらログが出ることもありません(本番環境のログレベルがinfo以上である前提ですが…)
FirebaseはJWKsを公開しているわけではない罠
最初公式のドキュメントにある証明書がJWKsだと勘違いをしていてハマりました。実際は独自のフォーマットで、しかも公開鍵ではなく証明書なので、証明書から公開鍵に変換する必要があるする必要があります。
GCPではJWksが公開されてるので、Firebaseも同様にJWKsだろうと思いこんでしまうと罠にはまります。
JWKsであれば、JWTのdecodeの際にjwksのHashを渡すことでJWT.decodeがいい感じに公開鍵を使って検証してくれて楽なのに残念です…。
と、記事をここまで書いて、「もしかしたらJWKsも公開されてるのでは?」と思ってぐぐってみたところ、公式サイトにはなかったJWKsを発見しました。
https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com
これを用いれば以下のようにシンプルに書くことができます。
connection = Faraday.new do |builder|
builder.use :http_cache, store: Rails.cache, logger: Rails.logger
builder.request :url_encoded
builder.response :json, parser_options: { symbolize_names: true }, content_type: 'application/json'
builder.adapter Faraday.default_adapter
end
res = connection.get('https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com
')
payload, = JWT.decode(id_token, nil, true, { algorithms: ['RS256'], jwks: res.body })
シンプルに書けるようになりますが、公式ドキュメントに書かれているURLではないため、利用する際には注意してください。