Flutter SDKでNCMBのFacebook認証を行う

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を使ってFacebook認証を実装します。ソーシャル認証であればパスワードやメールアドレスの管理を行わずに済み、手軽に利用できます。

## Facebookアプリの作成とキーの取得

Facebookの開発者向けサイトにて、アプリを作成します。そしてアプリのIDとクライアントトークンを取得します。アプリの設定を行う時にバンドルIDも適切なものを指定してください。

![FireShot_Capture_286_-_20220509152022_NCMBAuthDemo_-_Settings_-_Facebook_for_Developers__-_developers_facebook_com.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/a4f872b0-a2a4-cd7a-de36-9c08ff256b4d.jpeg)

## NCMBの設定

NCMBのSNS連携にて、先ほど取得したアプリIDを設定します。Facebook連携も許可して保存してください。

![FireShot_Capture_284_-_20220509131235_ニフクラ_mobile_backend_-_console_mbaas_nifcloud_com.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/d755ebc8-c2f3-028f-799a-d2cd8e6ff01d.jpeg)

## Xcodeの設定（iOS向け）

Xcodeで `ios/Runner.xcworkspace` を開いて、バンドルIDを設定します。

### Podfileの編集

利用するライブラリが11.0以降にのみ対応なので、 `ios/Podfile` を開いて編集します。

```
platform :ios, '11.0'
```

## Flutterの実装

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

### アプリの設定

#### Android

`android/app/src/main/res/values/styles.xml` を開いて、以下を記述します。`FB_APP_ID` はFacebookで取得したアプリID、 `FB_CLIENT_TOKEN` はクライアントトークンです。

```xml
<resources>
	<!-- 以下を追加 -->
  <string name="facebook_app_id">FB_APP_ID</string>
  <string name="facebook_client_token">FB_CLIENT_TOKEN</string>
```

#### iOS

`ios/Runner/Info.plist` を開いて以下を記述します。 `FB_APP_ID` と書かれている部分をFacebookアプリのIDに、 `FB_CLIENT_TOKEN` をクライアントトークンに書き換えてください。 `fbFB_APP_ID` は `fb99999999` のようにします。

```xml
<key>CFBundleURLTypes</key>
<array>
	<dict>
		<key>CFBundleURLSchemes</key>
		<array>
			<string>fbFB_APP_ID</string>
		</array>
	</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>FacebookAppID</key>
<string>FB_APP_ID</string>
<key>FacebookClientToken</key>
<string>FB_CLIENT_TOKEN</string>
<key>FacebookDisplayName</key>
<string>NCMB Auth</string>
```

### 実装について

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

利用するライブラリ、SDKをインストールします。

```
flutter pub add flutter_facebook_auth
flutter pub add ncmb
```

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

インストールしたライブラリ、SDKをインポートします。intlは後述する日付フォーマット用です。

```dart
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:ncmb/ncmb.dart';
import 'package:intl/intl.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` を処理するボタンを配置しています。

```dart
@override
Widget build(BuildContext context) {
	return MaterialApp(
		home: Scaffold(
			appBar: AppBar(
				title: const Text('Facebook Login App'),
			),
			body: Center(
					child: _user == null
							// 未ログインの場合
							? TextButton(
									child: const Text('Login With Facebook'),
									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-09 at 15.12.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0f3388cc-038e-fe08-7255-75dfc1ee0b18.png)


#### login処理

ログイン処理です。ちょっと長いですが、Facebook認証は `FacebookAuth.instance.login()` だけで完了します。その後、ログインステータスをチェックしてログイン成功していない場合（ユーザーキャンセルやエラー）は何もせず終了します。

ログイン成功した場合には、ユーザーデータを取得します（FacebookAuth.instance.getUserData()）。認証データとユーザーデータを使って、リクエストデータを作成します。認証用データは少し複雑なので注意してください。

Facebook認証ではuserNameなどは自動生成された文字列でユーザーが作成されます。そこで displayName フィールドにFacebookのユーザー名を適用してデータ更新しています。

```dart
// ログイン処理
login() async {
	final result = await FacebookAuth.instance.login();
	// 認証成功以外は終了
	if (result.status != LoginStatus.success) return;
	// 有効期限が取得できないので30日にしておく
	final now = DateTime.now();
	final expirationDate = now.add(const Duration(days: 30));
	// 日付フォーマット用
	final format = DateFormat("yyyy-MM-ddTHH:mm:ss.S'Z'");
	// ユーザーデータをFacebookから取得
	final userData = await FacebookAuth.instance.getUserData();
	// リクエストデータの組立
	final data = {
		'id': userData['id'].toString(),
		'access_token': result.accessToken!.token,
		'expiration_date': {
			'__type': 'Date',
			'iso': format.format(expirationDate)
		}
	};
	// ログイン実行
	var user = await NCMBUser.loginWith('facebook', data);
	if (user.getString('displayName', defaultValue: '') == '') {
		// 表示名を追加
		user.set('displayName', userData['name']);
		// 更新実行
		await user.save();
	}
	// 表示に反映
	setState(() {
		_user = user;
	});
}
```

#### ログアウト処理

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

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

![Simulator Screen Shot - iPod touch (7th generation) - 2022-05-09 at 14.53.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/3e0dc1c9-165f-bc3e-4602-4586bcd74a63.png)


## まとめ

Facebookは日本はもちろん世界中で利用ユーザーの多いサービスなので、ソーシャルログインとして利用されることが多いです。利用したことがある方も多いでしょう。

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

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