Firebase Authenticationを用いた「やってはいけない」システム設計の話

エクストーンの豊田です。先日、エクストーン社内で技術勉強会があり、そちらでFirebase Authenticationを利用してWebサービスを設計・運用した際に困った話をさせていただいたので、こちらでも紹介させていただきたいと思います。

Firebase Authentication

FirebaseはGoogleが提供しているモバイル・Webアプリケーション向けのプラットフォームで、認証やストレージ、関数実行等の機能を提供します。今回はFirebaseが提供する認証サービスであるAuthenticationについてお話しします。

Firebase Authenticationはユーザーの管理や認証を行うサービスで、メールアドレス・パスワードによる認証の他に、同じユーザーに対してGoogleアカウントやApple IDを利用した認証を紐づける等が可能です。ユーザーの管理自体をFirebase側で行うため、認証のための情報を開発するサービス側で保持しなくてもよくてFirebaseに任せることが出来るというのが設計上の最大のメリットです。

また、Webやアプリ等でFirebaseのログインを行うUIを提供するライブラリもあるため、これらを組み合わせることで認証の仕組みを少ない開発工数で実装することができます。

やってはいけない設計の話

Firebase Authenticationを利用して認証を行う際に、ちゃんと考慮せずに設計してしまうと後々の運用でトラブルになるケースがあるので、いくつか紹介したいと思います。

Case1. ユーザーアカウントを無条件に信頼する

Firebase AuthenticationではWebブラウザで実行するクライアント側のライブラリからユーザーアカウントを作成することが出来ます。FirebaseにおけるAPI Keyは機密情報ではないため、 curl コマンド等を利用することで直接実行してアカウントを作成することが出来ます。

「ログインしないと見られない情報」「限られたユーザーしかアカウントを作ることが出来ない」というような要件がある場合、Firebase Authenticationで作成されたユーザーというものは無条件に信頼することはできません。

この場合、以下のような設計を追加することで上記の要件を満たすシステムを実現できます。

  • 社内システム等、アカウントに利用されるメールアドレスのドメインが限られている場合、メールアドレスのドメインの検証を行う
  • カスタムクレームの設定を行う。サーバー側でのみ設定可能な情報を付与することで、その情報をもとにアカウントの検証を行う

必要なのはクライアント側で実行するユーザー作成APIで作成・更新できない情報に基づいたアカウント検証です。

Case2. Firebaseが提供するコンソールだけで実サービスの運用を行う

Firebaseでは利用しやすいようにWeb上でのコンソールがあり、Authenticationの場合ですとメールアドレスからユーザーの検索を行うこと等が出来ます。ただし提供されている機能が限定的で、実際にこのコンソールのみを利用して実際のサービスの運用を行うと困るケースがいくつか存在します。

Firebaseのユーザーを検索する際、コンソールから問い合わせの条件として利用できるのはFirebaseのUID、メールアドレス、電話番号の3つです。一番困るケースとして、SNSを利用したログインを行っているユーザーで、メールアドレスや電話番号の情報をFirebase側で保持していないケースが上げられます。例えばTwitterを利用したログインを行っている場合、Twitterアカウント情報が分かってもそれを利用してFirebaseのユーザーを検索することはコンソール上からは不可能です。

Firebase Authenticationのコンソール。出来ることは思ったより少ない

Firebase Admin SDKを利用すればAPI経由でFirebaseのユーザーをUID、メールアドレス、電話番号に加えてSNSのユーザーIDで検索することが出来ます。そのため、運用等を考えるならばユーザーをちゃんと追跡できる仕組みを構築する必要があり、SNSによるログインを許容する場合はFirebaseが提供するコンソールだけではユーザーが検索できない可能性があることを考慮する必要があります。

それに加えて、認証時に利用できるSNSにおいてユーザーIDを取得する方法をかならず確認しておく必要があります。Twitterの場合はユーザーIDの取得方法が結構面倒(APIを利用するか、ユーザーのページのソースコードから検索する等、どちらもいつまでその方法が利用できるか分からないという恐怖がありますw)なので、そのあたりも採用するかどうかの検討材料になります。

Case3. メールアドレスの検証をしない

Firebaseのパスワード認証ではユーザー名にメールアドレスを利用します。ただしこのメールアドレスが実際に有効なメールアドレスかどうかと言うことをログイン時に検証はしていません。そのため、疎通確認を行っていないメールアドレスではログインできないようにする、という処理はFirebase側で実現することは出来ません。

メールアドレスがユーザーの識別子として重要なサービスである場合(例えば登録しているメールアドレスにメールを送信する機能がある場合など)、攻撃したい対象のメールアドレスでアカウントを作ることで、サービス経由で大量のメールをそのメールアドレスに送ることが出来ます。

Firebase Authenticationではメールアドレスの検証で、Firebaseから実際にメールを送信し、そこに含まれるURLにアクセスすることで、そのアカウントを検証済み(email_verified)にすることが出来ます。

サービス側が検証の済んでないユーザーに対して機能を提供しないように適切に実装を行う必要があります。具体的には、Firebaseのユーザーの属性に email_verified という値が存在するため、この値が true であるかどうかのチェックを逐一行うようにします。メールアドレスの検証はFirebaseの機能でも行えますし、自前でメールアドレス検証の仕組みを実装したうえでFirebase Admin SDKを介して email_verified を更新することも出来ます。

また、Case1で言及したカスタムクレームについて検証を行いたい場合も同じタイミングで行うとよいかと思います。Firebaseから取得できる情報に基づき、サービス側で適切に有効なユーザーかどうか、有効でないユーザーからのアクセスの場合はどういうフローを実行するか、等を考慮する必要があります。

// ユーザーのemail_verifiedを取得する
const { initializeApp } = require('firebase-admin/app');
const { getAuth } = require('firebase-admin/auth');

const firebase_user_id = 'Firebase User ID'

getAuth().getUser(firebase_user_id).then((user) => {
    // emailが検証済みならtrueを返す
    console.log(user.emailVerified)
});

Case4. ユーザーからの問い合わせに対して本人確認をしない

Case1, Case3と近い話なのですが、Firebase Admin SDKを利用した管理画面を構築できているサービスにおいては、かなり柔軟な対応を行うことが出来ます。例えば「ログインできなくなりました」みたいな問い合わせに対して、メールアドレス・パスワードの再設定やSNSアカウントの紐づけ等を行うことで対応する等も可能となっています。

この際、メールアドレスやSNSアカウントの設定に関しては特に本人確認を行うことなく、任意のメールアドレスやSNSのユーザーIDを設定することが出来るため、もし問い合わせを行った人がアカウント所有者と別の人の場合、問い合わせ経由でアカウントの乗っ取りを行うことが出来てしまいます。

開発の設計とは異なるのですが、必ず問い合わせに対して対応前に本人確認を行うようにしましょう。メールアドレスでログインしているユーザーについてはメールが届くことを確認する(GoogleやApple IDについても同様の方法で確認を行えます)、Twitterアカウントの場合はDMを送信するなど、採用しているログイン方法ごとに本人確認の手段を事前に整理しておくといいと思います。

おわりに

Firebase Authenticationは認証サーバーを自前で構築しなくても手軽にサービスに認証の仕組みを追加できる便利なサービスです。一方でクライアント側から直接APIをリクエストするという設計上、ちゃんと仕組みを理解して構築しないとセキュリティ的な問題が生じます。

また、長期運用を考慮するとFirebaseが提供する管理画面だけでは機能が不足していて、ユーザーのトラッキングが不可能だったり、認証情報のアップデートができないという問題があります。この辺りを理解したうえでFirebaseを採用するのかどうか、採用する場合上記の問題をどのように解決するのかをちゃんと考えるようにするといいかと思います。