[iOS] Googleログインを追加する

本記事では、iOSアプリにGoogleログインを追加する実装を紹介します。
2020.01.23

はじめに

こんにちは。CX事業本部の平屋です。

本記事では、iOSアプリにGoogleログインを追加する実装を紹介します。

扱う内容は以下の通りです。

  • OAuth クライアント IDの作成
  • 初期設定
  • ログインの実装
  • 動作確認

検証環境

  • macOS Mojave 10.15.2
  • Xcode Version 10.3

OAuth クライアント IDの作成

Google Developer Consoleを開きます。

プロジェクト未作成の場合は、ページ左上の[プロジェクトの選択]をクリックし、表示されるダイアログの右上の[新しいプロジェクト]をクリックしてプロジェクトを作成します。

プロジェクトページ左側のメニュー内の[認証情報]をクリックします。

[認証情報を作成] > [OAuth クライアント ID]をクリックします。

同意画面の設定

「OAuth クライアント IDを作成するには、まず同意画面でサービス名を設定する必要があります」という表示が出た場合は、[同意画面を設定]をクリックし、設定を行います。表示が出なかった場合は、「IDの作成」まで読み飛ばしてください。

[User Type]を選択するページが表示されます。[User Type]を選択し、[作成]をクリックします。ここでは[外部]を選択しました。

同意画面の詳細を設定するページが表示されます。各項目の入力/選択を行い、[保存]をクリックします。

ここでは[アプリケーション名]の入力だけ行いました。各値はあとからでも変更できるようです。

同意画面の設定が終わりました。[認証情報]をクリックし、IDの作成へ戻りましょう。

再度、[認証情報を作成] > [OAuth クライアント ID]をクリックします。

IDの作成

OAuth クライアント IDを作成するページが表示されます。

[アプリケーションの種類]は[iOS]を選択します。

[名前]と[バンドル ID]に値を入力します。[名前]は開発者向けの名前でありエンドユーザーには表示されません。[バンドル ID]はXcodeで設定しているIDを入力します。[App Store ID]と[チームID]の入力は任意です。

入力できたら[作成]をクリックします。

ダイアログの内容を一読し、[OK]をクリックします。

plistファイルのダウンロード

IDの作成が完了しました。右端のダウンロードボタンをクリックしてplistファイルをダウンロードします。

ダウンロードしたplistファイルは以下のような内容になっています。

CLIENT_IDおよびREVERSED_CLIENT_IDは次の「初期設定」で使用します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CLIENT_ID</key>
    <string>XXXXXX.apps.googleusercontent.com</string>
    <key>REVERSED_CLIENT_ID</key>
    <string>com.googleusercontent.apps.XXXXXX</string>
    <key>PLIST_VERSION</key>
    <string>1</string>
    <key>BUNDLE_ID</key>
    <string>com.example.GoogleSignInSample</string>
</dict>
</plist>

初期設定

Google Sign-In SDKの追加

まず、Google Sign-In SDKをアプリに追加しましょう。

インストール方法はいくつかありますが、CocoaPodsを使用してインストールを行うのが最も簡単です。

ここでは、CocoaPodsを使用してインストールします。Podfileに以下のpodを記述し、$ pod installを実行します。

pod 'GoogleSignIn'

インストールが完了したら、*.xcworkspaceファイルを開きます。

URL schemeの設定

[プロジェクトファイル] > [{ターゲット}] > [Info]を選択します。

[URL Types]左の三角アイコンをクリックして展開し、[+]をクリックします。

[URL Schemes]に、「plistファイルのダウンロード」でダウンロードしたplist内のREVERSED_CLIENT_IDの値を入力します。

Client IDの設定

アプリ起動時などのタイミングでClient IDを設定します。GIDSignIn.sharedInstance()clientIDに、「plistファイルのダウンロード」でダウンロードしたplist内のCLIENT_IDの値を設定します。

以下の例ではAppDelegatefunc application(_:didFinishLaunchingWithOptions:)内でClient IDを設定しています。

import UIKit
import GoogleSignIn

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Client IDを設定する
        GIDSignIn.sharedInstance()?.clientID = "XXXXXX.apps.googleusercontent.com"
        return true
    }
}

ログインの実装

ログイン処理完了のハンドリング

ログイン処理が終わると「URL schemeの設定」で設定したURL schemeを使用してアプリが開かれます。このイベントをGoogle Sign-In SDK側に伝達する処理をAppDelegateapplication(_:open:options:)内に追加します。

// ...

class AppDelegate: UIResponder, UIApplicationDelegate {
    // ...

    // 追加する
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        // GIDSignInのhandle()を呼び、返り値がtrueであればtrueを返す
        if GIDSignIn.sharedInstance()!.handle(url) {
            return true
        }
        return false
    }
}

ログイン

ログイン画面の表示元の設定と、ログイン処理の実行を追加します。

ログイン画面の表示元の設定を行うにはGIDSignInpresentingViewControllerにビューコントローラーを指定します。また、ログイン処理はsignIn()を呼ぶと実行できます。

以下の例では、画面に配置したボタンをタップしたタイミングでログイン処理を実行します。

import UIKit
import GoogleSignIn

class ViewController: UIViewController {
    @IBAction func logInButtonDidTap(_ sender: Any) {
        // ログイン画面の表示元を設定
        GIDSignIn.sharedInstance()?.presentingViewController = self

        // ログインを実行
        GIDSignIn.sharedInstance()?.signIn()
    }
}

ログイン処理結果のハンドリング

このままではログイン処理の結果をハンドリングすることができません。

ハンドリングできるようにするには、まず、GIDSignInDelegateへの適合とメソッドの追加を行います。

GIDSignInDelegateに適合する場合に実装が必須なのはsign(_:didSignInFor:withError)のみです。リファレンスによると、このメソッドのerror引数は、ログインが成功した場合にnilになるようです。以下の例では、ログインが成功したらユーザーのemailをログ出力し、失敗したらエラーの概要をログ出力しています。

class ViewController: UIViewController {
    // ...
}

// GIDSignInDelegateへの適合とメソッドの追加を行う
extension ViewController: GIDSignInDelegate {
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        if error == nil {
            // ログイン成功した場合
            print("signIned user email: \(user!.profile!.email!)")
        } else {
            // ログイン失敗した場合
            print("error: \(error!.localizedDescription)")
        }
    }
}

そして、デリゲートの設定を行います。デリゲートの設定はGIDSignInsignIn()を呼ぶ前に設定します。GIDSignIndelegateGIDSignInDelegateに適合したオブジェクトを設定します。

class ViewController: UIViewController {
    @IBAction func logInButtonDidTap(_ sender: Any) {
        // デリゲートを設定
        GIDSignIn.sharedInstance()?.delegate = self

        // ...
    }
}

// ...

認証情報の他のGoogle APIでの利用

ログイン成功時にsign(_:didSignInFor:withError)内で取得できるGIDGoogleUserauthenticationプロパティ(GIDAuthentication型)にはfetcherAuthorizer()メソッドがあります。このメソッドはGTMFetcherAuthorizationProtocolに適合したオブジェクトを返します。このオブジェクトを他のGoogle API向けのサービスクラスに設定すると、Authenticationヘッダ付きでリクエストを実行できるようになります。

以下の例では、Google Drive用のGTLRDriveServiceにauthorizerを設定しています。これでログインしているユーザーのドライブに対する操作を行えるようになります。

import GTMSessionFetcher
import GoogleAPIClientForREST

// ...

extension ViewController: GIDSignInDelegate {
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        if error == nil {
            // ...

            let service = GTLRDriveService()
            service.authorizer = user.authentication.fetcherAuthorizer()
        } else {
            // ...
        }
    }
}

保存された認証情報を復元する

現状の実装だと、ログイン成功しても、アプリが次回起動時したタイミングではログインしているユーザーの情報は取得できません。

override func viewDidLoad() {
    super.viewDidLoad()

    if let user = GIDSignIn.sharedInstance()?.currentUser {
        print("currentUser.profile.email: \(user.profile!.email!)")
    } else {
        // 次回起動時にはこちらのログが出力される
        print("currentUser is nil")
    }
}

Google Sign-In SDKには認証情報の復元用のメソッドが用意されており、必要なタイミングでこのメソッドを呼べば認証情報を復元できます。

以下の例では、hasPreviousSignIn()で以前のログイン情報が残っていないかを確認し、残っていたらrestorePreviousSignIn()で復元しています。

// ...

class ViewController: UIViewController {
    // ...

    @IBAction func logInButtonDidTap(_ sender: Any) {
        // ...

        if GIDSignIn.sharedInstance()!.hasPreviousSignIn() {
            // 以前のログイン情報が残っていたら復元する
            GIDSignIn.sharedInstance()!.restorePreviousSignIn()
        } else {
            // 通常のログインを実行
            GIDSignIn.sharedInstance()?.signIn()
        }
    }
}

復元に成功すると、通常のログインの場合と同様にsign(_:didSignInFor:withError)が呼ばれ、このメソッド内でユーザー情報や認証情報を取得できます。

extension ViewController: GIDSignInDelegate {
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        // 認証情報を復元した場合も本メソッドが呼ばれる
    }
}

動作確認

サンプルアプリを起動すると、ボタンが表示されます。

ボタンをタップすると、ログインフローに進んでよいか尋ねる確認アラートが表示されます。

[Continue]をタップすると、SafariViewControllerが立ち上がり、ログインページが表示されます。

emailを入力して[次へ]をタップすると、パスワード入力ページが表示されます。

パスワードを入力して[次へ]をタップし、ログインが成功するとログが出力されます。

さいごに

本記事では、iOSアプリにGoogleログインを追加する実装を紹介しました。

次の記事ではログインの結果得られた認証情報を他のGoogleのAPIで使用する例を扱う予定です。

参考資料