iOS アプリに Google でログインを追加する

1. 始める前に

この Codelab では、Google でログインを実装し、シミュレーターで実行する iOS アプリケーションを構築する手順を説明します。SwiftUI と UIKit の両方を使用する実装が提供されています。

SwiftUI は、新しいアプリ開発のための Apple の最新の UI フレームワークです。単一の共有コードベースからすべての Apple プラットフォームのユーザー インターフェースを構築できます。iOS 13 以上のバージョンが必要です。

UIKit は、iOS 向けの Apple のオリジナルの基盤となる UI フレームワークです。古い iOS バージョンとの下位互換性を提供します。そのため、さまざまな古いデバイスをサポートする必要がある既存のアプリに適しています。

開発のニーズに最も適したフレームワークのパスに沿って進むことができます。

前提条件

学習内容

  • Google Cloud プロジェクトを作成する方法
  • Google Cloud コンソールで OAuth クライアントを作成する方法
  • iOS アプリに「Google でログイン」を実装する方法
  • [Google でログイン] ボタンをカスタマイズする方法
  • ID トークンをデコードする方法
  • iOS アプリで App Check を有効にする方法

必要なもの

  • 最新バージョンの Xcode
  • インストールした Xcode のバージョンのシステム要件を満たす macOS を搭載したパソコン

この Codelab は、iOS 18.3 シミュレータで Xcode 16.3 を使用して作成されました。開発には Xcode の最新バージョンを使用する必要があります。

2. 新しい Xcode プロジェクトを作成する

  1. Xcode を開いて、[Create a new Xcode project] を選択します。
  2. [iOS] タブを選択し、[App] テンプレートを選択して、[Next] をクリックします。

Xcode プロジェクト作成テンプレート ページ

  1. プロジェクト オプション:
    • サービス名を入力します。
    • 必要に応じて [チーム] を選択します。
    • 組織 ID を入力します。
    • 生成されたバンドル ID をメモします。書き留めておきます。
    • [インターフェース] で、次のいずれかを選択します。
      • SwiftUI ベースのアプリの場合は SwiftUI
      • UIKit ベースのアプリのストーリーボード
    • [言語] で [Swift] を選択します。
    • [次へ] をクリックして、プロジェクトの保存場所を選択します。

Xcode プロジェクトのオプション ページ

3. OAuth クライアントを作成する

アプリが Google の認証サービスと通信できるようにするには、OAuth クライアント ID を作成する必要があります。これには Google Cloud プロジェクトが必要です。次の手順では、プロジェクトと OAuth クライアント ID を作成する手順について説明します。

Google Cloud プロジェクトを選択または作成する

  1. Google Cloud コンソールに移動し、プロジェクトを選択または作成します。既存のプロジェクトを選択すると、コンソールは自動的に次の必要な手順に移動します。

Google Cloud コンソールのプロジェクト セレクタ ページ

  1. 新しい Google Cloud プロジェクトの名前を入力します。
  2. [作成] を選択します。

Google Cloud コンソールのプロジェクト セレクタ ページ

選択したプロジェクトの同意画面をすでに構成している場合は、この時点で構成を求められることはありません。その場合は、このセクションをスキップして、OAuth 2.0 クライアントを作成するに進んでください。

  1. [同意画面を構成] を選択します。

Google Cloud コンソールの OAuth クライアント作成ページで、同意画面の構成が必要であることを示すメッセージが表示されている

  1. ブランディング ページで [開始] を選択します。

Google Cloud コンソールのブランディングのスタートガイド ページ

  1. プロジェクト構成ページで、次のフィールドに入力します。
    • アプリ情報: アプリの名前とユーザー サポートのメールアドレスを入力します。このサポートのメールアドレスは一般公開され、ユーザーが同意について問い合わせる際に使用されます。
    • Audience: [External] を選択します。
    • 連絡先情報: Google がプロジェクトについて連絡する際に使用するメールアドレスを入力します。
    • Google API サービス: ユーザーデータに関するポリシーを確認します。
    • [作成] をクリックします。

Google Cloud コンソールのクライアント ブランディング構成ページ

  1. ナビゲーション メニューで [クライアント] ページを選択します。
  2. [Create Client] をクリックします。

Google Cloud プロジェクトのクライアント ページ

OAuth 2.0 クライアントを作成する

  1. [アプリケーションの種類] で [iOS] を選択します。
  2. クライアントの名前を入力します。
  3. 前の手順で作成したバンドル ID を入力します。
  4. Apple によってチームに割り当てられたチーム ID を入力します。この手順は現時点では省略可能ですが、この Codelab の後半で App Check を有効にするには、チーム ID が必要になります。
  5. [作成] を選択します。

OAuth クライアントの詳細入力ページ

  1. ダイアログ ウィンドウからクライアント ID をコピーします。これは後で必要になります。
  2. 後で参照できるように、plist ファイルをダウンロードします。

OAuth クライアント ID が作成されたことを示すダイアログ

4. Xcode プロジェクトを構成する

次のステップでは、Google でログイン SDK を使用するように Xcode プロジェクトを設定します。このプロセスでは、SDK を依存関係としてプロジェクトに追加し、一意のクライアント ID を使用してプロジェクト設定を構成します。この ID により、SDK はログイン プロセス中に Google の認証サービスと安全に通信できます。

「Google でログイン」の依存関係をインストールする

  1. Xcode プロジェクトを開きます。
  2. [File] > [Add Package Dependencies] に移動します。
  3. 検索バーに、Google でログイン リポジトリの URL(https://github.com/google/GoogleSignIn-iOS)を入力します。

Swift Package Manager で Google でログインの依存関係を見つける

  1. [パッケージを追加] を選択します。
  2. GoogleSignIn パッケージのメイン アプリケーション ターゲットを選択します。
  3. SwiftUI を使用している場合は、GoogleSignInSwift パッケージのメイン アプリケーション ターゲットを選択します。UIKit を使用する場合は、このパッケージのターゲットを選択しないでください。
  4. [パッケージを追加] を選択します。

プロジェクトに Google ログインの依存関係を追加する

アプリの認証情報を構成する

  1. Project Navigator で、プロジェクトのルートをクリックします。
  2. メイン エディタ領域で、[TARGETS] リストからメイン アプリケーション ターゲットを選択します。
  3. エディタ領域の上部にある [情報] タブを選択します。
  4. [Custom iOS Target Properties] セクションの最後の行にカーソルを合わせ、表示される [+] ボタンをクリックします。

iOS ターゲット プロパティに新しいターゲット キーを追加

  1. [Key] 列に「GIDClientID」と入力します。
  2. [] 列に、Google Cloud コンソールからコピーしたクライアント ID を貼り付けます。

メインアプリのターゲットに GIDClientID を追加する

  1. Google Cloud コンソールからダウンロードした plist ファイルを開きます。
  2. [Reversed Client ID] の値をコピーします。

Google Cloud Console plist ファイル

  1. [情報] タブの下部にある [URL タイプ] を展開します。
  2. [+] ボタンを選択します。
  3. [URL スキーム] ボックスに反転クライアント ID を入力します。

メイン アプリケーション ターゲットに URLSchemes キーを追加

これで、アプリにログインボタンを追加する準備が整いました。

5. ログインボタンを追加する

Xcode プロジェクトが構成されたら、アプリに [Google でログイン] ボタンを追加します。

このステップのコアロジックは、GIDSignIn.sharedInstance.signIn の呼び出しです。このメソッドは認証プロセスを開始し、Sign in with Google SDK に制御を渡して、ユーザーに Sign in with Google フローを表示します。

SwiftUI

  1. Xcode のプロジェクト ナビゲータで ContentView.swift ファイルを見つけます。
  2. このファイルの内容を次のテキストに置き換えます。
import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack {
      GoogleSignInButton(action: handleSignInButton).padding()
    }
  }

  func handleSignInButton() {
    // Find the current window scene.
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
      print("There is no active window scene")
      return
    }

    // Get the root view controller from the window scene.
    guard
      let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
        .rootViewController
    else {
      print("There is no key window or root view controller")
      return
    }

    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(
      withPresenting: rootViewController
    ) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }
      // If sign in succeeded, display the app's main content View.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }
}

#Preview {
  ContentView()
}

iOS シミュレータの SwiftUI フレームワークの [Google でログイン] ボタン

UIKit

  1. Xcode のプロジェクト ナビゲータで ViewController.swift ファイルを見つけます。
  2. このファイルの内容を次のテキストに置き換えます。
import GoogleSignIn
import UIKit

class ViewController: UIViewController {

  // Create an instance of the Sign in with Google button
  let signInButton = GIDSignInButton()

  override func viewDidLoad() {
    super.viewDidLoad()

    // Add the sign-in button to your view
    view.addSubview(signInButton)

    // Position the button using constraints
    signInButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    ])

    // Add a target to the button to call a method when it's pressed
    signInButton.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside)
  }

  // This method is called when the sign-in button is pressed.
  @objc func signInButtonTapped() {
    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      // If sign in succeeded, print the ID token.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }
}

iOS シミュレータの UIKit フレームワークの [Google でログイン] ボタン

ログインボタンを表示する

シミュレータでアプリを起動します。[Google でログイン] ボタンが表示されますが、まだ正しく機能しません。これは想定どおりの動作です。ユーザーが認証された後にアプリにリダイレクトする処理を行うコードを実装する必要があります。

6. ログインボタンをカスタマイズする

デフォルトの [Google でログイン] ボタンをカスタマイズして、アプリのテーマに合わせることができます。Google ログイン SDK を使用すると、ボタンの配色とスタイルを変更できます。

SwiftUI

デフォルトのボタンは、次のコード行でページに追加されます。

GoogleSignInButton(action: handleSignInButton)

GoogleSignInButton は、イニシャライザにパラメータを渡すことでカスタマイズされます。次のコードを使用すると、ログインボタンがダークモードで表示されます。

  1. ContentView.swift を開きます。
  2. GoogleSignInButton のイニシャライザを更新して、次の値を含めます。
GoogleSignInButton(
  scheme: .dark,  // Options: .light, .dark, .auto
  style: .standard,  // Options: .standard, .wide, .icon
  state: .normal,  // Options: .normal, .disabled
  action: handleSignInButton
).padding()

iOS シミュレータの SwiftUI フレームワークのダークモードの [Google でログイン] ボタン

カスタマイズ オプションの詳細については、GoogleSignInSwift Framework Reference をご覧ください。

UIKit

デフォルトのボタンは、次のコード行で作成されます。

// Create an instance of the Sign in with Google button
let signInButton = GIDSignInButton()

// Add the button to your view
view.addSubview(signInButton)

GIDSignInButton は、ボタン インスタンスのプロパティを設定することでカスタマイズされます。次のコードを使用すると、ログインボタンがダークモードで表示されます。

  1. ViewController.swift を開きます。
  2. viewDidLoad 関数でビューにログインボタンを追加する直前に、次のコード行を追加します。
// Set the width and color of the sign-in button
signInButton.style = .standard  // Options: .standard, .wide, .iconOnly
signInButton.colorScheme = .dark  // Options: .dark, .light

iOS シミュレータの UIKit フレームワークのダークモードの [Google でログイン] ボタン

カスタマイズの詳細については、GoogleSignIn Framework Reference をご覧ください。

7. 認証リダイレクト URL を処理する

ログインボタンを追加したら、次のステップはユーザーが認証された後に発生するリダイレクトを処理することです。認証後、Google は一時的な認証コードを含む URL を返します。ログイン プロセスを完了するために、ハンドラはこの URL をインターセプトし、Google でログイン SDK に渡して、署名付き ID トークン(JWT)と交換します。

SwiftUI

  1. App 構造体を含むファイルを開きます。このファイルはプロジェクトに基づいて命名されるため、YourProjectNameApp.swift のような名前になります。
  2. このファイルの内容を次のテキストに置き換えます。
import GoogleSignIn
import SwiftUI

@main
struct iOS_Sign_in_with_Google_App: App {
  var body: some Scene {
    WindowGroup {
      ContentView()

        .onOpenURL { url in
          GIDSignIn.sharedInstance.handle(url)
        }
    }
  }
}

UIKit

  1. AppDelegate.swift を開きます。
  2. ファイルの先頭に次のインポートを追加します。
import GoogleSignIn
  1. AppDelegate クラス内に次の認証ハンドラ関数を追加します。このコードを配置するのに適した場所は、application(_:didFinishLaunchingWithOptions:) メソッドの閉じ中かっこ直後です。
func application(
  _ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  var handled: Bool

  handled = GIDSignIn.sharedInstance.handle(url)
  if handled {
    return true
  }
  // If not handled by this app, return false.
  return false
}

これらの変更を行うと、AppDelegate.swift ファイルは次のようになります。

import GoogleSignIn
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // Override point for customization after application launch.
    return true
  }

  func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    var handled: Bool

    handled = GIDSignIn.sharedInstance.handle(url)
    if handled {
      return true
    }
    // If not handled by this app, return false.
    return false
  }

  // MARK: UISceneSession Lifecycle

  func application(
    _ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions
  ) -> UISceneConfiguration {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return UISceneConfiguration(
      name: "Default Configuration",
      sessionRole: connectingSceneSession.role
    )
  }

  func application(
    _ application: UIApplication,
    didDiscardSceneSessions sceneSessions: Set<UISceneSession>
  ) {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
  }
}

ログインフローをテストする

これで、完全なログインフローをテストできるようになりました。

アプリを実行して、ログインボタンをタップします。認証が完了すると、同意画面が表示され、アプリが情報にアクセスするための権限を付与できます。承認すると、ログインが完了し、アプリに戻ります。

ログインフローが正常に完了すると、Google でログイン SDK はユーザーの認証情報をデバイスのキーチェーンに安全に保存します。これらの認証情報は、後でユーザーがアプリを起動したときにログイン状態を維持するために使用できます。

8. ログアウト ボタンを追加する

ログインが機能するようになったので、次はログアウト ボタンを追加し、ユーザーの現在のログイン状態を反映するように UI を更新します。ログインが成功すると、SDK は GIDGoogleUser オブジェクトを提供します。このオブジェクトには、UI のカスタマイズに使用するユーザーの名前やメールアドレスなどの基本情報を含む profile プロパティが含まれています。

SwiftUI

  1. ContentView.swift ファイルを開きます。
  2. ContentView 構造体の先頭に状態変数を追加します。この変数は、ユーザーがログインした後にユーザーの情報を保持します。@State 変数であるため、値が変更されるたびに SwiftUI が UI を自動的に更新します。
struct ContentView: View {
  @State private var user: GIDGoogleUser?
}
  1. ContentView 構造体の現在の body を次の VStack に置き換えます。これにより、user 状態変数にユーザーが含まれているかどうかが確認されます。ログインしている場合は、ウェルカム メッセージとログアウト ボタンが表示されます。そうでない場合は、元の [Google でログイン] ボタンが表示されます。
var body: some View {
  VStack {
    // Check if the user is signed in.
    if let user = user {
      // If signed in, show a welcome message and the sign-out button.
      Text("Hello, \(user.profile?.givenName ?? "User")!")
        .font(.title)
        .padding()

      Button("Sign Out", action: signOut)
        .buttonStyle(.borderedProminent)

    } else {
      // If not signed in, show the "Sign in with Google" button.
      GoogleSignInButton(
        scheme: .dark,  // Options: .light, .dark, .auto
        style: .standard,  // Options: .standard, .wide, .icon
        state: .normal,  // Options: .normal, .disabled
        action: handleSignInButton
      ).padding()
    }
  }
}
  1. handleSignInButton 完了ブロックを更新して、新しい user 変数に signInResult.user を割り当てます。これにより、UI がログイン ビューに切り替わります。
func handleSignInButton() {
  // Find the current window scene.
  guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
    print("There is no active window scene")
    return
  }

  // Get the root view controller from the window scene.
  guard
    let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
      .rootViewController
  else {
    print("There is no key window or root view controller")
    return
  }

  // Start the sign-in process.
  GIDSignIn.sharedInstance.signIn(
    withPresenting: rootViewController
  ) { signInResult, error in
    guard let result = signInResult else {
      // Inspect error
      print("Error signing in: \(error?.localizedDescription ?? "No error description")")
      return
    }

    DispatchQueue.main.async {
      self.user = result.user
    }

    // If sign in succeeded, display the app's main content View.
    print("ID Token: \(result.user.idToken?.tokenString ?? "")")
  }
}
  1. ログアウト ボタンから呼び出される signOut 関数を ContentView 構造体の末尾に追加します。
func signOut() {
  GIDSignIn.sharedInstance.signOut()
  // After signing out, set the `user` state variable to `nil`.
  self.user = nil
}

アプリを起動してログインします。認証に成功すると、UI が変更されます。

iOS シミュレータでの SwiftUI フレームワークのログイン状態

これらの変更を行うと、ContentView.swift ファイルは次のようになります。

import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

struct ContentView: View {

  @State private var user: GIDGoogleUser?

  var body: some View {
    VStack {
      // Check if the user is signed in.
      if let user = user {
        // If signed in, show a welcome message and the sign-out button.
        Text("Hello, \(user.profile?.givenName ?? "User")!")
          .font(.title)
          .padding()

        Button("Sign Out", action: signOut)
          .buttonStyle(.borderedProminent)

      } else {
        // If not signed in, show the "Sign in with Google" button.
        GoogleSignInButton(
          scheme: .dark,  // Options: .light, .dark, .auto
          style: .standard,  // Options: .standard, .wide, .icon
          state: .normal,  // Options: .normal, .disabled
          action: handleSignInButton
        ).padding()
      }
    }
  }

  func handleSignInButton() {
    // Find the current window scene.
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
      print("There is no active window scene")
      return
    }

    // Get the root view controller from the window scene.
    guard
      let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
        .rootViewController
    else {
      print("There is no key window or root view controller")
      return
    }

    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(
      withPresenting: rootViewController
    ) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      DispatchQueue.main.async {
        self.user = result.user
      }

      // If sign in succeeded, display the app's main content View.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }

  func signOut() {
    GIDSignIn.sharedInstance.signOut()
    // After signing out, set the `user` state variable to `nil`.
    self.user = nil
  }
}

#Preview {
  ContentView()
}

UIKit

  1. ViewController.swift を開きます。
  2. ViewController の上部、signInButton を宣言した直下に、ログアウト ボタンとウェルカム ラベルを追加します。
let signOutButton = UIButton(type: .system)
let welcomeLabel = UILabel()
  1. ViewController の末尾に次の関数を追加します。この関数は、ユーザーのログイン ステータスに応じて異なる UI を表示します。
private func updateUI(for user: GIDGoogleUser?) {
  if let user = user {
    // User is signed in.
    signInButton.isHidden = true
    signOutButton.isHidden = false
    welcomeLabel.isHidden = false
    welcomeLabel.text = "Hello, \(user.profile?.givenName ?? "User")!"
  } else {
    // User is signed out.
    signInButton.isHidden = false
    signOutButton.isHidden = true
    welcomeLabel.isHidden = true
  }
}
  1. viewDidLoad 関数の最後に次のコードを追加して、ウェルカム ラベルとログアウト ボタンをビューに追加します。
// --- Set up the Welcome Label ---
welcomeLabel.translatesAutoresizingMaskIntoConstraints = false
welcomeLabel.textAlignment = .center
welcomeLabel.font = .systemFont(ofSize: 24, weight: .bold)
view.addSubview(welcomeLabel)

NSLayoutConstraint.activate([
  welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  welcomeLabel.bottomAnchor.constraint(equalTo: signInButton.topAnchor, constant: -20),
])

// --- Set up the Sign-Out Button ---
signOutButton.translatesAutoresizingMaskIntoConstraints = false
signOutButton.setTitle("Sign Out", for: .normal)
view.addSubview(signOutButton)

NSLayoutConstraint.activate([
  signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  signOutButton.topAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: 20),
])

signOutButton.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)

// --- Set Initial UI State ---
updateUI(for: nil)
  1. ログインが成功した場合に UpdateUI メソッドを呼び出すように signInButtonTapped 関数を更新します。
@objc func signInButtonTapped() {
  // Start the sign-in process.
  GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
    guard let result = signInResult else {
      // Inspect error
      print("Error signing in: \(error?.localizedDescription ?? "No error description")")
      return
    }

    // If sign in succeeded, print the ID token.
    print("ID Token: \(result.user.idToken?.tokenString ?? "")")

    DispatchQueue.main.async {
      self.updateUI(for: result.user)
    }
  }
}
  1. 最後に、ログアウト プロセスを処理する signOutButtonTapped 関数を ViewController に追加します。
@objc func signOutButtonTapped() {
  GIDSignIn.sharedInstance.signOut()
  // Update the UI for the signed-out state.
  updateUI(for: nil)
}

アプリを起動してログインします。認証に成功すると、UI が変更されます。

iOS シミュレータでの UIKit フレームワークのログイン状態

これらの変更を行うと、ViewController.swift ファイルは次のようになります。

import GoogleSignIn
import UIKit

class ViewController: UIViewController {

  // Create an instance of the Sign in with Google button
  let signInButton = GIDSignInButton()
  let signOutButton = UIButton(type: .system)
  let welcomeLabel = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()

    // Set the width and color of the sign-in button
    signInButton.style = .standard  // Options: .standard, .wide, .iconOnly
    signInButton.colorScheme = .dark  // Options: .dark, .light

    // Add the sign-in button to your view
    view.addSubview(signInButton)

    // Position the button using constraints
    signInButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    ])

    // Add a target to the button to call a method when it's pressed
    signInButton.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside)

    // --- Set up the Welcome Label ---
    welcomeLabel.translatesAutoresizingMaskIntoConstraints = false
    welcomeLabel.textAlignment = .center
    welcomeLabel.font = .systemFont(ofSize: 24, weight: .bold)
    view.addSubview(welcomeLabel)

    NSLayoutConstraint.activate([
      welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      welcomeLabel.bottomAnchor.constraint(equalTo: signInButton.topAnchor, constant: -20),
    ])

    // --- Set up the Sign-Out Button ---
    signOutButton.translatesAutoresizingMaskIntoConstraints = false
    signOutButton.setTitle("Sign Out", for: .normal)
    view.addSubview(signOutButton)

    NSLayoutConstraint.activate([
      signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signOutButton.topAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: 20),
    ])

    signOutButton.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)

    // --- Set Initial UI State ---
    updateUI(for: nil)
  }

  // This method is called when the sign-in button is pressed.
  @objc func signInButtonTapped() {
    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      // If sign in succeeded, print the ID token.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")

      DispatchQueue.main.async {
        self.updateUI(for: result.user)
      }
    }
  }

  private func updateUI(for user: GIDGoogleUser?) {
    if let user = user {
      // User is signed in.
      signInButton.isHidden = true
      signOutButton.isHidden = false
      welcomeLabel.isHidden = false
      welcomeLabel.text = "Hello, \(user.profile?.givenName ?? "User")!"
    } else {
      // User is signed out.
      signInButton.isHidden = false
      signOutButton.isHidden = true
      welcomeLabel.isHidden = true
    }
  }

  @objc func signOutButtonTapped() {
    GIDSignIn.sharedInstance.signOut()
    // Update the UI for the signed-out state.
    updateUI(for: nil)
  }
}

9. ユーザーのログイン状態を復元する

リピーターのユーザー エクスペリエンスを向上させるには、アプリの起動時にログイン状態を復元する必要があります。restorePreviousSignIn を呼び出すと、キーチェーンに保存された認証情報を使用してユーザーが自動的に再ログインするため、ユーザーは毎回ログインフローを完了する必要がなくなります。

SwiftUI

  1. ContentView.swift を開きます。
  2. body 変数内の VStack の直後に次のコードを追加します。
.onAppear {
  // On appear, try to restore a previous sign-in.
  GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
    // This closure is called when the restoration is complete.
    if let user = user {
      // If a user was restored, update the `user` state variable.
      DispatchQueue.main.async {
        self.user = user
      }

      // Print the ID token to the console when restored.
      print("Restored ID Token: \(user.idToken?.tokenString ?? "")")
    }
  }
}

ContentView.swift は次のようになります。

import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

struct ContentView: View {

  @State private var user: GIDGoogleUser?

  var body: some View {
    VStack {
      // Check if the user is signed in.
      if let user = user {
        // If signed in, show a welcome message and the sign-out button.
        Text("Hello, \(user.profile?.givenName ?? "User")!")
          .font(.title)
          .padding()

        Button("Sign Out", action: signOut)
          .buttonStyle(.borderedProminent)

      } else {
        // If not signed in, show the "Sign in with Google" button.
        GoogleSignInButton(
          scheme: .dark,  // Options: .light, .dark, .auto
          style: .standard,  // Options: .standard, .wide, .icon
          state: .normal,  // Options: .normal, .disabled
          action: handleSignInButton
        ).padding()
      }
    }

    .onAppear {
      // On appear, try to restore a previous sign-in.
      GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
        // This closure is called when the restoration is complete.
        if let user = user {
          // If a user was restored, update the `user` state variable.
          DispatchQueue.main.async {
            self.user = user
          }

          // Print the ID token to the console when restored.
          print("Restored ID Token: \(user.idToken?.tokenString ?? "")")
        }
      }
    }
  }

  func handleSignInButton() {
    // Find the current window scene.
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
      print("There is no active window scene")
      return
    }

    // Get the root view controller from the window scene.
    guard
      let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
        .rootViewController
    else {
      print("There is no key window or root view controller")
      return
    }

    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(
      withPresenting: rootViewController
    ) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      DispatchQueue.main.async {
        self.user = result.user
      }

      // If sign in succeeded, display the app's main content View.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }

  func signOut() {
    GIDSignIn.sharedInstance.signOut()
    // After signing out, set the `user` state variable to `nil`.
    self.user = nil
  }
}

#Preview {
  ContentView()
}

UIKit

  1. ViewController.swift を開きます。
  2. viewDidLoad メソッドの末尾に次の restorePreviousSignIn 呼び出しを追加します。
// Attempt to restore a previous sign-in session
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
  if let user = user {
    print("Successfully restored sign-in for user: \(user.profile?.givenName ?? "Unknown")")

    // Print the ID token when a session is restored.
    print("Restored ID Token: \(user.idToken?.tokenString ?? "")")

    // On success, update the UI for the signed-in state on the main thread.
    DispatchQueue.main.async {
      self.updateUI(for: user)
    }
  }
}

ViewController.swift ファイルは次のようになります。

import GoogleSignIn
import UIKit

class ViewController: UIViewController {

  // Create an instance of the Sign in with Google button
  let signInButton = GIDSignInButton()
  let signOutButton = UIButton(type: .system)
  let welcomeLabel = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()

    // Set the width and color of the sign-in button
    signInButton.style = .standard  // Options: .standard, .wide, .iconOnly
    signInButton.colorScheme = .dark  // Options: .dark, .light

    // Add the sign-in button to your view
    view.addSubview(signInButton)

    // Position the button using constraints
    signInButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    ])

    // Add a target to the button to call a method when it's pressed
    signInButton.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside)

    // --- Set up the Welcome Label ---
    welcomeLabel.translatesAutoresizingMaskIntoConstraints = false
    welcomeLabel.textAlignment = .center
    welcomeLabel.font = .systemFont(ofSize: 24, weight: .bold)
    view.addSubview(welcomeLabel)

    NSLayoutConstraint.activate([
      welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      welcomeLabel.bottomAnchor.constraint(equalTo: signInButton.topAnchor, constant: -20),
    ])

    // --- Set up the Sign-Out Button ---
    signOutButton.translatesAutoresizingMaskIntoConstraints = false
    signOutButton.setTitle("Sign Out", for: .normal)
    view.addSubview(signOutButton)

    NSLayoutConstraint.activate([
      signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signOutButton.topAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: 20),
    ])

    signOutButton.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)

    // --- Set Initial UI State ---
    updateUI(for: nil)

    // Attempt to restore a previous sign-in session
    GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
      if let user = user {
        print("Successfully restored sign-in for user: \(user.profile?.givenName ?? "Unknown")")

        // Print the ID token when a session is restored.
        print("Restored ID Token: \(user.idToken?.tokenString ?? "")")

        // On success, update the UI for the signed-in state on the main thread.
        DispatchQueue.main.async {
          self.updateUI(for: user)
        }
      }
    }
  }

  // This method is called when the sign-in button is pressed.
  @objc func signInButtonTapped() {
    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      // If sign in succeeded, print the ID token.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")

      DispatchQueue.main.async {
        self.updateUI(for: result.user)
      }
    }
  }

  private func updateUI(for user: GIDGoogleUser?) {
    if let user = user {
      // User is signed in.
      signInButton.isHidden = true
      signOutButton.isHidden = false
      welcomeLabel.isHidden = false
      welcomeLabel.text = "Hello, \(user.profile?.givenName ?? "User")!"
    } else {
      // User is signed out.
      signInButton.isHidden = false
      signOutButton.isHidden = true
      welcomeLabel.isHidden = true
    }
  }

  @objc func signOutButtonTapped() {
    GIDSignIn.sharedInstance.signOut()
    // Update the UI for the signed-out state.
    updateUI(for: nil)
  }
}

サイレント ログインをテストする

ログインしたら、アプリを完全に終了して、もう一度起動します。ボタンをタップしなくても自動的にログインしていることがわかります。

10. ID トークンについて

GIDGoogleUser オブジェクトは、ユーザーの名前とメールアドレスを使用して UI をパーソナライズするのに便利ですが、SDK から返される最も重要なデータは ID トークンです。

この Codelab では、オンライン ツールを使用して JWT の内容を検査します。本番環境のアプリでは、この ID トークンをバックエンド サーバーに送信する必要があります。サーバーは ID トークンの完全性を検証し、JWT を使用して、バックエンド プラットフォームでの新しいアカウントの作成やユーザーの新しいセッションの確立など、より意味のある処理を行う必要があります。

JWT トークンにアクセスしてデコードする

  1. アプリを起動します。
  2. Xcode コンソールを開きます。ID トークンが出力されます。eyJhbGciOiJSUzI1Ni ... Hecz6Wm4Q のようになります。
  3. ID トークンをコピーし、jwt.io などのオンライン ツールを使用して JWT をデコードします。

デコードされた JWT は次のようになります。

{
  "alg": "RS256",
  "kid": "c8ab71530972bba20b49f78a09c9852c43ff9118",
  "typ": "JWT"
}
{
  "iss": "https://accounts.google.com",
  "azp": "171291171076-rrbkcjrp5jbte92ai9gub115ertscphi.apps.googleusercontent.com",
  "aud": "171291171076-rrbkcjrp5jbte92ai9gub115ertscphi.apps.googleusercontent.com",
  "sub": "10769150350006150715113082367",
  "email": "example@example.com",
  "email_verified": true,
  "at_hash": "JyCYDmHtzhjkb0-qJhKsMg",
  "name": "Kimya",
  "picture": "https://lh3.googleusercontent.com/a/ACg8ocIyy4VoR31t_n0biPVcScBHwZOCRaKVDb_MoaMYep65fyqoAw=s96-c",
  "given_name": "Kimya",
  "iat": 1758645896,
  "exp": 1758649496
}

注目すべきトークン フィールド

デコードされた ID トークンには、さまざまな目的のフィールドが含まれています。名前やメールアドレスなど、簡単に理解できるものもあれば、バックエンド サーバーで検証に使用されるものもあります。

次のフィールドは特に重要です。

  • sub: sub フィールドは、ユーザーの Google アカウントの一意の永続的な識別子です。ユーザーはメインのメールアドレスまたは名前を変更できますが、sub ID は変更できません。これにより、sub フィールドはバックエンド ユーザー アカウントの主キーとして使用するのに最適な値になります。

ID トークンからユーザー情報を取得するでは、すべてのトークン フィールドの意味について詳しく説明しています。

11. App Check でアプリを保護する

App Check を有効にして、自分のアプリだけがプロジェクトに代わって Google の OAuth 2.0 エンドポイントにアクセスできるようにすることを強くおすすめします。App Check は、バックエンド サービスに対するリクエストが、改ざんされていない実機上の正規のアプリから発信されたものであることを確認することで機能します。

このセクションでは、App Check をアプリに統合し、シミュレータでのデバッグと実際のデバイスで実行される本番環境ビルドの両方用に構成する方法について説明します。

コンソールの設定

App Check をアプリケーションに統合するには、Google Cloud コンソールと Firebase コンソールで 1 回限りの設定が必要です。これには、Google Cloud コンソールで iOS OAuth クライアントの App Check を有効にする、App Check デバッグ プロバイダで使用する API キーを作成する、Google Cloud プロジェクトを Firebase にリンクする、といった作業が含まれます。

Google Cloud コンソールで App Check を有効にする

  1. Google Cloud プロジェクトに関連付けられているクライアントのリストに移動します。
  2. iOS アプリ用に作成した OAuth 2.0 クライアント ID を選択します。
  3. [Google Identity for iOS] の下にある [App Check] をオンに切り替えます。

OAuth クライアントの編集ページと App Check の切り替え

  1. [保存] をクリックします。

API キーを作成する

  1. Google Cloud プロジェクトの [API ライブラリ] ページに移動します。
  2. 検索バーに「Firebase App Check API」と入力します。

Google Cloud コンソールの API ライブラリ ページ

  1. [Firebase App Check API] を選択して有効にします。
  2. [API とサービス] に移動し、ナビゲーション メニューで [認証情報] を選択します。
  3. ページの上部にある [認証情報を作成] を選択します。

Google Cloud コンソールの API 認証情報ページ

  1. この API キーに名前を割り当てます。
  2. [アプリケーションの制限] で [iOS アプリ] を選択します。
  3. アプリのバンドル ID を承認済みアプリケーションとして追加します。
  4. [API の制限] で [キーを制限] を選択します。
  5. プルダウン メニューから [Firebase App Check API] を選択します。
  6. [作成] を選択します。

Google Cloud コンソールの API キー作成ページ

  1. 作成された API キーをコピーします。これは今後の手順で必要となります。

Firebase を Google Cloud プロジェクトに追加する

  1. Firebase コンソールに移動します。
  2. [Firebase プロジェクトを設定して開始] を選択します。
  3. [Firebase を Google Cloud プロジェクトに追加] を選択します。

既存の Google Cloud プロジェクトに Firebase を追加する

  1. プルダウンから Google Cloud プロジェクトを選択し、登録フローを続行します。
  2. [Firebase を追加] を選択します。
  3. Firebase プロジェクトの準備ができたら、[続行] を選択してプロジェクトを開きます。

クライアントサイド コードの統合

App Check 用に Google Cloud プロジェクトを構成したら、クライアントサイドのコードを記述して App Check を有効にします。構成証明に使用されるプロバイダは、本番環境とデバッグ環境で異なります。実際のデバイス上の本番環境アプリは、Apple の組み込み App Attest サービスを使用して、その信頼性を証明します。ただし、iOS シミュレータではこの種の証明書を提供できないため、デバッグ環境では API キーが渡される特別なデバッグ プロバイダが必要です。

次のコードでは、コンパイラ ディレクティブを使用してビルド時に正しいプロバイダを自動的に選択することで、両方のシナリオを処理します。

SwiftUI

  1. メインアプリのファイルを開きます。
  2. インポートの後、@main 属性の前に、次の AppDelegate クラスを定義します。
class AppDelegate: NSObject, UIApplicationDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {

    #if targetEnvironment(simulator)
      // Configure for debugging on a simulator.
      // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
      let apiKey = "YOUR_API_KEY"
      GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
        if let error {
          print("Error configuring GIDSignIn debug provider: \(error)")
        }
      }
    #else
      // Configure GIDSignIn for App Check on a real device.
      GIDSignIn.sharedInstance.configure { error in
        if let error {
          print("Error configuring GIDSignIn for App Check: \(error)")
        } else {
          print("GIDSignIn configured for App Check.")
        }
      }
    #endif

    return true
  }
}
  1. 提供されたコードの "YOUR_API_KEY" を、Google Cloud コンソールからコピーした API キーに置き換えます。
  2. App 構造体の body 変数の直前に、次の行を追加します。これにより、AppDelegate クラスがアプリのライフサイクルに登録され、アプリの起動やその他のシステム イベントに応答できるようになります。
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

メインアプリ ファイルは次のようになります。

import GoogleSignIn
import SwiftUI

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {

    #if targetEnvironment(simulator)
      // Configure for debugging on a simulator.
      // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
      let apiKey = "YOUR_API_KEY"
      GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
        if let error {
          print("Error configuring GIDSignIn debug provider: \(error)")
        }
      }
    #else
      // Configure GIDSignIn for App Check on a real device.
      GIDSignIn.sharedInstance.configure { error in
        if let error {
          print("Error configuring GIDSignIn for App Check: \(error)")
        } else {
          print("GIDSignIn configured for App Check.")
        }
      }
    #endif

    return true
  }
}

@main
struct iOS_Sign_in_with_Google_App: App {

  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

  var body: some Scene {
    WindowGroup {
      ContentView()

        .onOpenURL { url in
          GIDSignIn.sharedInstance.handle(url)
        }
    }
  }
}

UIKit

  1. AppDelegate.swift を開きます。
  2. App Check の初期化を含むように application(_:didFinishLaunchingWithOptions:) メソッドを更新します。
func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

  #if targetEnvironment(simulator)
    // Configure for debugging on a simulator.
    // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
    let apiKey = "YOUR_API_KEY"
    GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
      if let error {
        print("Error configuring GIDSignIn debug provider: \(error)")
      }
    }
  #else
    // Configure GIDSignIn for App Check on a real device.
    GIDSignIn.sharedInstance.configure { error in
      if let error {
        print("Error configuring GIDSignIn for App Check: \(error)")
      }
    }
  #endif

  return true
}
  1. 提供されたコードの "YOUR_API_KEY" を、Google Cloud コンソールからコピーした API キーに置き換えます。

AppDelegate.swift ファイルは次のようになります。

import GoogleSignIn
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {

    #if targetEnvironment(simulator)
      // Configure for debugging on a simulator.
      // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
      let apiKey = "YOUR_API_KEY"
      GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
        if let error {
          print("Error configuring GIDSignIn debug provider: \(error)")
        }
      }
    #else
      // Configure GIDSignIn for App Check on a real device.
      GIDSignIn.sharedInstance.configure { error in
        if let error {
          print("Error configuring GIDSignIn for App Check: \(error)")
        }
      }
    #endif

    return true
  }

  func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    var handled: Bool

    handled = GIDSignIn.sharedInstance.handle(url)
    if handled {
      return true
    }
    // If not handled by this app, return false.
    return false
  }

  // MARK: UISceneSession Lifecycle

  func application(
    _ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions
  ) -> UISceneConfiguration {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return UISceneConfiguration(
      name: "Default Configuration",
      sessionRole: connectingSceneSession.role
    )
  }

  func application(
    _ application: UIApplication,
    didDiscardSceneSessions sceneSessions: Set<UISceneSession>
  ) {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
  }
}

シミュレータで App Check をテストする

  1. Xcode のメニューバーで、[Product] > [Scheme] > [Edit Scheme] の順に移動します。
  2. ナビゲーション メニューで [実行] を選択します。
  3. [Arguments] タブを選択します。
  4. [Arguments Passed on Launch] セクションで [+] を選択し、-FIRDebugEnabled を追加します。この起動引数を使用すると、Firebase デバッグ ロギングが有効になります。
  5. [閉じる] を選択します。

Xcode の引数エディタ ページ

  1. シミュレータでアプリを起動します。
  2. Xcode コンソールに出力された App Check デバッグ トークンをコピーします。

Xcode コンソールの App Check デバッグ トークン

  1. Firebase コンソールでプロジェクトに移動します。
  2. ナビゲーション メニューの [ビルド] セクションを開きます。
  3. [アプリのチェック] を選択します。
  4. [アプリ] タブを選択します。
  5. アプリにカーソルを合わせて、その他メニュー アイコンを選択します。

Firebase App Check の設定

  1. [デバッグ トークンを管理] を選択します。
  2. [デバッグ トークンを追加] を選択します。
  3. デバッグ トークンに名前を付け、先ほどコピーしたデバッグ トークンを値として貼り付けます。
  4. [保存] を選択してトークンを登録します。

Firebase App Check デバッグ トークンの管理

  1. シミュレータに戻ってログインします。

指標がコンソールに表示されるまでに数分かかることがあります。App Check が有効になると、次のいずれかの場所で Verified リクエストの増加を確認することで、App Check が機能していることを確認できます。

  • Firebase コンソールの [App Check] セクションの [API] タブ。

Firebase App Check の指標

  • Google Cloud コンソールの OAuth クライアントの編集ページ。

Google Cloud コンソールでの App Check の指標

アプリの App Check 指標をモニタリングし、正当なリクエストが検証されていることを確認したら、App Check の適用を有効にします。適用後は、App Check は未検証のリクエストをすべて拒否し、正規のアプリからのトラフィックのみがプロジェクトに代わって Google の OAuth 2.0 エンドポイントにアクセスできるようにします。

12. 参考情報

お疲れさまでした

OAuth 2.0 iOS クライアントを構成し、iOS アプリに [Google でログイン] ボタンを追加し、ボタンの外観をカスタマイズする方法を学び、JWT ID トークンをデコードし、アプリの App Check を有効にしました。

次のリンクは、次の手順に役立ちます。

よくある質問