Flutter・Firebase で Android アプリの Sign in with Apple 対応をする🎯🔥🤖🍎
[2022年8月26日追記] firebase_auth 3.7.0 から簡単になりました
コード的には以下だけで済むようになりました:
import 'package:firebase_auth/firebase_auth.dart';
Future<UserCredential> signInWithApple() async {
final appleProvider = AppleAuthProvider();
if (kIsWeb) {
await _auth.signInWithPopup(appleProvider);
} else {
await _auth.signInWithAuthProvider(appleProvider);
}
}
各種設定は必要なので、以下を見ながらFirebaseコンソールなど弄って設定しましょう。
以下、元の記事も残しておきます🐶
iOS 13から導入された Sign in with Apple (以下、「Apple認証」と表記)ですが、それをAndroid上でFlutter・Firebaseを用いて対応する方法を説明していきます。
※ 本記事を参考にする場合、セキュリティリスクなど自己責任でお願いします。もし誤りなど見付けた場合はご指摘いただけたらすぐに反映するのでよろしくお願いします🙏
Android上でのApple認証の現状
https://firebase.flutter.dev/docs/auth/social#apple の記載では https://github.com/FirebaseExtended/flutterfire/issues/2691 を理由に未対応になっています。
また、AndroidでもApple認証で The required action is invalid.
という エラーが発生してしまうというissueがあって、微妙な感じです🤔
ただ、 sign_in_with_apple パッケージの方式を参考に組んだら正常に動いたのでその方法を紹介します。
大まかな流れ
以下が大まかな流れです。
- Apple認可サーバーからリダイレクトされるWeb APIをCloud Functionsで用意しておく
- Androidアプリ上でApple認証を要求されたら、その用意したリダイレクトURL・ハッシュ化したnonce・stateなどのパラメーター付きでApple認証サイトをChromeカスタムタブにて開く
- ユーザーがログインに成功したら、用意したWeb APIにリダイレクトされる
- Web APIではインテントURLスキームを用いてAndroidアプリにリダイレクトしながらApple認可サーバーから受け取った認証情報(identityToken・authorizationCode・stateなど)を渡す
- 認証情報を受け取ったアプリは
state
が渡した値と同一であることを検証 - identityToken・authorizationCode・nonce(ハッシュ化前)から組み立てた認証情報をFirebase Authentication SDKに渡してログイン(内部的にJWT検証が行われる)
それでは、順に対応していきます。iOS版のApple認証は済ませた状態前提での説明です。
Apple認可サーバーからリダイレクトされるWeb APIをCloud Functionsで用意
次のようなCloud Functionsを組んでデプロイします。
インテントURLスキームを受け取れるようにしておく
上の以下の値でアプリが開かれたときに処理がされるようにします。
intent://callback
scheme=signinwithapple
sign_in_with_apple パッケージをインストールして、そのREADMEの記載の通りに、 android/app/src/main/AndroidManifest.xml
に以下を追加します。上記のパスとschemeが一致していることが分かります。
これをAndroidアプリで受け取ると、sign_in_with_apple パッケージの次のActivityが呼ばれて、158行目でApple認可サーバーからリダイレクトWeb API経由で受け取った認証情報をMethod ChannelでFlutter側に渡していることが分かります。
Service IDの作成
さらに、Service ID周りの設定が必要です。まず、Configure Sign in with Apple for the web の手順に従って、Service IDを設定して対象のアプリと紐づけておきます。
Service IDに、Apple認可サーバーから上で用意したWeb APIへのリダイレクト許可設定
仮に、デプロイしたURLが https://us-central1-mono-firebase.cloudfunctions.net/siwa だとすると、作成したService ID のWebsite URLs
に次のように指定して保存します。
Service IDのFirebase Authenticationへの紐付け
さらに、Firebase AuthenticationのApple認証の設定で、Service IDを紐づけて保存します。Firebase SDKのログインメソッド実行時などにJWT検証がされますが、これが不合致だとそこでエラーが発生します。
JWT検証は公開鍵のみで行えるため、その他の秘密鍵などの項目は本記事の自前コールバックWeb API方式で組む場合は空欄で大丈夫です(Firebase Authenticationだけに頼る場合はiOS以外では必要になるはずですが秘密鍵を使った操作が必要な場面が理解できていません🤔)。
Flutterアプリでログインボタンを押された時の処理
Flutterアプリでログインボタンを押された時の処理は、各種パッケージを活用しながら次のように記述します。sign_in_with_appleパッケージの内部処理によって、AndroidではwebAuthenticationOptions・nonce・stateに指定した値のパラメーター付きでApple認証サイトをChromeカスタムタブにて開かれます。
以下は省略しても動きますが、セキュリティ対策のために必ず指定することをお勧めします。
nonce
: リプレイアタック対策state
: CSRF対策
実行すると、次のような画面を経て、appleCredentialが得られるはずです。
先述の通り、以下の処理が走っています。
- Apple認可サーバーでのログイン操作
- リダイレクトWeb APIに渡ってきた認証情報をIntent経由でアプリに渡す
- Activityが起動されて、渡された認証情報をFlutter側にMethod Channelで渡す
appleCredentialを用いてFirebase Authenticationにログイン
stateは自分で検証し、nonceはハッシュ化前の値を認証情報に含めてFirebase Authentication SDKにJWT検証時に使ってもらえるようにします。
ユーザー名は初回Apple認証時しか取れないため、必要に応じてそれを活用します(14–19行目)。
https://appleid.apple.com/account/manage の次の画面からApple認証を解除すると初回Apple認証を何度も再現できます👌
サンプルリポジトリ
Firebase・Flutterプロジェクトが含まれるリポジトリはこちらです。
上記手順に従って諸々設定して、Flutterサンプルプロジェクトの以下などを変更すればそのまま動くはずです🐶
- Service ID (clientId)
- redirectUri (リダイレクトWeb APIのURL)
- applicationId
- Firebase接続ファイル