🌐

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

2021/11/24に公開
4

Deprecated

なので、「こういう仕組みがあったんだ」程度に読み物として見ていただければ幸いです orz

概要

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

モチベーション

この記事を書く目的は、パスワードなしメールアドレス認証基盤の実装が、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

事前準備

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

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の取得と、Provisioning Profile更新が必要になります。

TeamIDの取得

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

まずff-maillink-login-example/iosフォルダをXcodeで改めて開きます。

その後、Runner -> Signing & Capability を選択します。

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

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

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

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

Provisioning Profile更新

※2022/03/02追記: Kさん、情報ありがとうございますorz

Associated Domains(=ブラウザからiOSアプリへ結びつけ)の許可設定のため、
その設定とProvisioning Profileの更新が必要となります。

まずAssociated Domainsの許可設定として、
TeamIDを取得した画面上にて、以下のチェックを入れてください。

その後、以下手順にて、Provisioning Profileを更新してください。
https://qiita.com/Labi/items/d93624e4f6ec7d073e76

セットアップ

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

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

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

動作確認

手順

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

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

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

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

実機での動作確認

Android/iOS共に検証済。

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

よしかわよしかわ

補足:
1つのFirebase Project内にDynamic LinksのURL 接頭辞(e.g. https://ffloginexample.page.link )を複数登録するとバグるようです。
(2つ目のDynamic LinksをActionCodeSettingsに含めても、メールリンクのホストが1つ目のURL 接頭辞となってしまい、アプリ上でのメールリンク認証に失敗する)

なので、1つのFirebase Project内にDynamic LinksのURL 接頭辞は1つにしたほうが良さそうです。

イロハイロハ

本記事、大変参考になりました。
ありがとうございます。

一点質問させていただきたいです。

メールアドレス入力後にメールは届いたのですが、リンクを踏むと「Dynamic Link Not Found」というブラウザページに飛びます。 エラーメッセージは下記です。
If you are the developer of this app, ensure that your Dynamic Links domain is correctly configured and that the path component of this URL is valid.

質問内容は 、本記事に記載されていない設定などを、Firebase上で行ったりされているならご教示頂きたいです。

本記事通り進めていたのですが、上記現象のため原因等もよく分からない状態です...

よろしくお願いします。

イロハイロハ

解決したのでログとして残します🙇‍♂️

原因は、Apple developerのIdentifiersで、Associated Domainsにチェックを入れていなかったことでした。
チェック後、Provisioning profileを再生成→ダウンロード→ダブルクリックでXcodeに適用 で上手くいきました。

よしかわよしかわ

回答に気がつけずすみません!

Apple developerのIdentifiersで、Associated Domains

こちらのことですね、見逃していました。。

記事に修正反映します。貴重なご意見ありがとうございますm(__)m