【iOS】Facebook ログインを実装してみる

# はじめに
Facebookログインの機能を実装したく、Meta for Developersへの登録もしてない状況でしたが、一からドキュメントを見てやってみました。

https://developers.facebook.com/docs/facebook-login/overview/?locale=ja_JP

# 環境
Xcode 13.3
Swift 5.6
FBSDKLoginKit 13.2.0

# 作業内容

公式ドキュメント見ながらそのまんまやってみた（ものを書きました）

https://developers.facebook.com/docs/facebook-login/ios

## ディベロッパー登録

未登録でしたのでこちらから登録しました

https://developers.facebook.com/

FacebookアカウントでログインしてContinue
![スクリーンショット 2022-05-18 23.38.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/55a31df2-66ff-ce2f-552c-ef4c13fc0ae2.png)
Developerを選択して登録完了
![スクリーンショット 2022-05-18 23.40.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/de90c06e-73e8-8864-5565-86f7015d0f86.png)
Create Appしていきます
![スクリーンショット 2022-05-18 23.41.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/01c00692-0159-f112-713f-2b713127ad7c.png)
適当なものを選んでNext
![スクリーンショット 2022-05-18 23.42.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/5c8fc563-efdb-a653-7d82-d573b1d01f41.png)
必要事項も適当に入力してCreate App
![スクリーンショット 2022-05-18 23.44.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/edf956f6-12aa-6bd2-1469-b4a2e19883d0.png)
無事ダッシュボードが表示されました
![スクリーンショット 2022-05-18 23.46.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/833847fc-05db-18ef-5d44-1710686bfda8.png)


## 実装準備

`Facebook Login`のQuickstart(iOS)をやっていきます

![スクリーンショット 2022-05-19 0.05.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/3ec994ce-5706-54cf-6328-5a40acb6f643.png)
![スクリーンショット 2022-05-19 0.07.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/36933724-8938-138b-e02c-9b3fda8f8e4f.png)

`Cocoapods`を使っていくので
`Podfile`に`pod 'FBSDKLoginKit', '~> 13.2.0'`を追記してインストール
<img width="300" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/ba55add0-2fc2-7362-52f3-41d06cd626ff.png">

### BundleIDの追加

BundleIDをコピペして保存

![スクリーンショット 2022-05-19 0.09.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/cf86dcce-90e6-6596-13df-0b2d3daa8dc5.png)

### シングルサインオンの選択

オンにしてみました

![スクリーンショット 2022-05-19 0.13.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/dfae6314-dd61-84dd-f353-a59fbda6a24e.png)
>「シングルサインオン(Single Sign-On)」は、「シングル 」と「サインオン」を組み合わせた造語で、「1度システム利用開始のユーザー認証 (ログイン) を行うと、複数のシステムを利用開始する際に、都度認証を行う必要がない仕組み」や「1度の認証で、以後その認証に紐づけられている複数のシステムやアプリ・サービスにも、追加の認証なしで利用できる製品・システム・ツール」を意味します。

### plistの修正

![スクリーンショット 2022-05-19 0.16.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/103b8c3a-1d01-6d1d-ac5a-af74b7a0da61.png)

`fbAPP-ID`の文字を『fb + ダッシュボードで確認できるApp-ID』で書き換え
`APP-ID`の文字を『ダッシュボードで確認できるApp-ID』で書き換え
`CLIENT-TOKEN`の文字を『ダッシュボードで確認できるClient token』で書き換え
`APP-NAME`をアプリ名で書き換え

※ 他のSNSログイン等でplistに同項目の追加をしている場合は編集方法が異なる場合もあります

<details><summary>Code</summary>

```xml
<key>CFBundleURLTypes</key>
<array>
  <dict>
  <key>CFBundleURLSchemes</key>
  <array>
    <string>fbAPP-ID</string>
  </array>
  </dict>
</array>
<key>FacebookAppID</key>
<string>APP-ID</string>
<key>FacebookClientToken</key>
<string>CLIENT-TOKEN</string>
<key>FacebookDisplayName</key>
<string>APP-NAME</string>
```

```xml
<key>LSApplicationQueriesSchemes</key>
<array>
  <string>fbapi</string>
  <string>fb-messenger-share-api</string>
</array>
```

</details>

### Keychainの追加

![スクリーンショット 2022-05-19 0.18.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/cc84754b-aa7b-4b3d-2869-fefe3caf8e85.png)

ここまでスムーズにできました

## 実装

### `App Delegate`の修正

`import FacebookCore`ではなく`import FBSDKCoreKit`で良さそう

![スクリーンショット 2022-05-19 0.19.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/b3cb5a21-5cce-0040-23a0-a7df1f3612f6.png)

<details><summary>AppDelegate</summary>

>このコードは、アプリの起動時にSDKを初期化し、LoginまたはShareアクションを実行すると、SDKがネイティブFacebookアプリからログインと共有を処理できるようにします。それ以外の場合は、アプリ内ブラウザーを使ってログインするために、ユーザーがFacebookにログインしている必要があります。

```swift
    
// AppDelegate.swift
import UIKit
import FacebookCore

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {    
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {          
        ApplicationDelegate.shared.application(
            application,
            didFinishLaunchingWithOptions: launchOptions
        )

        return true
    }
          
    func application(
        _ app: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey : Any] = [:]
    ) -> Bool {
        ApplicationDelegate.shared.application(
            app,
            open: url,
            sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
            annotation: options[UIApplication.OpenURLOptionsKey.annotation]
        )
    }  
}
```
</details>

<details><summary>SceneDelegate</summary>

iOS 13 では、オープニング URL 機能が SceneDelegate に移動しました。iOS 13 をお使いの方は、以下のメソッドを SceneDelegate に追加し、ログインや共有などの操作が意図したとおりに動作するようにしてください。

```swift

// SceneDelegate.swift
import FacebookCore
  ...
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else {
        return
    }

    ApplicationDelegate.shared.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: [UIApplication.OpenURLOptionsKey.annotation]
    )
}
```

</details>

### ログインボタンの追加

`import FacebookLogin`ではなく`import FBSDKLoginKit`で良さそう

![スクリーンショット 2022-05-19 0.21.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/8266cd74-ec23-f59f-f5e3-9988a873c117.png)

<details><summary>ViewController</summary>

```swift
// Add this to the header of your file, e.g. in ViewController.swift 
import FacebookLogin

// Add this to the body
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
	
        let loginButton = FBLoginButton()
        loginButton.center = view.center
        view.addSubview(loginButton)
    }
}
```

</details>

見た目はこんな感じになりました

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/1a9271cb-1014-3b0a-dab8-aa8d176f6c7a.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/0a50c901-cd95-9459-ab76-c48e941f9d7c.png">
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/fa7f8edc-bebb-1aab-e8b0-d02bfe80e7a6.png">

このままログインを続けると正常にログインでき、`Log out`ボタンに変わりました
<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/34f790b5-3096-7f83-09ea-50c26e778b3a.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/227932a1-a5d6-1c36-3320-b3593c10630c.png">

Quickstart(iOS)には含まれていませんでしたが
このままだとログイン&ログアウトはできても情報が取得できてないので
`loginFBButton.delegate = self`としてログイン・ログアウト時の処理を追加しました


```swift
extension ViewController: FBSDKLoginKit.LoginButtonDelegate {
    func loginButton(_ loginButton: FBLoginButton, didCompleteWith result: LoginManagerLoginResult?, error: Error?) {
        switch (result?.isCancelled, error) {
        case (_, .some(let error)):
            print(error)
        case (true, .none):
            print("エラー")
        case (_, .none):
            print("ログイン")
            let accessToken = AccessToken.current
        }
    }
    func loginButtonDidLogOut(_ loginButton: FBLoginButton) {
        print("ログアウト")
    }
}
```

https://developers.facebook.com/docs/reference/iossdk/current/FBSDKLoginKit/classes/fbsdkloginbutton.html

https://developers.facebook.com/docs/reference/iossdk/current/FBSDKLoginKit/protocols/fbsdkloginbuttondelegate.html

### ログイン状況の管理

![スクリーンショット 2022-05-19 0.22.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/62602942-7293-060a-4435-1f15ee7a5984.png)

<details><summary>ViewController</summary>

```swift

override func viewDidLoad() {
    super.viewDidLoad()

    if let token = AccessToken.current,
        !token.isExpired {
        // User is logged in, do work such as go to next view controller.
    }
}
```

</details>

https://developers.facebook.com/docs/reference/iossdk/current/FBSDKLoginKit/classes/fbsdkloginmanager.html

### 許可の取得

>アプリには、一度に1人しかログインできません。アプリにログインしている各人をAccessToken.currentで表します。
LoginManagerがこのトークンを設定し、AccessToken.currentを設定すると、キーチェーンストアに自動的に書き込まれます。
AccessTokenにはuserIDが含まれており、これを用いてユーザーを特定することができます。
ロード時に既存のトークンをチェックするように、ビューコントローラを更新する必要があります。これにより、誰かがすでにアプリに権限を与えている場合に、ログインフローを再度表示する必要がなくなります。

![スクリーンショット 2022-05-19 0.24.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/152afcc1-975d-de25-986d-01a1d1ac65f6.png)

```swift
// Extend the code sample from 6a. Add Facebook Login to Your Code
// Add to your viewDidLoad method:
loginButton.permissions = ["public_profile", "email"]
```

サンプル通りのpermissionsで表示したところ、
`public_profile`は名前とプロフィール画像で必須項目、メールアドレスは任意となるようです。

<img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/0799d6cc-7af6-f813-5ad5-968c696db883.png"><img width="200" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/649ad188-724b-adbf-dea7-a5a140042449.png">

>ユーザーは、要求された権限をアプリに付与するよう促されます。一部のパーミッションでは、ログイン審査が必要になることに注意してください。

https://developers.facebook.com/docs/facebook-login/ios/permissions

## おまけ: データ利用チェックアップ

ダッシュボードに`Data Use Checkup`の表示が...

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/f30c75ee-666e-ea77-41fe-56d6b5755aee.png">
<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/f6d76809-da77-2ab2-b32e-bf67a8c7ac38.png">

>データ利用チェックアップの期限は2022年7月18日まで
人々のプライバシーを保護することは、Facebookと、Facebookプラットフォーム上で開発を行う開発者にとっての主要な優先事項です。そのため、APIアクセスとデータ利用がFacebookのポリシーに準拠していることを確認するために、毎年チェックアップをお願いしています。詳細はこちら
APIアクセスを維持し、無効化を回避するために、2022年7月18日までに行う必要があることは次のとおりです。
1.過去に承認または追加した権限、機能、製品を確認します。
2.アプリが許可された使用方法に準拠していることを証明する。
3.acebookプラットフォーム利用規約と開発者ポリシー、およびその他の適用されるすべての規約とポリシーに準拠していることを証明します。
データ使用チェックは、アプリの管理者が年に1回完了しなければならない要件です。

![スクリーンショット 2022-05-20 13.11.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/4ff0289a-e6ac-a355-aa5f-240dbd08fd5c.png)
![スクリーンショット 2022-05-20 13.15.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/31e7cc98-42ce-408b-d518-0c19fb4c2dd5.png)
<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/9369c22c-2204-18bb-beeb-2fa2c4e10efc.png">


## おまけ: その他実装に関連することなどなど

##### Implement a Data Deletion Callback

https://developers.facebook.com/docs/development/create-an-app/app-dashboard/data-deletion-callback

Facebookからデータを削除する人々のリクエストに応えるために、データ削除コールバックを実装する。

##### Add App Events

https://developers.facebook.com/docs/ios/getting-started#app-events

アプリにイベントを追加して、アナリティクスを表示し、広告パフォーマンスを測定し、広告ターゲティングのためのオーディエンスを構築する。

##### Advanced Topics & Setup

https://developers.facebook.com/docs/facebook-login/ios/advanced

iOSアプリのFacebookログインの高度なセットアップを確認します。

##### Permissions

https://developers.facebook.com/docs/facebook-login/ios/permissions

アプリがFacebookログインを通じてアクセスできるデータを管理します。

##### Handling Errors

https://developers.facebook.com/docs/ios/errors

Facebook SDKから返されるエラーに対応する方法を確認する。

##### Testing a Login Flow

https://developers.facebook.com/docs/facebook-login/guides/test

Facebookログインフローが動作することをテストし、確認する。

##### App Review

https://developers.facebook.com/docs/app-review

App Reviewは、アプリが承認された方法でAPIを使用しているかどうかを確認するもの。

# おわりに
思っていたよりも簡単（ログイン機能だけなら）でしたが、実際のサービスで使うにはまだまだやることがあると思うと、それならやっぱりFirebase Authenticationを使う方がいいなと思ったり。
上級者向けのドキュメントもあるので、時間があるときにやっていきたいと思います。

https://developers.facebook.com/docs/facebook-login/ios/advanced

# 参考

https://developers.facebook.com/docs/ios/reference_obj-c

https://stackoverflow.com/questions/60005793/no-such-module-facebookcore-in-swift-5