😇

Next.js(App Router)にFirebase Authを組み込む際の難しさと解決策

2024/01/02に公開

Firebase AuthenticationとApp Routerを組み合わせたアプリの開発中に発生した悩みがあります。

それは認証情報を必要とするコンポーネントは、すべてクライアントコンポーネントになってしまうことです。
Next.jsには強力なサーバーコンポーネントがあるのに、使えなくなってしまうのはあまりに惜しすぎます。。。

色々と試行錯誤した結果、解決策をいくつか考えましたので共有します。
より良い解決法があれば、ぜひコメントなどで教えていただければ嬉しいです。

問題の概要

Firebase Authenticationは、ログイン中のユーザーの認証キーをJWT形式(以下、idTokenと呼びます)で管理しています。
idTokenはローカルストレージに格納されるため、サーバーサイドからのアクセスができません。
これにより、認証情報を必要とする機能はサーバーコンポーネントでは実装できません(例:ユーザー固有のDBのCRUD操作など)

※現在はFirebase App HostingのWebフレームワーク機能を使うことがベストプラクティスとして推奨されているようです。
ドキュメントによると、Firebase App Hostingを使うことで、上記の問題は解決できるようです。
https://firebase.google.com/codelabs/firebase-nextjs?hl=ja#0

Firebase HostingのWebフレームワーク機能を使うと簡単に認証管理ができる。...

※Firebase HostingのBlazeプランで利用できるWebフレームワーク機能を利用すると、サーバーコンポーネントでも特別な設定無しに認証管理ができると公式ドキュメントには書いてあります。

サンプルのコードを追ってみました。
これから紹介する解決策と同じく、サーバーサイドCookieを使用しているようですが、よりシンプルで保守しやすいように見えました。

ですが、以下の点を考慮するとまだ実用的ではないと思います。
1. プレビューリリースの状況
現在、Webフレームワークデプロイ機能はプレビューリリースの段階にあります。これは、今後仕様が変更される可能性があることを意味します。技術の仕様が変わると、それに合わせてシステムの更新や調整が必要になる可能性があります。
2. バグ報告の存在
GitHubで報告されているように、この機能にはまだ部分的にしか動作しないというバグが存在します。このような問題は、安定した運用に必要な信頼性を損なう可能性があります。
https://github.com/firebase/friendlyeats-web/issues/243
3. 将来的な移行コストの問題
プレビューリリースの機能が将来的に大きく変更されると、他の認証サービスへの移行をする必要が生じるかもしれません。Webフレームワークデプロイ機能への依存性を高めると、移行コストが大きくなります。

上記で紹介したドキュメントによると解決済みの事象のようです。

解決策

解決策を2つ紹介します。
上部で紹介したドキュメントによると、現在はFirebase App HostingのWebフレームワーク機能を使うことがベストプラクティスとして推奨されているようです。
Firebase App Hostingを使いたくない方のみ下記を参考にしてください

Cookieベースでの認証

  • サーバーサイドCookieの独自発行

サーバーサイドCookieはサーバーコンポーネントから取得できますので、認証情報取得が必要な機能だとしてもサーバーコンポーネントで実装できます。

Firebaseの公式ドキュメントにもサーバーサイドCookieを使った認証の実装方法は書いてあります。
https://firebase.google.com/docs/auth/admin/manage-cookies?hl=ja

これを使えばidTokenの取得をサーバーコンポーネントでも行えると思います。
しかし、開発者側のCookie管理責務が増えてしまい、セキュリティリスクが高すぎると感じています。

  • Firebase AuthとNextAuthによるCookieベース認証

独自Cookieの発行にともなうリスクの解決策として、Firebaseでのログイン後にidTokenをキーとしてNextAuthで再ログインし、認証管理をNextAuthが発行するサーバーサイドCookieに委ねる方法があります。
こちらの方法だとCookieの管理責務がNextAuth側に移るので開発者の責務が低減します。

具体的な認証フローを図を用いて説明します
認証情報が必要な処理の際に、NextAuth側で発行されたサーバーサイドCookieに格納されたidTokenと、Firebase atuhで管理されているidTokenを検証するようにします。
これにより2つのサービス間での認証状態の整合性を保つことができます。

サービス間での認証状態の整合性についての補足

ユーザーが認証に使用したSSOサービスのアカウントを削除した場合、ローカルストレージに保存されているFirebaseが発行したidTokenは無効になります。
そのため、ユーザーがサービスにアクセスしようとした際に、NextAuthとFirebase AuthenticationのidTokenの整合性チェックに失敗し、エラーが返されます。これにより、不正アクセスや古いアカウント情報に基づくアクセスを防ぐことができます。
※idTokenは無効になるとはいえ、一応セッション領域のidTokenも削除しておいたほうがいいとは思います。

上記の認証フローの実装方法については↓の記事が参考になりました。
https://zenn.dev/tentel/articles/cc76611f4010c9

Firebase Authの代わりにNextAuthかauth0を使う

筆者はFirebase Authenticationの匿名認証機能を利用したいためFirebaseにこだわっています。
しかし、Firebase特有の機能を求めてないのであればNextAuthかauth0を使うのが適切かと思います。

NextAuthだけではなく、auth0もapp routerに特化した認証管理メソッドも用意してくれているようです。
https://auth0.com/blog/auth0-stable-support-for-nextjs-app-router/

まとめ

総合的に考えて、App RouterでFirebase Authenticationを使うことは開発者のセキュリティ管理の責務が増えるので微妙でしょう。。。

Firebase Authenticationでの認証管理は、ローカルストレージから認証状況を取得して検証する方法がベストプラクティスであり、これから逸脱すると実装が複雑になってしまうので。。。
https://firebase.google.com/docs/auth/admin/verify-id-tokens?hl=ja

App Routerは便利ですが、他サービスが使いにくくなるのがつらみですね。(GraphQLやUIライブラリなどなど)
ご意見やアドバイスなどあればコメントいただけると嬉しいです!

Discussion