Google Sign-In for iOS

以前から運用しているサーバGAE+クライアントiOSなアプリがあり、クライアントサーバ間の認証にはGAEの[OAuth1 Provider](https://cloud.google.com/appengine/docs/java/oauth/)を使っている。しかし今OAuth1はないだろう、という事でクライアントサーバ間の認証を[Google Sign-In for iOS](https://developers.google.com/identity/sign-in/ios/)に置き換えることにした。

## 前提

Xcodeプロジェクトはすでに存在していて、cocoapodsでライブラリ管理をし、言語はSwift。

## クライアント側の設定ファイルの生成

iOS Bundle IDとGoogle Developer Console ProjectのCredentiaのペアが記述された plist ファイルが必要になるので、それを生成する。

1. 公式リファレンスの [Start integrating Google Sign-In into your iOS app](https://developers.google.com/identity/sign-in/ios/start-integrating/) にある **GET A CONFIGURATION FILE** ボタンをクリックする

![Screen Shot 2015-07-30 at 01.21.26.png](https://qiita-image-store.s3.amazonaws.com/0/1387/0464959c-eb66-06d1-363f-81749a8f40d6.png)
2. 次のようなポップアップが表示されるので、App nameにはDeveloper Console上の既存のプロジェクトを選択するか、新規プロジェクト名を入力する。なお既存プロジェクトにすでにiOS向けのCredentialsの設定がある場合は、Bundle IDも選択可能。
![Screen Shot 2015-07-30 at 01.17.13.png](https://qiita-image-store.s3.amazonaws.com/0/1387/9da9ca56-6ab6-38ec-2cad-bca1d2660d39.png)
3. すべて入力したあと、 **Generate configuratino files** で次へ進めて **Download GoogleService-Info.plist**　ボタンで`GoogleService-Info.plist`ファイルがダウンロードできる。

## Xcodeプロジェクトの設定

0. Podfileに`pod 'Google/SignIn'`を追加して`pod install`を実行し、Xcodeプロジェクトを開き直す。
0. Xcode のプロジェクトにダウンロードした`GoogleService-Info.plist`を追加する
0. プロジェクトの設定の **Info** タブの **URL Types** に以下の２つを追加する（**URL Schemes**に値を設定し、**Identifier**は入力しなくてOK）
  * `GoogleService-Info.plist`に含まれている REVERSED_CLIENT_ID
  * Bundle ID
0. **~-Bridging-Header.h** に`#import <Google/SignIn.h>`を追加する

## 実装

UIApplicationDelegateの実装を`AppDelegate`、サインインを起動する画面を`MyViewController`とした場合。基本的にはすべて`GIDSignIn`を通じて操作する。

### SignIn関連のDelegateの実装

```swift:SignInDelegate.swift
// MARK: GIDSignInDelegate
extension AppDelegate: GIDSignInDelegate {
  func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!, withError error: NSError!) {
    if (error == nil) {
      let idToken = user.authentication.idToken // サーバへ送信するトークン
      let name = user.profile.name
      let email = user.profile.email
      let refreshToken = user.authentication.refreshToken
      // ..他にもいろいろ取得できる
    } else {
      println("singIn:didSignInForUser: \(error.localizedDescription)")
    }
  }
}

// MARK: GIDSignInUIDelegate
extension MyViewController: GIDSignInUIDelegate {    
  func signInWillDispatch(signIn: GIDSignIn!, error: NSError!) {
    // サインインウインドウの表示前処理
  }

  func signIn(signIn: GIDSignIn!, presentViewController viewController: UIViewController!) {
    // サインインウインドウを表示する処理
    self.presentViewController(viewController, animated: true, completion: nil)
  }

  func signIn(signIn: GIDSignIn!, dismissViewController viewController: UIViewController!) {
    // サインウインドウが閉じたあとの処理
  }
}
```

### 起動時の処理

```swift:AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.

    // GIDSignIn
    var configureError: NSError?
    GGLContext.sharedInstance().configureWithError(&configureError)
    assert(configureError == nil, "Error configuring Google services: \(configureError)")
    GIDSignIn.sharedInstance().delegate = self

    return true
  }

  func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool {
    // GIDSignIn
    return GIDSignIn.sharedInstance().handleURL(url, sourceApplication: sourceApplication, annotation: annotation)
  }
}
```

### サインインウインドウを起動する処理

```swift:MyViewController.swift
@IBAction func signInButtonClicked(sender: AnyObject) {
  let signIn = GIDSignIn.sharedInstance()
  signIn.uiDelegate = self
  signIn.signIn()
}
```

上記のように実装しても良いし、`GIDSignButton`というUIクラスが含まれているので、Viewを貼り付けてCustom Classで`GIDSignInButton`に変更すると、実行時にGoogleぽいボタンが表示されて、クリックするとサインインウインドウが起動する機能もある。

### サインインの状態の確認

自前でサインインの状態を保存しなくても、`signInSilently()`（いわゆるWebの場合のログインURL生成時のログイン画面の`auto`モード）を使って次のように実装することも可能。`signInSilently()`が成功したら、GIDSignInDelegateの実装が呼ばれ、先の実装にある`GIDGoogleUser`が受け取れ、idToken等を取得できる。

```swift:MyViewController.swift
func signInIfNeeded() {
  let signIn = GIDSignIn.sharedInstance()
  if signIn.hasAuthInKeychain() {
    let user = signIn.currentUser
    if user == nil {
      signIn.uiDelegate = self
      signIn.signInSilently()
    }
  } else {
    signIn.uiDelegate = self
    signIn.signIn()
  }
}
```

## サーバ側の実装

クライアント側で取得したidTokenを次のようにパースできる（**com.google.api-client:google-api-client:1.20.0**を利用）。


```java:MyServlet.java
GoogleIdTokenVerifier verifier = 
  new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
    .setAudience(Arrays.asList(CLIENT_ID)).build();
GoogleIdToken idToken = verifier.verify(IDTOKENSTRING);
Payload payload = idToken.getPayload();
// payloadから各種情報を取り出せる
```