🌐

Flutter+Firebaseで、パスワードなしメールアドレス認証基盤

14 min read

Flutter+Firebaseで、メールアドレスだけで認証できる、パスワードなし認証基盤を作成しました。その実装例をGitに公開したので、その仕組みと、動作させるための設定手順を説明します。

iOSの検証には「Appleのデベロッパーアカウント」が必要になります!!
(App IDにAppleアカウントのTeamIDが含まれており、TeamIDをアプリに署名するにはデベロッパーアカウントが必要なため)

モチベーション

この記事を書く目的は、パスワードなしメールアドレス認証基盤の実装が、Flutter経験のみの開発者には複雑すぎたので「これさえ見れば動くモノをつくれる記事」をまとめて、同じ苦しみを経験せず楽に実装できるようにしたい。 です。

メールアドレス認証を選んだ理由は単純で、「FlutterでiOS/Android両方楽につくりたい」「二段階認証したい」「電話番号認証したくない(と多くのユーザーが思ってるはず)」「SSOだけじゃ心許ない」といったものでした。

しかし、いざ実現しようとすると、そもそもの仕組みが複雑であり、かつ手間のかかる設定が必要だったため、さまざまな記事や設定を右往左往し、何度も検証をする必要がありました。また、iOSとAndroidで仕組みが異なっていたり、flutterfireリポジトリの実装例が全然使えなかったりと、いろいろ辛い部分があったので、検証が終わったら「最低限の動く実装例」と「設定手順」を作成しようと思って、今に至ります。

実装例

リポジトリを以下に公開しています。

https://github.com/sadahry/ff-maillink-login-example

コミットとしては、このあたりを参考にしていただくと、自前実装もやりやすいかと思います。

https://github.com/sadahry/ff-maillink-login-example/commit/02284a8332bf15d175fe3026ed0b700ff055f5df

ただ仕組み上、リポジトリをクローンorコピペしただけでは動作せず、「開発者固有の設定」が必要になります。(理由は後述)

この記事では、その設定手順を記載します。

認証処理フロー

先に、どういった処理フローで認証が行われているのかを説明します。
(ざっくりイメージを掴んでもらうため。すぐ動かしたい方は設定手順まで読み飛ばしてOK。)

やっていることは「メールアドレスをFirebaseに送信してメールリンクを受け取り、メールアプリからそのリンクにアクセスすることでメールアドレスが認証される」だけなのですが、サービス間の通信やセキュリティ担保のために、かなり複雑になっています。

この認証処理フローは大きく3段階に分類できます。それぞれ説明します。

※元リンクはこちら

https://github.com/sadahry/ff-maillink-login-example/blob/master/maillink_entire_flow_image.png

Firebase Authへ「メールリンクの作成とメール送信」をリクエスト

  • Firebase AuthからFirebase Dynamic Linksへ、リンクURL発行をリクエスト

Firebase Authから送られたメールの「リンクを端末上アプリで開いてもいいか?」を検証

  • 偽アプリを開いてしまわないように、Request Link JSON(= Link JSONをFirebase Dynamic Linksからダウンロード)し、Valid App Info?(= 端末上アプリの情報と突合)する
  • Link JSON URLは、Dynamic Linkを作成して同Firebaseプロジェクト上にAndroid/iOSプロジェクトを作成すると自動で生成されるURL
  • AndroidとiOSで、突合する内容が異なる

「①で入力したメールアドレス(=点線部)」と「②のリンク内のトークン(=実線部)」を検証

  • 誤ってリンクを他者が開いてもユーザー登録されないように、リンクURLにはメールアドレスを直接含まずトークンを検証に利用
  • Firebase Auth上で検証し、完了と同時に「ユーザー作成」「ログイン成功」となる
  • 同一メールアドレスのユーザーが存在する場合は、作成をスキップ

設定手順

ここから、具体的にアプリを動かすための設定手順を説明していきます。

  • リポジトリのクローン
  • Firebaseセットアップ
  • Firebase Dynamic Links作成
  • Firebase Auth設定
  • Android事前準備+セットアップ
  • iOS事前準備+セットアップ
  • ソースコードの書き換え

リポジトリのクローン

ff-maillink-login-exampleリポジトリをクローンしてください。

$ git clone git@github.com:sadahry/ff-maillink-login-example.git

Firebaseセットアップ

Firebaseプロジェクトを持っていない場合は新たに作成してください。

Firebase Dynamic Links作成

Firebase Dynamic Links を構成する1.Firebase Dynamic Links を有効にします。 を参考に構築してください。

以下のように設定を行います。(ffloginexample.page.link は私が利用しているため、他のユーザーと被らないドメイン(e.g. hogehoge.page.link)を指定して登録してください)

Firebase Auth設定

MailLinkの許可

Firebase プロジェクトでメールリンク ログインを有効にする を参考に設定してください。

この設定で「[メール / パスワード]を有効」にする必要がありますが、アプリ上ではパスワードを入力することなく認証が可能です。

Firebase Dynamic Linksの許可

以下のように、Authenticationの Sign-in Method -> 承認済ドメイン に、あなたがFirebase Dynamic Links作成時に設定したドメイン(e.g. hogehoge.page.link)を登録してください。

OSごとのセットアップ

Android/iOSごとに別の設定が必要になるため、それぞれ説明します。

Android/iOSセットアップにより、ローカル端末上で作成されたアプリの「署名情報」がFirebase上に保管されます。そうすることで、Firebase Authが作成するリンクを開くとき、「端末上アプリの署名情報」と「Firebase上に保管された署名情報」を突合して、偽アプリによるリンク情報へのアクセスを防ぐことができます。この設定には、端末上アプリが関係するため、これが「開発者固有の設定」が必要となる理由です。

Android

事前準備

Androidのセットアップには、SHA-256証明書フィンガープリントが必要になります。

SHA-256証明書フィンガープリントの取得

Android Studio上から、signingReport タスクを実行することで取得できます。

まずff-maillink-login-example/androidフォルダを「Androidモジュール」として改めて開きます。

正しく設定されていると左側の Gradle タブをクリックすると表示されるのですが、デフォルトでは表示されません。

表示されるように Android Studio -> Preferences -> Experimental をクリックし、 Do not build Gradle ... のチェックを外してください。

その後、File -> Sync Project with Gradle Files を実行すると、

signingReportTasks/android 以下に表示されます。

それを右クリックして Debug 'android [sign... を実行すると、
(Emulaterで動作確認を行うためDebugモードで)

SHA-256キーが出力されます。この文字列をセットアップで利用します。

セットアップ

AndroidのFirebaseセットアップ手順ステップ 3 Firebase 構成ファイルを追加する まで進めてください。

ただし以下のように設定してください。

  • Android パッケージ名は com.example.ff_maillink_login_example
  • アプリ設定ページから SHA-256フィンガープリント証明書を追加
    • 登録ページではSHA-1フィンガープリント証明書しか追加できない

セットアップ確認

セットアップが完了すると、Androidアプリの情報をFirebase Dynamic Linksから取得できるようになります。

Androidの場合は以下のURLから取得できます。
e.g. https://ffloginexample.page.link/.well-known/assetlinks.json

このURLが、認証処理フローにおけるLink JSON URLです。Link JSON URLからOS/Browserが「Firebase上に保管された署名情報」を取得し、偽アプリによるリンク情報へのアクセスを防ぐことができます。このリンク方式をAndroidでは「アプリリンク」と呼びます。

ffloginexample.page.linkを、あなたがFirebase Dynamic Links作成時に設定したドメインに書き換えて接続を試してみてください。

以下のように、設定したフィンガープリントを含むJSONが取得できれば、セットアップは完了です。(反映に1時間ほどかかる場合があります)

[{"relation":["delegate_permission/common.handle_all_urls"],"target":{"namespace":"android_app","package_name":"com.example.ff_maillink_login_example","sha256_cert_fingerprints":["96:3E:72:9A:28:D2:7C:10:A9:14:34:A0:93:1D:A5:04:EB:32:44:C2:A4:6C:A6:00:B2:14:0C:F5:8C:0D:D2:AC"]}}]

iOS

事前準備

iOSのセットアップには、アプリのTeamIDが必要になります。

TeamIDの取得

TeamIDの取得には、Appleアカウントへのアプリ登録が必要になります。

ios/Runner.xcworkspace からXcodeを開き、 Runner -> Signing & Capability を選択します。

そして、まず私のTeamID(CBS798444Y)になっている箇所を、あなたのAppleアカウントに変更してください。

その後、Signing CertificateがApple Developmentになっている(=署名が完了している)ことを確認します。

署名が完了している場合、以下のように、自身のアカウントでログインしたApple Developerサイト上にアプリが表示されるはずです。

そのアプリ名をクリックして表示されるApp ID prefixが、TeamIDになります。

セットアップ

iOSのFirebaseセットアップ手順ステップ 3 Firebase 構成ファイルを追加する まで進めてください。

ただし以下のように設定してください。

  • バンドルIDは com.example.ff_maillink_login_example
  • App Store IDは 999999999
    • App Store上の情報を取得するためのID。リリースしていないため、なんでもOKです
  • アプリ設定ページから TeamIDを追加
    • アプリ登録ページでは設定できないため

セットアップ確認

セットアップが完了すると、iOSアプリの情報がFirebase Dynamic Linksから取得できるようになります。

iOSの場合は以下のURLから取得できます。
e.g. https://ffloginexample.page.link/.well-known/apple-app-site-association

このURLが、認証処理フローにおけるLink JSON URLです。Link JSON URLからOS/Browserが「Firebase上に保管された署名情報」を取得し、偽アプリによるリンク情報へのアクセスを防ぐことができます。このリンク方式をiOSでは「Universal Link」と呼びます。

ffloginexample.page.linkを、あなたがFirebase Dynamic Links作成時に設定したドメインに書き換えて接続を試してみてください。

以下のように、設定したTeamIDを含むJSONが取得できれば、セットアップは完了です。(反映に1時間ほどかかる場合があります)

{"applinks":{"apps":[],"details":[{"appID":"CBS798444Y.com.example.ffMaillinkLoginExample","paths":["NOT /_/*","/*"]}]}}

ソースコードの書き換え

ここまでの設定で取得した文字列のうち、
以下は、Github上のソースコードにも含まれます。

  • Firebase Dynamic Linksのサブドメイン名
  • iOSのTeamID

これらを以下のコミットを参考にし、あなたの文字列に書き換えてください。

https://github.com/sadahry/ff-maillink-login-example/commit/02284a8332bf15d175fe3026ed0b700ff055f5df

これで設定手順は完了です。

動作確認

iOS(Simulator)でSafariへメールリンクをペーストしたときの挙動。"Open app in link?"という確認画面を挟んでからアプリへ遷移する。iOS(実機)でメールアプリ(Gmail)から直接遷移する際には発生しない。

手順

  • 動作確認を行いたい端末へアプリをビルド
  • 入力画面に メールを受信可能な メールアドレスを入力して Register ボタンをタップ

  • メールを開いてリンク(e.g data にログイン)をクリック
    • Emulater/Simulatorの場合は、PC上のブラウザでリンクアドレスをコピーしたあとEmulater/Simulator上のブラウザにペースト

  • アプリへ遷移したあと、ログイン成功のメッセージが表示される

  • ログインに成功したあと、Firebase Authのページにメールアドレスが登録されているのを確認

実機での動作確認

Android/iOS共に検証済。

Firebase Dynamic Linksの一部の記事には、「iOS端末上にて「開発者モード」の「Associate Domains Development」設定をONにする必要がある」という記載も見かけますが、この認証処理フローでは不要です。

Emulater/Simulatorでの動作確認

Emulater/Simulatorとも検証済。

Emulaterでの注意点

Ctrl+Vでコピペができないため、コマンドでペーストする必要あり。
かつ、&をエスケープする必要があるため、以下のようなコマンドになるはず。

$ adb shell input text "https://ffloginexample.page.link/?link=https://....link/emailLink%26lang%3Dja\&apn=com.example.ff_maillink_login_example\&amv=0"

Simulatorでの注意点

Edit -> Automatically Sync PasteboardがONになっていなければ、PCのクリップボードをSimulatorで利用できずコピペできない。ONにしてからリンクアドレスをコピーする必要あり。

最後に

おつかれさまでした。長い記事を読んでいただきありがとうございます。
(設定を試した方はもっと有難いです。フィードバックいただけたら嬉しいです。)

認証処理フローでも話した通り、やっていることは単純で、アプリのUXも実装もシンプルです。設定は複雑ですが、ユーザーに価値を産む機能だと思うので、もし興味があれば実装してみてください。

また、設定方法だけではなく、ソースコード上の試行錯誤や、リンクの仕組みについても書いておきたかったのですが、まとめて書くことが難しかったので (参考) リンク に断片的に記載しました。気になる記事があれば読んでみてください。

それではまた。

(参考) 開発環境

PC

機種名: MacBook Air
機種ID: MacBookAir10,1
チップ: Apple M1
コアの総数: 8(パフォーマンス: 4、効率性: 4)
メモリ: 16 GB
OS: macOS Big Sur 11.6(20G165)

Flutter

$ flutter --version         
Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 18116933e7 (6 weeks ago) • 2021-10-15 10:46:35 -0700
Engine • revision d3ea636dc5
Tools • Dart 2.14.4

Emulater(Android)

Display name: Pixel 3 API 31
Device name: sdk_gphone64_arm64
Android version: 12

Simulator(iOS)

Model Name: iPhone 13
Model Number: A2482J/A
Software Version: 15.0

Android Studio

Xcode

(参考) リンク

FlutterFire

FlutterFireのメールリンク実装例(メールリンクの送信までは(動かなかったが)参考にできる。ただメールリンクの受信+認証は実装自体が存在しない)

https://github.com/FirebaseExtended/flutterfire/tree/0a16d04af525ee68cf27fa48173d5c955ca3ef4e/packages/firebase_auth/firebase_auth/example

FlutterにおけるFirebase Dynamic Linksの受信方法

https://pub.dev/packages/firebase_dynamic_links#handle-received-dynamic-links

Android

Androidにおけるフィンガープリントの取得方法

https://medium.com/@umayanga.fcb/how-to-find-android-sha1-and-sha256-keys-in-your-flutter-application-project-9d6cfcf9c135
デフォルトではフィンガープリント取得タスクが表示されないため、設定の変更が必要
https://stackoverflow.com/questions/67405791/gradle-tasks-are-not-showing-in-the-gradle-tool-window-in-android-studio-4-2

iOS

Firebase Dynamic Linksの活用

https://logmi.jp/tech/articles/320720

Universal Linkの仕様について

https://www.wantedly.com/companies/wantedly/post_articles/305303

Universal LinksにはDeveloper登録が必要

https://qiita.com/toya108/items/73e4c683c78c9a4aa3d8

efr=1をつけるとOpen Appの確認をスキップできる(ただしメールリンク認証のリンクはFirebase Authで作成されるためefr=1をつけられない)

https://firebase.google.com/docs/dynamic-links/create-manually

クリップボード通知

FirebaseDeepLinkPasteboardRetrievalEnabledをNO(=false)にするとクリップボード通知が来ないよう設定できる

https://firebase.google.com/docs/dynamic-links/ios/receive?hl=ja#set-up-firebase-and-the-dynamic-links-sdk

Simulatorの場合、CoreSimulatorBridgeがクリップボードを利用してしまう。そのため Edit -> Automatically Sync PasteboardもOFFにする必要あり

https://stackoverflow.com/questions/15188852/copy-paste-text-into-ios-simulator

"Open app in link?"の仕様

https://developers-jp.googleblog.com/2017/10/whats-new-with-firebase-dynamic-links.html

Deep Linkという、今回のFirebase Dynamic LinksやUniversal Linksとは異なる旧式のリンク方式。(特にiOSでは)本番環境で利用することはない。

https://docs.flutter.dev/development/ui/navigation/deep-linking

Android/iOSのDeep Linkによる乗っ取りの防止(アプリリンク/Universal Link)

https://akaki.io/2021/url_scheme_hijack

Discussion

ログインするとコメントできます