【2022年版】Flutter+FirebaseでAppleアカウント認証を実装する

## はじめに

自分で開発しているアプリに以下の3rd-party製の認証をつけてリリース申請をしたところ、Appleから以下のような返事が来た。

![App_Store_Connect.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/5cface3e-5391-f81e-76df-147f7d0b4057.jpeg)

つまり、他のアカウントとの連携認証システム入れるならAppleアカウントとの連携認証も入れなさい、とのこと。

現状は以下のサービスのアカウントとの連携認証を実装しているので、これにAppleアカウントとの連携認証機能も追加していく。

- MailAddress/Password
- Google account
- Twitter account

ちなみに上記のサービスのアカウントとの連携認証に関しては、以下の記事に記述している。

https://qiita.com/ninoko1995/items/9242005432808ae1a7f3

## 実装

Flutter packageで`apple sign in`で調べたら何個かヒットした。
その中で一番likeが多く、READMEも丁寧に書かれていてやりやすそうな以下のパッケージを選択。


https://pub.dev/packages/sign_in_with_apple

基本的にREADMEを読めば問題はないが、英語読むのめんどい時のためにメモ。
大まかな流れは以下。

1. apple developer programでアプリのbundleIdを登録(既にしている場合はスキップ)
2. bundleIdに、`sign in with apple`を許可する
3. FirebaseAuthenticationで、appleでのサインインを設定
4. xcodeへの導入
5. コードを修正

### 1. アプリのBundleIdを登録

page: [Apple Developer: Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list/bundleId)

すでにあったら大丈夫。

### 2. `sign in with apple`を許可
1.のページから、該当アプリのBundleIdを選択し、クリック。
そこで、`Sign in with Apple`を有効化する。

![スクリーンショット_2022-02-04_0_37_25.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/a169e934-5f95-34c9-9b8f-276d5340adac.jpeg)

ユーザ情報の変更通知等を受け取らなくていい場合は、下の部分は気にしないで良い。
![Certificates__Identifiers___Profiles_-_Apple_Developer.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/c0734d95-caba-2553-50fd-b2060e15a1f2.jpeg)

### 3. Firebase Authenticationの設定
iPhoneアプリからしか使わない場合は、下は無視して良い。
![photo-app_-_Authentication_-_Firebase_コンソール-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/167921b5-4aa9-689a-fd3a-b6638647d2ac.jpeg)

追加されているか確認
![photo-app_-_Authentication_-_Firebase_コンソール.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/1a504f6a-3edb-21c9-dc7b-3305daedc275.jpeg)

### 4. xcodeへの導入
該当アプリの`runner.xcodeprj`ファイルをxcodeで開き、`Capability`をクリックし、`Sign in With Apple`を検索しクリック。
そうすると、画像のように`Sign in With Apple`がアプリに追加される。

![Runner_xcodeproj.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/4f7fc253-5341-53c4-2098-7e836dc2c968.jpeg)

### 5. コードの修正

以下のブランチが、今回の変更を行っているブランチ。

https://github.com/ninoko1995/FlutterFirebaseDemo/tree/add_apple_account_authentication

以下のPRを見ると、差分がわかりやすいかもしれない。

https://github.com/ninoko1995/FlutterFirebaseDemo/pull/2

パッケージの導入

```yaml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  # 以下追加
  sign_in_with_apple: ^3.3.0
```

パッケージの利用

```dart:sign_in_screen.dart
import 'package:sign_in_with_apple/sign_in_with_apple.dart';

// ログインメソッド
Future<void> _onSignInWithApple(User? user) async {
    try{
      
      // AuthorizationCredentialAppleIDのインスタンスを取得
      final appleCredential = await SignInWithApple.getAppleIDCredential(
        scopes: [
          AppleIDAuthorizationScopes.email,
          AppleIDAuthorizationScopes.fullName,
        ],
      );
      
      // OAthCredentialのインスタンスを作成
      OAuthProvider oauthProvider = OAuthProvider('apple.com');
      final credential = oauthProvider.credential(
        idToken: appleCredential.identityToken,
        accessToken: appleCredential.authorizationCode,
      );

      if (user !=null && user.isAnonymous) {
        await user.linkWithCredential(credential);
        Navigator.of(context).pop();
      } else {
        await FirebaseAuth.instance.signInWithCredential(credential);

        if (Navigator.of(context).canPop()) {
          Navigator.of(context).pop();
        } else {
          Navigator.of(context).pushReplacement(
            MaterialPageRoute(
              builder: (_) => PhotoListScreen(),
            ),
          );
        }
      }
    } catch(e) {
      await showDialog(
          context: context,
          builder: (context) {
            return AlertDialog(
              title: Text('エラー'),
              content: Text(e.toString()),
            );
          }
      );
    }
  }

// コンポーネント
if (Platform.isIOS) SignInButton(
  Buttons.Apple,
  onPressed: () {
  　　_onSignInWithApple(user);
  },
)
```

#### UIパッケージの利用

Buttonの見た目には、`flutter_signin_button`というパッケージを用いている。

#### Credentialの扱い
`sign_in_with_apple`を用いて取得した`AuthorizationCredentialAppleID`は、そのままfirebaseに渡すことはできない。
Firebaseに渡す用に、`AuthorizationCredentialAppleID`から情報を抜き出して`OAthCredential`のインスタンスを作成しなければならない。

```dart:
      // AuthorizationCredentialAppleIDのインスタンスを取得
      final appleCredential = await SignInWithApple.getAppleIDCredential(
        scopes: [
          AppleIDAuthorizationScopes.email,
          AppleIDAuthorizationScopes.fullName,
        ],
      );
      
      // OAthCredentialのインスタンスを作成
      OAuthProvider oauthProvider = OAuthProvider('apple.com');
      final credential = oauthProvider.credential(
        idToken: appleCredential.identityToken,
        accessToken: appleCredential.authorizationCode,
      );
```

ちなみに`AuthorizationCredentialAppleID`に含まれる情報は以下。

```dart:authorization_credential.dart
@immutable
class AuthorizationCredentialAppleID {
  /// Creates an instance which contains the result of a successful Sign in with Apple flow.
  const AuthorizationCredentialAppleID({
    @required this.userIdentifier,
    @required this.givenName,
    @required this.familyName,
    required this.authorizationCode,
    @required this.email,
    @required this.identityToken,
    @required this.state,
  });
  ...

```

どの変数が何を意味しているかは、以下の記事をみるとなんとなくわかる。
完全にはわからないが、動いたからよしとしている。

https://qiita.com/tamappe/items/fa7ba61f57c20cb26b75

## 既知の問題
ちなみに、**simulatorでいくら試してもこのコードは動かない(2022/2/3時点)**。
githubディレクトリ飛んでissueでも作って文句言ってやろうかと思ったら、すでにあった。
どうやらsimulatorの問題らしく、実機でテストをするときちんと動くことが確認できた。

https://github.com/aboutyou/dart_packages/issues/270

![スクリーンショット_2022_02_04_1_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/92a3cf28-1e76-21ad-565b-a5ec72271e15.jpeg)

## まとめ

今回は、appleから直々に怒られたので、appleアカウント連携認証を実装してみた。
全然難しくない変更ではあるが、simulatorの問題でめちゃくちゃ引っかかってしまった。
xcode周りは挙動が怪しいので、コードを疑いつつソフトウェアそのものにも疑いを持つようにしよう。