NCMBのFlutter SDKを使ってSign in with Appleを実装する

NCMBでは多様なプラットフォームに対してSDKを提供しています。公式ではiOS（Swift/Objective-C）、Android（Java/Kotlin）、JavaScript（Web/Node.js/Cordova/Monaca）、Unityがあります。さらにコミュニティSDKとしてRubyやPython、PHPなどのサーバーサイド言語、React NativeやFlutter向けのSDKも開発されています。

今回はそんなコミュニティSDKの一つ、Flutter SDKを使ってSign in with Apple認証を実装します。ソーシャル認証であればパスワードやメールアドレスの管理を行わずに済み、手軽に利用できます。

## Appleの開発者用サイトでSign in with Appleを有効化

手順が多いので詳細は[Sign in with Appleを利用する際に必要な情報について - ニフクラ mobile backend（mBaaS）お役立ちブログ](https://blog.mbaas.nifcloud.com/entry/2020/05/18/133052)を参照して欲しいのですが、最終的に必要な情報は下記の4つになります。

- アプリのバンドルID（App ID）
- AppleチームID
- AppleキーID
- 秘密鍵（p8ファイル）

## NCMBの設定

NCMBのSNS連携にて、先ほど取得した4つの情報を設定します。Apple ID連携も許可して保存してください。

![FireShot_Capture_300_-_20220511113104_ニフクラ_mobile_backend_-_console_mbaas_nifcloud_com.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e2fe1513-7034-a849-ed7a-ec37bf4f6bf3.jpeg)


## Flutterの実装

ここからはFlutterでアプリを生成後の実装です。

### Xcodeの設定

#### CapabilitiesでSign in with Appleを追加

Signing & Capabilitiesで、Sign in with Appleを追加してください。

![スクリーンショット_2022-05-10_14_13_57.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/074f0f46-b9f7-a6d7-40d7-0786696bff35.jpeg)


### 実装について

#### ライブラリ、SDKのインストール

利用するライブラリ、SDKをインストールします。 `package_info_plus` はバンドルIDを取得するものなので、固定文字列で指定する場合には不要です。

```
flutter pub add the_apple_sign_in
flutter pub add package_info_plus
flutter pub add ncmb
```

#### ライブラリのインポート

インストールしたライブラリ、SDKをインポートします。

```dart
import 'package:the_apple_sign_in/the_apple_sign_in.dart';
import 'package:ncmb/ncmb.dart';
import 'package:package_info_plus/package_info_plus.dart';
```

#### NCMBの初期化

ニフクラ mobile backendにて取得したアプリケーションキー、クライアントキーを使って初期化します。

```dart
void main() {
  // NCMBの初期化
  NCMB('YOUR_APPLICATION_KEY', 'YOUR_CLIENT_KEY');
  runApp(const MyApp());
}
```

#### ステートクラスの変数

ステートクラスではログイン状態を管理するための NCMBUser オブジェクトを定義します。初期値は null です。

```dart
class _MyHomePageState extends State<MyHomePage> {
  NCMBUser? _user;
```

#### ステートクラスの初期化

ステートクラスの初期化 `initState` では認証状態をチェックします。ログインしている場合には `await NCMBUser.currentUser()` でNCMBUserオブジェクトが取得できます。認証していない場合には null が返ってきます。

```dart
@override
void initState() {
	super.initState();
	Future(() async {
		// 現在ログインしているユーザー情報（未ログインの場合はnull）を取得
		final user = await NCMBUser.currentUser();
		setState(() {
			_user = user;
		});
	});
}
```

#### 画面構築

`build` は次のように記述しています。 `_user` の状態によって表示を変えています。そして認証していない場合には `login` 、認証している場合には `logout` を処理するボタンを配置しています。

未ログイン時用に `AppleSignInButton` というコンポーネントが用意されています。Appleの推奨するボタンデザインで表示してくれるコンポーネントです。

```dart
@override
Widget build(BuildContext context) {
	return MaterialApp(
		home: Scaffold(
			appBar: AppBar(
				title: const Text('Apple Login App'),
			),
			body: Center(
					child: _user == null
							// 未ログインの場合
							? AppleSignInButton(
									onPressed: login,
								)
							// ログインしている場合
							: TextButton(
									child: Text(
											'Logged in by ${_user!.getString('displayName', defaultValue: 'No name')}'),
									onPressed: logout,
								)),
		),
	);
}
```

![Simulator Screen Shot - iPod touch (7th generation) - 2022-05-10 at 14.17.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/33f29be0-0a9b-c81b-1544-e6da0d031058.png)


#### login処理

ログイン処理です。ちょっと長いですが、 Sign in with Appleは `TheAppleSignIn.performRequests` だけで完了します。スコープは必要な情報に対して行いますが、今回はメールアドレスとフルネームだけです。レスポンスのステータスが `AuthorizationStatus.authorized` であればログイン成功です。ログイン成功していない場合（ユーザーキャンセルやエラー）は何もせず終了します。

ログイン成功した場合には、アクセストークンを取得します。さらにIDとバンドルIDを組み合わせて、リクエストデータを作成します。後は `NCMBUser.loginWith` でSign in with Appleが完了します。

Sign in with AppleではuserNameなどは自動生成された文字列でユーザーが作成されます。そこで displayName フィールドにAppleから取得した表示名を適用してデータ更新しています。

```dart
// ログイン処理
login() async {
	final result = await TheAppleSignIn.performRequests([
		const AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
	]);
	if (result.status != AuthorizationStatus.authorized) return;
	final credential = result.credential!;
	final accessToken = utf8.decode(credential.authorizationCode!.toList());
	final info = await PackageInfo.fromPlatform();
	// ログインを実行して結果を受け取る
	final data = {
		'id': credential.user,
		'access_token': accessToken,
		'client_id': info.packageName
	};
	// ログイン実行
	var user = await NCMBUser.loginWith('apple', data);
	// 表示名を追加
	var name = credential.fullName!;
	final displayName = "${name.givenName} ${name.familyName}";
	user.set('displayName', displayName);
	// 更新実行
	await user.save();
	// 表示に反映
	setState(() {
		_user = user;
	});
}
```

![Simulator Screen Shot - iPod touch (7th generation) - 2022-05-10 at 14.17.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f1ab2f2a-9085-12d0-26e4-f7a8aa04abe1.png)


#### ログアウト処理

`logout` メソッドは以下のようになります。 `NCMBUser.logout()` で認証データを削除し、 `_user` を null にすることで画面表示を更新しています。

```dart
// ログアウト処理
logout() async {
	await NCMBUser.logout();
	setState(() {
		_user = null;
	});
}
```

## まとめ

何らかのソーシャル認証を実装する場合、Sign in with Appleの実装も必須とされています。この方法を使ってFlutter SDKでもSign in with Appleを実装してください。

NCMBを使うことでユーザーごとにデータアクセス権限を制御したり、ソーシャルログイン機能が手軽に実装できます。ぜひお試しください。

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)