Firebase AuthenticationでTwitterのOAuthを利用し、Twitter APIにリクエストする

[2018年5月にTwitterKitのサポートが終了](https://blog.twitter.com/developer/ja_jp/topics/community/2018/TKSDK_tr.html)し、Firebase Authenticationでも[OAuthを利用したTwitterログイン](https://firebase.google.com/docs/auth/ios/twitter-login?hl=ja)が推奨されるようになりました。
設定まわりに関してはすでにいくつか記事があるので、主にiOS/Swiftでの実装部分について書きたいと思います。

というわけでさっそく作っていきましょう👨‍🍳🐟

## 環境
* iOS 11.0〜
* Swift 5
* FirebaseAuth
* [OAuthSwift](https://github.com/OAuthSwift)
* [KeychainAccess](https://github.com/kishikawakatsumi/KeychainAccess)

## TwitterのDeveloperアカウントの登録
[Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ　※2019年8月時点の情報](https://qiita.com/kngsym2018/items/2524d21455aac111cdee)
こちらが大変参考になります💁‍♀️

## Firebase Authenticationの設定
このへんまでは[公式ドキュメント](https://firebase.google.com/docs/auth/ios/twitter-login?hl=ja)を見れば特に迷うことなく実装できると思います💁‍♂️
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118028/03ca7a93-8f3f-a90a-d6fa-6af4ca43e5a2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118028/76ebc7ea-768d-3dd0-cde0-b04a2d3aaafc.png)

設定で一点気をつけないといけないのが、Firebase Cloud Messagingで**[メソッド実装入れ替え](https://firebase.google.com/docs/cloud-messaging/ios/client?hl=ja)を無効にしている場合**です。
認証後にSafariViewControllerが閉じなくなり、ログインに失敗します。
Info.plistで`FirebaseAppDelegateProxyEnabled`を設定している場合は、値をYESにする必要があります。

また`UIWindowSceneDelegate`が実装されている場合にも、上記と同様の現象が発生することがあります。
その場合は以下を行ってみましょう。

```
1. SceneDelegate.swiftをProjectから削除
2. AppDelegate内の関連する箇所でエラーが出るのでコードを削除
3. Info.plistのApplication Scene Manifestを削除
4. AppDelegateのプロパティにUIWindowを追加
```

## FirebaseAuthでOAuth認証を実装
ざっくりと以下のような処理になります（エラー処理などは省略しています）
取得したAccessTokenとSecretはとりあえずKeychainに登録しています。

```swift
class Login {
    private var provider: OAuthProvider?

    func signinByTwitter() {
        provider = OAuthProvider(providerID: "twitter.com")
        provider?.getCredentialWith(nil) { credential, error in
            guard let credential = credential, error == nil else {
                return
            }
            
            Auth.auth().signIn(with: credential) { result, error in
                guard error == nil else {
                    return
                }
                
                if let credential = result?.credential as? OAuthCredential,
                    let accessToken = credential.accessToken,
                    let secret = credential.secret {
                    
                    let keychain = Keychain(service: Bundle.main.bundleIdentifier!)
                    try? keychain.set(accessToken, key: "accessTokenKey")
                    try? keychain.set(secret, key: "accessTokenSecretKey")
                }
            }
        }
    }
}
```

実装でも一点気をつけることがあって、`OAuthProvider`をクラスのプロパティなど**参照が保持される形で持っておく必要があります**。
OAuthProviderが解放されてしまうと、内部的にメソッドが呼ばれず認証用のSafariViewControllerが表示されません。

## OAuthSwiftでAPIにリクエスト
FirebaseでOAuthのAccessTokenとSecretをすでに取得しているので、TwitterのAPIリクエストには`OAuthSwiftClient`を利用しています。
`ConsumerKey`や`ConsumerKeySecret`はDeveloperサイトから取得しましょう。
今回は設定情報から表示名をとってくることにします。

```swift
struct TwitterSetting: Decodable {
    let screenName: String
    
    enum CodingKeys: String, CodingKey {
        case screenName = "screen_name"
    }
}

class TwitterDataStore {
    func fetchSettings() {
        let consumerKey = "ConsumerKey"
        let consumerKeySecret = "ConsumerKeySecret"
        let keychain = Keychain(service: Bundle.main.bundleIdentifier!)

        guard let accessToken = try? keychain.get("accessTokenKey"),
            let accessTokenSecret = try? keychain.get("
accessTokenSecretKey"),
            let url = URL(string: "https://api.twitter.com/1.1/account/settings.json") else {
            return
        }
        
        let client = OAuthSwiftClient(
            consumerKey: consumerKey,
            consumerSecret: consumerKeySecret,
            oauthToken: accessToken,
            oauthTokenSecret: accessTokenSecret,
            version: .oauth1
        )

        client.get(url) { result in
            switch result {
            case .success(let response):
                guard let setting = try? JSONDecoder().decode(TwitterSetting.self, from: response.data) else {
                    return
                }
                
                print(setting)
                
            case .failure:
                break
            }
        }
    }
}
```

これで表示名が表示されるはずです✌️