【iOS】Gmail APIを使って、ユーザーの複数のGmailアカウントからメールを取得する。

# この記事が伝えること
タイトルの内容をやろうとして想定外にハマったのですが、解決できたのでその時わかったことを共有します。
実際の実装の手順も詳しく載せました。
実際の手順は[準備](#準備)からどうぞ。

# やりたかったこと
簡単な**Gmailクライアントアプリ＝メーラー**的なものを作りたいと思った。

OAuth2で、ユーザーの**複数のGmailアカウント**のデータへのアクセスを承認してもらいたい。

**複数のアカウント**というところが今回は重要でした。
メーラーなので、Gmailのアカウントを複数登録できるようにしたい。

# 想定外にハマったこと

[公式のGmailAPIリファレンス](https://developers.google.com/gmail/api/?hl=ja)だけを見ていても、上記の**複数アカウントのOAuth認証**が達成できなかった。
ハマったのはGmail APIというより、GoogleのOAuth認証の実行です。

結論から言うと、今のGoogle APIを使うためのOAuthライブラリは、`Google/SignIn`と、`GTMAppAuth`の**２種類が存在**し、**この記事の目的で言うなら、後者の`GTMAppAuth`を使う方が良いです。**

前者の`Google/SignIn`を使ってやろうとして一向にできず3日くらいの時間を消費しました。ツラい...😂

公式のAPIリファレンスには`SignIn`の方しか書いてないので、そもそもライブラリ自体が違うとか考えもしないわけで...


## 2017年6月現在、iOSアプリ開発において、Google APIを使うためのOAuthライブラリとして何を使うか

まず、"Gmail API"とググって出てくるこの公式のリファレンス[Gmail API  |  Google Developers](https://developers.google.com/gmail/api/)にある[iOS Quick Start](https://developers.google.com/gmail/api/quickstart/ios?ver=swift)では、ユーザーのOAuth2認証に、`Google/SignIn`というライブラリを使用しています。

**が、**今回の目的においては、リファレンスに出てこない`GTMAppAuth`という**もう一つの公式ライブラリを使いました。**

### GoogleSignInとGTMAppAuth
リファレンスに出てこない`GTMAppAuth`って何者なのよって話ですが、Google公式ブログの、APIについての重要な記事がこちら。

[Google Developers Japan: ネイティブ アプリの OAuth インタラクションを最新にしてユーザビリティとセキュリティを向上する / 2016年9月9日金曜日](https://developers-jp.googleblog.com/2016/09/modernizing-oauth-interactions-in-native-apps.html)

以下、上記記事より引用

> この移行をサポートするため、皆様が利用できる最新のベスト プラクティスに基づくライブラリとサンプルを公開しています。

> - Google Sign-In: Android と iOS 向け。Google アカウントを使ったログインと OAuth の推奨 SDK です。
> - AppAuth: Android と iOS および OS X 向け。Google などの OAuth プロバイダで使用できるオープンソース OAuth クライアント ライブラリです。さらに、Objective-C 向け Google API クライアント ライブラリで AppAuth サポートを有効にするライブラリ GTMAppAuth（iOS および OS X 向け）と、GTM Session Fetcher プロジェクトも提供しています。 

とのことです。

よくわかってなかったんですが、いわゆる**「Googleでログイン」**みたいなものを実装したいときは`Google/SignIn`(`GIDSignIn`)を使えば良くて、今回のようにただただOAuthでアクセストークンがほしい！みたいな場合は`GTMAppAuth`を使えば良いっぽいです。
間違ってたら教えてください...🙇

`GTMAppAuth`でググると英語の記事は結構出てきますね。
こんな記事（？）もありました。

[Implementing OAuth 2.0 · MailCore/mailcore2 Wiki - Github](https://github.com/MailCore/mailcore2/wiki/Implementing-OAuth-2.0)
2017年1月ですが、**`GTMOAuth2`は非推奨になったから、今後はGTMAppAuthを使うべきだよ！**とのこと。
今までは`GTMOAuth2`っていうのを使っていたんですね。

ただでさえわかりにくい認証周りを英語で読むの大変です。ひえ〜
日本語が少ないので、この記事では日本語でお役に立てれば...

### 今回の目的における、GoogleSignIn(GIDSignIn)でできなかったこと

- 複数アカウントでのAPIコール
  - APIを叩くクラスへ渡す**認証情報が、サインインしたときしか取得できない**。
  - サインインは一度に１アカウントでしかできない。
- そもそも認証情報が欲しいわけなのだけど、「サインイン」ってちょっと**概念が違う**...
  - セッションみたいに一時的にユーザーが承認されるのではなく、アクセストークンで半永久的に（ユーザーが連携を切るまで）承認してほしい。
- 認証情報の**楽なキーチェーンへの保存**
  - ２度目以降にアプリを開いたときの自動ログインがなんか上手くいかない。

## ググり力
[Gmail API Client Libraries](https://developers.google.com/gmail/api/downloads)の[Objective-C samples](https://github.com/google/google-api-objectivec-client-for-rest/blob/master/Examples/CalendarSample/CalendarSampleWindowController.m)が`GTMAppAuth`を使用しているので、僕はそこから辿り着きましたが、普通はどうやって見つけるんだろう...

"Google OAuth"とかでググると最初に出てくる[Using OAuth 2.0 to Access Google APIs  |  Google Identity Platform  |  Google Developers](https://developers.google.com/identity/protocols/OAuth2)の一番下に`GTMAppAuth`へのリンクがありますね。

うーん"Gmail API"でググっても出てこないわけだ。


`GTMAppAuth`についてはリファレンスページなどはないので、[Githubのレポジトリ](https://github.com/google/GTMAppAuth)のREADMEが2017年6月現在唯一（？）の公式の資料になります。

ググり力と英語力をもっとつけたい。
ヤバい、ポエムみたいになってきた。
次から`GTMAppAuth`を使った実際の手順 in Japaneseに入っていきます！

# 準備
Google Developersでの設定はできてるよ！って方は[Xcode側の準備](#xcode側の準備)からどうぞ！GoogleService-Info.plistは使いません。
## Google Developers側の準備
まず、Google APIを使うには、Google DevelopersでAppを作り、APIを有効化する必要があります。

基本は[iOS Quickstart  |  Gmail API  |  Google Developers](https://developers.google.com/gmail/api/quickstart/ios?ver=swift&hl=ja)の通りですが、今回はちょっとずつ違う箇所があるので説明していきます。

最初にXcodeで新しいプロジェクトを作り、Bundle IDを控えます。

[Add Google Services  |  Google Developers](https://developers.google.com/mobile/add?platform=ios&cntapi=signin&cnturl=https%3A%2F%2Fconsole.developers.google.com%2Fstart%2Fapi%3Fid%3Dgmail&cntlbl=Enable+the+Gmail+API&hl=ja)にアクセスします。
そこで、好きなApp名を入力し、iOS Bundle IDには先ほど作成したプロジェクトのBundle IDを入力します。

![スクリーンショット 2017-06-03 22.17.54.png](https://qiita-image-store.s3.amazonaws.com/0/132179/824c75e3-e14d-7f5c-92b6-77d784f4ab47.png)

で、↑赤い矢印が指すボタンをクリック。

![スクリーンショット 2017-06-03 22.21.39.png](https://qiita-image-store.s3.amazonaws.com/0/132179/fa7e7262-4b4a-d247-a0f1-ac6379cf13e7.png)

↑赤い矢印が指すボタンをクリック。

![スクリーンショット 2017-06-03 22.21.56.png](https://qiita-image-store.s3.amazonaws.com/0/132179/143a5b33-4595-783b-4295-ea1d600fed4f.png)

↑赤い矢印が指すボタンをクリック。

そうすると"Download GoogleService-Info.plist"と出てきますが、**今回これは使わないので、ダウンロードしなくて大丈夫です。**
それから、その下で`Google/SignIn`をPodfileに書き足すように書いてありますが、**これも使わないので、書き足しません。**

![スクリーンショット 2017-06-03 22.24.01.png](https://qiita-image-store.s3.amazonaws.com/0/132179/a5adb302-e5e1-b566-4b08-b35611dcbd19.png)

↑このページは何もせず"Enable the Gmail API"を押します。

![スクリーンショット 2017-06-03 22.29.40.png](https://qiita-image-store.s3.amazonaws.com/0/132179/ce536386-cec1-3c68-fe89-c69715544b87.png)

↑そうするとこんな画面に飛ぶので、今さっき入力したApp名を選択し、続行を押します。

![スクリーンショット 2017-06-03 22.30.23.png](https://qiita-image-store.s3.amazonaws.com/0/132179/ece6270a-71d5-5d1e-51af-9883c04ac293.png)

↑Gmail APIが有効になりました。認証情報に進むを押します。

![スクリーンショット 2017-06-03 22.31.03.png](https://qiita-image-store.s3.amazonaws.com/0/132179/931ab8cc-2c4c-9218-ac7b-2c986ca16fcb.png)

↑使用するAPIをGmail APIにし、APIを呼び出す場所をiOSにし、ユーザーデータにチェックを入れて次に進みます。

そうすると、**「この目的に適した認証情報が既に存在します」**と言われるので、「あ、はい」と呟いて「完了」を押します。

ごめんなさいこの認証情報の操作いらないですね笑
APIを有効化すると自動で作られるみたいです。

これでGoogle側の設定は終了です！
OAuth 2.0 クライアント IDという欄に**「iOS client for {Bundle ID} (auto created by Google Service) 」**という認証情報ができていればオッケーです！

あとでこのページの情報を使うので、ブラウザのウィンドウは開いたままで進んでください。

## Xcode側の準備
### ライブラリのインストール
ライブラリのインストールにCocoaPodsを使います。
Podfileの中身は以下の感じで。

```Podfile
platform :ios, '8.0'
use_frameworks!
target 'TestApp' do
    pod 'GoogleAPIClientForREST/Gmail', '~> 1.3.0'
    pod 'GTMAppAuth'
end
```

targetのプロジェクト名は自分のプロジェクトに変更してくださいね。
[`GoogleAPIClientForREST`](https://github.com/google/google-api-objectivec-client-for-rest)の2017年6月現在の最新バージョンは`1.3.0`です。

リファレンスと違うのは

```
    pod 'GTMAppAuth'
```

の部分です。

`pod install`して、`TestApp.xcworkspace`を開きます。

ライブラリの中身はObjective-Cですが、pod経由の場合はBridging-Headerなどは不要です。

### URL Typesの追加
ユーザーにOAuth2でこのアプリを承認してもらう手順の中でリダイレクトが行われるので、公式にのっとってURL Typeを追加します。

まず、先ほど開いたままにした[Google APIsの認証情報ページ](https://console.developers.google.com/apis/credentials)から、先ほど作成した認証情報の詳細ページに飛びます（名前をクリック）。

そして、**「iOS の URL スキーム」欄にある値をコピーします。**

Xcodeに戻り、InfoのURL Typesにて+を押し、URL Schemeの欄に値をペーストします。
他の値は空のままでOKです。

# 実装
[`GTMAppAuth`のREADME](https://github.com/google/GTMAppAuth)に書いてある通りのことを、Swiftに置き換えてやっていきます。

## GTMAppAuthが読み込まれない問題
やっと書くぜ！と思ったらXcodeが`GTMAppAuth`を認識してくれない...

[[XCode]追加してインポートしたヘッダーファイルが見つからないときの対処法3つ](http://www.hawk-a.com/exception_code/archives/906)

ということで僕の場合は`Always Search User Paths`を`Yes`にすると読み込むようになりました。


## OAuth認証
やっとここからコード書きます！

### AppDelegate
まずはAppDelegateです。必要な部分だけ抜粋してます。

```swift:AppDelegate.swift
// AppAuthのimportを忘れずに！
import AppAuth
import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    // メンバ変数を宣言します。
    var currentAuthorizationFlow: OIDAuthorizationFlowSession?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        return true
    }

    // 〜〜〜〜〜〜〜〜〜〜〜〜
    // 〜メソッドの記述を省略〜
    // 〜〜〜〜〜〜〜〜〜〜〜〜

    // 以下のメソッドを書き足します。
    // 上で追加したURL Schemeでのリダイレクトを受けた際の処理です
    @available(iOS 9.0, *)
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        // Sends the URL to the current authorization flow (if any) which will
        // process it if it relates to an authorization response.
        if (currentAuthorizationFlow?.resumeAuthorizationFlow(with: url))! {
            currentAuthorizationFlow = nil
            return true
        }
        // Your additional URL handling (if any) goes here.
        return false
    }

}


```

変更点は`import AppAuth`と、`currentAuthorizationFlow`の宣言と、一番下のメソッドの実装の３点です。

### GoogleのOAuth APIを叩く
次に、実際にGoogleのOAuth APIを叩きます。
[Githubの例( GTMAppAuth/Example-iOS/)](https://github.com/google/GTMAppAuth/blob/master/Example-iOS/Source/GTMAppAuthExampleViewController.m)ではViewControllerに書いていますが、僕はViewControllerから処理を切り離すために、GoogleOAuth処理用のクラスを別に作りました。
ので、以降の処理はViewControllerに書いてもいいですし、別の場所に書いてもOKです。

まずは定数と変数宣言から。

```swift
private let scopes        = [kGTLRAuthScopeGmailModify]
private let kClientID     = クライアントID
private let kRedirectURL  = URL.init(string: "リバースクライアントID:/oauthredirect")
private let configuration = GTMAppAuthFetcherAuthorization.configurationForGoogle()
private var authorization: GTMAppAuthFetcherAuthorization?
```

#### scope
スコープというのは、このアプリがユーザーに要求する、**ユーザー情報へのアクセス権限**のことです。
[Choose Auth Scopes  |  Gmail API  |  Google Developers](https://developers.google.com/gmail/api/auth/scopes)から一覧を見ることができます。ユーザーに承認してもらえるよう、必要最小限の権限を選びましょう。

#### kClientID
先ほど上で作った認証情報の「クライアントID」のことです。
Google APIsの認証情報ページから自分のクライアントIDをコピペしましょう。
`123456789-hogehoge.apps.googleusercontent.com`のようなやつです。

#### kRedirectURL
リダイレクト先のURLです。先ほど上でURL Typesに追加したやつです。
[Githubの例( GTMAppAuth/Example-iOS/Source/GTMAppAuthExampleViewController.m
 46, 47行目)](https://github.com/google/GTMAppAuth/blob/master/Example-iOS/Source/GTMAppAuthExampleViewController.m#L46-L47)では、
`com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect`
のようになっているので、ここでも同じようにします。

Google APIsの認証情報ページの「iOSのURLスキーム」欄から値をコピーして、

```
URL.init(string: com.googleusercontent.apps.123456789-hogehoge:/oauthredirect)
```
となるようにペーストしましょう。

#### configuration
OAuth用にサービス（今回はGoogle）へ送るリクエスト(`OIDAuthorizationRequest`)を作る際に必要な設定です。
今回は`GTMAppAuth`がGoogle用の設定を用意してくれるので、`GTMAppAuthFetcherAuthorization.configurationForGoogle()`の戻り値を入れるだけです！

#### authorization
ユーザーが提示されたスコープを承認した際にGoogleから送られてくる認証情報オブジェクトが入ります。

次に、Googleへ送るリクエストを作ります。

```swift
let request = OIDAuthorizationRequest.init(configuration: configuration,
                                           clientId: kClientID,
                                           scopes: self.scopes,
                                           redirectURL: kRedirectURL!,
                                           responseType: OIDResponseTypeCode,
                                           additionalParameters: nil)
```

リクエストを送ります。

```swift
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request,
                                                              presenting: self.delegate as! UIViewController,
                                                              callback: { (authState, error) in
            if (authState != nil) {
                // 認証情報オブジェクトを生成
                self.authorization = GTMAppAuthFetcherAuthorization.init(authState: authState!)
                let accessToken = authState?.lastTokenResponse?.accessToken
                print("Authorization トークンを取得しました：\(String(describing: accessToken))")
        })

```

これでOAuth認証処理のコードは終わりです！
`OIDAuthState.authState()`が実行されると、ユーザーにOAuthで承認を要求する画面が表示されます。
コード自体は短いですね。
この辺はほとんどREADME通りなので、本家が見たければ[google/GTMAppAuth
 - Github](https://github.com/google/GTMAppAuth)を見てください！


## Gmail API叩く
やっと本命のAPIを叩くコードです。٩( ᐛ )و

で、GTMAppAuthのREADMEでは`GTMSessionFetcherService`というクラスを使っているのですが、**これに関しては[iOS Quickstart](https://developers.google.com/gmail/api/quickstart/ios?ver=swift&hl=ja)で使われている`GTLRService`の方が便利です。**
というのも、**クエリの組み立てがこちらの方が分かりやすい**し、**レスポンスのJSONパースまで内部でやってくれて、専用のオブジェクトで返してくれる**からです。

これまた、API専用のクラスを自分で作った方が使い勝手が良いと思います。
エッセンスだけ書いておきます！

例として[users.messages.list](https://developers.google.com/gmail/api/v1/reference/users/messages/list?hl=ja)を実行します。

```swift
import GoogleAPIClientForREST

let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)
let service = GTLRGmailService()
service.authorizer = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: "NAME")
service.executeQuery( query,
                      delegate: self,
                      didFinish: #selector(callback) )

```

### query
APIリクエストのクエリです。
いや、クエリですとか言って実際はリクエストのパスですね。
`GTLRQuery`クラスのサブクラスである`GTLRGmailQuery_HogeHoge`のqueryメソッドを使うと簡単に生成してくれます。
[API Reference  |  Gmail API  |  Google Developers](https://developers.google.com/gmail/api/v1/reference/?hl=ja)を見ながら同じような単語を打ち込めばXcodeが補完してくれます！

ちなみにここでいう`userID`はメアドのことです。

### service
実際にリクエストを送ってくれるオブジェクトです。
Gmailの場合は`GTLRGmailService`クラスのインスタンスです！

### service.authorizer
認証情報オブジェクト（`GTMAppAuthFetcherAuthorization`）です。service.authorizerに正しい認証情報オブジェクトを入れることで、リクエストヘッダに自動的にアクセストークンを挿入してくれます。

ここでは、iOSのキーチェーンから、過去に保存した認証情報を取り出して使用しています。
認証情報の保存について後述します。

### service.executeQuery
クエリ実行！
コールバックをセレクタで指定します。


### コールバック

```swift
func callback( ticket : GTLRServiceTicket,
                         finishedWithObject response : GTLRGmail_ListMessagesResponse,
                         error : NSError? ){
        if let error = error {
            print("メッセージリストの取得に失敗しました。")
            print(error)
            return
        }
        print("メッセージリストの取得に成功しました。")
        print(response.messages!)
        print(response.nextPageToken!)
        print(response.resultSizeEstimate!)
        
    }
```

#### finishedWithObject
叩くAPIによってレスポンスの種類が違うので、`finishedWithObject`の型もXcodeの補完に頼ります。
なんか網羅的に一覧にしてるのとかないの...


ま、というわけで、これでAPIを叩くことができた！！
**わーい！！たーのしー！！今日はいいAPI日和だねー！！**

## 認証情報の保存
で、次に気になるのが認証情報の保存についてです。

一度OAuthで認証してもらったら、あとは明示的な許可を必要としないでアプリを使ってほしい。そのためには、**最初に得た認証情報をアプリに保存しておきたい**わけです。

### GTMAppAuthFetcherAuthorization
`GTMAppAuthFetcherAuthorization`はOAuth認証のところで使用した認証情報のクラスですが、これがキーチェーンの保存までお世話してくれます！

```swift
// Serialize to Keychain
GTMAppAuthFetcherAuthorization.save(_authorization, toKeychainForName: kGTMAppAuthExampleAuthorizerKey)

// Deserialize from Keychain
let authorization = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: kGTMAppAuthExampleAuthorizerKey)

// Remove from Keychain
GTMAppAuthFetcherAuthorization.removeFromKeychain(forName: kGTMAppAuthExampleAuthorizerKey)
```

簡単。
保存するときに指定した`toKeychainForName:`さえ保存しておけば、いつでも復元可能です。
`Google/SignIn(GIDSignIn)`にはこういう機能が見当たらなかった...

認証情報を任意のキーで保存できるということは、アカウントごとに認証情報を保存するキーを保存しておけば、**複数アカウントでのAPIコール**が実現できるわけです！やったー！

複数にするときは、appDelegateに宣言した変数とかは場所を変えた方が良いかもですね。

## おまけ：ユーザー情報を取得したい

`GIDSignIn`を使うとサインインした段階で、そのユーザーの情報が返ってくるのですが、`GTMAppAuth`の場合はそういうのがなく...

2016年2月にGoogle People APIというのが[発表されていた](https://developers-jp.googleblog.com/2016/02/people-api.html)ので、API練習も兼ねて使ってみました。

長くなりそうなので記事分けます。

[【iOS】Google People APIでユーザーの情報を取得する](http://qiita.com/ryokkkke/items/4c3da87b50d7a298e604)

# まとめ
- GoogleのAPIを使うときにOAuthするためのライブラリには、`GoogleSignIn`と`GTMAppAuth`がある。
- 「Googleでログイン」したいのではなく、OAuthでアクセストークンが欲しいだけの場合は`GTMAppAuth`を使う。