Firebase Authentication を使用してユーザーのスマートフォンに SMS メッセージを送信することで、ユーザーはログインすることができます。ユーザーは SMS メッセージに記載されたワンタイム コードを使用してログインします。
電話番号ログインをアプリに追加する最も簡単な方法は、FirebaseUI を使用することです。このライブラリには、電話番号ログインのほか、パスワードに基づくログインやフェデレーション ログインのログインフローを実装するドロップイン式のログイン ウィジェットが含まれています。このドキュメントでは、Firebase SDK を使用して電話番号ログインフローを実装する方法について説明します。
始める前に
- アプリを Firebase プロジェクトに接続していない場合は、Firebase コンソールで接続します。
-
Swift Package Manager を使用して Firebase の依存関係をインストールし、管理します。
- Xcode でアプリのプロジェクトを開いたまま、[File] > [Add Packages] の順に移動します。
- プロンプトが表示されたら、Firebase Apple プラットフォーム SDK リポジトリを追加します。
- Firebase Authentication ライブラリを選択します。
- ターゲットのビルド設定の [Other Linker Flags] セクションに
-ObjC
フラグを追加します。 - 上記の作業が完了すると、Xcode は依存関係の解決とダウンロードをバックグラウンドで自動的に開始します。
https://github.com/firebase/firebase-ios-sdk.git
セキュリティに関する懸念
電話番号の所有権はユーザー間で簡単に移転できるため、電話番号のみを使用する認証は便利である反面、セキュリティ面では他の認証方法より劣ります。また、複数のユーザー プロファイルを持つデバイスでは、SMS メッセージを受信できるすべてのユーザーが、デバイスの電話番号を使用してアカウントにログインできます。
アプリで電話番号ベースのログインを使用する場合は、よりセキュリティの高いログイン方法も同時に提供し、電話番号ログインを使用した場合のセキュリティ面での懸念をユーザーに通知する必要があります。
Firebase プロジェクトで電話番号ログインを有効にする
ユーザーが SMS を介してログインできるようにするには、まず Firebase プロジェクトで電話番号ログイン方法を有効にする必要があります。
- Firebase コンソールで [Authentication] セクションを開きます。
- [Sign-in method] ページで、[電話番号] のログイン方法を有効にします。
アプリの確認を有効にする
電話番号認証を使用するには、アプリから送信される電話番号ログイン リクエストを、Firebase が検証できるようにしなければなりません。Firebase Authentication でこれを可能にするには、次の 2 つの方法があります。
- サイレント APNs 通知: ユーザーがデバイスで初めて電話番号を使用してログインすると、Firebase Authentication はサイレント プッシュ通知を使用してデバイスにトークンを送信します。アプリが Firebase からの通知を正常に受信した場合、電話番号ログインを続行できます。
iOS 8.0 以降のサイレント通知は、明示的なユーザーの同意を必要としないため、ユーザーがアプリで APNs 通知の受信を拒否してもその影響を受けません。したがって、アプリは Firebase 電話番号認証を実装する際に、プッシュ通知を受信する許可をユーザーに求める必要はありません。
- reCAPTCHA 検証: サイレント プッシュ通知を送信または受信できない場合(たとえば、ユーザーがアプリのバックグラウンド更新を無効にしている場合や、iOS シミュレータでアプリをテストしている場合など)、Firebase Authentication では reCAPTCHA による確認を使用して電話番号ログインフローを実行します。通常、reCAPTCHA チャレンジを完了するためにユーザーが解決しなければならないものはありません。
サイレント プッシュ通知が適切に構成されていれば、reCAPTCHA フローが発生するユーザーの割合はわずかです。それでも、サイレント プッシュ通知を使用できるかどうかにかかわらず、電話番号ログインが正常に機能することを確認してください。
サイレント通知の受信を開始する
Firebase Authentication で APN 通知を有効にするには:
- Xcode で、プロジェクトのプッシュ通知を有効にします。
-
APNs 認証キーを Firebase にアップロードします。まだ APNs 認証キーを用意していない場合は、Apple Developer Member Center で作成してください。
-
Firebase コンソールのプロジェクト内で歯車アイコンを選択し、[プロジェクトの設定]、[Cloud Messaging] タブの順に選択します。
-
[iOS アプリの構成] の下の [APNs 認証キー] で [アップロード] ボタンをクリックします。
-
キーを保存した場所に移動し、キーを選択して [開く] をクリックします。キーのキー ID(Apple Developer Member Center で確認できます)を追加し、[アップロード] をクリックします。
APNs 証明書を用意している場合には、証明書をアップロードできます。
-
- Xcode で、プロジェクトのバックグラウンド モード機能を有効にしてから、[バックグラウンド取得] モードと [リモート通知] モードのチェックボックスをオンにします。
reCAPTCHA 検証を設定する
Firebase SDK で reCAPTCHA 検証を使用できるようにするには:
- Xcode プロジェクトにカスタム URL スキームを追加します。
- プロジェクト構成を開きます(左側のツリービューでプロジェクト名をダブルクリックします)。[TARGETS] セクションでアプリを選択し、[Info] タブを開いて [URL Types] セクションを展開します。
- [+] ボタンをクリックし、エンコードされたアプリ ID を URL スキームとして追加します。エンコードされたアプリ ID は、Firebase コンソールの [全般設定] ページで、iOS アプリのセクションで確認できます。その他のフィールドは空白にしておきます。
完了すると、構成は次のようになります(ただし、値はアプリケーションによって異なります)。
- 省略可: アプリがユーザーに reCAPTCHA を表示する際の
SFSafariViewController
の提示方法をカスタマイズするには、AuthUIDelegate
プロトコルに従ったカスタムクラスを作成して、そのクラスをverifyPhoneNumber(_:uiDelegate:completion:)
に渡します。
ユーザーの電話に確認コードを送信する
電話番号ログインを開始するには、ユーザーに電話番号の入力を求めるインターフェースを表示した後、verifyPhoneNumber(_:uiDelegate:completion:)
を呼び出して、ユーザーのスマートフォンに認証コードを SMS で送信するよう Firebase にリクエストします。
-
ユーザーの電話番号を取得します。
法的要件はさまざまに異なりますが、電話番号ログインを使用する場合は確認用の SMS メッセージが送られる旨、それには標準料金がかかる旨をユーザーにあらかじめ知らせることをおすすめします。
verifyPhoneNumber(_:uiDelegate:completion:)
を呼び出し、ユーザーの電話番号を渡します。Swift
PhoneAuthProvider.provider() .verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in if let error = error { self.showMessagePrompt(error.localizedDescription) return } // Sign in using the verificationID and the code sent to the user // ... }
Objective-C
[[FIRPhoneAuthProvider provider] verifyPhoneNumber:userInput UIDelegate:nil completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { if (error) { [self showMessagePrompt:error.localizedDescription]; return; } // Sign in using the verificationID and the code sent to the user // ... }];
verifyPhoneNumber
メソッドはリエントラントです。たとえば、ビューのonAppear
メソッドなどで複数回呼び出された場合でも、元のリクエストがタイムアウトしていなければ、verifyPhoneNumber
メソッドから 2 番目の SMS は送信されません。verifyPhoneNumber(_:uiDelegate:completion:)
を呼び出すと、Firebase からアプリにサイレント プッシュ通知が送信されるか、ユーザーに対して reCAPTCHA チャレンジが発行されます。アプリが通知を受信した後、またはユーザーが reCAPTCHA チャレンジを完了した後、Firebase から認証コードを含む SMS メッセージが指定の電話番号に送信され、確認 ID が補完関数に渡されます。ユーザーをログインさせるには、確認コードと確認 ID の両方が必要です。Firebase によって送信される SMS メッセージはローカライズすることもできます。それには、Auth インスタンスの
languageCode
プロパティを使用して auth 言語を指定します。Swift
// Change language code to french. Auth.auth().languageCode = "fr";
Objective-C
// Change language code to french. [FIRAuth auth].languageCode = @"fr";
-
確認 ID を保存し、アプリの読み込み時に復元します。これにより、ユーザーがログインフローを完了する前にアプリが終了した場合でも(たとえば SMS アプリへの切り替え時など)、有効な確認 ID を残すことができます。
確認 ID は任意の方法で保持できます。簡単な方法は、確認 ID を
NSUserDefaults
オブジェクトに保存することです。Swift
UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
Objective-C
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:verificationID forKey:@"authVerificationID"];
これで、保存された値を復元できます。
Swift
let verificationID = UserDefaults.standard.string(forKey: "authVerificationID")
Objective-C
NSString *verificationID = [defaults stringForKey:@"authVerificationID"];
verifyPhoneNumber(_:uiDelegate:completion:)
への呼び出しが成功したら、SMS メッセージで受け取った確認コードを入力するようユーザーに要求できます。
確認コードを使ってユーザーをログインさせる
ユーザーが SMS メッセージで受信した確認コードをアプリに入力したら、確認コードと確認 ID から FIRPhoneAuthCredential
オブジェクトを作成し、そのオブジェクトを signInWithCredential:completion:
に渡して、ユーザーをログインさせます。
- ユーザーから確認コードを取得します。
- 確認コードと確認 ID から
FIRPhoneAuthCredential
オブジェクトを作成します。Swift
let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationID, verificationCode: verificationCode )
Objective-C
FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID verificationCode:userInput];
FIRPhoneAuthCredential
オブジェクトを使用して、ユーザーをログインさせます。Swift
Auth.auth().signIn(with: credential) { authResult, error in if let error = error { let authError = error as NSError if isMFAEnabled, authError.code == AuthErrorCode.secondFactorRequired.rawValue { // The user is a multi-factor user. Second factor challenge is required. let resolver = authError .userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver var displayNameString = "" for tmpFactorInfo in resolver.hints { displayNameString += tmpFactorInfo.displayName ?? "" displayNameString += " " } self.showTextInputPrompt( withMessage: "Select factor to sign in\n\(displayNameString)", completionBlock: { userPressedOK, displayName in var selectedHint: PhoneMultiFactorInfo? for tmpFactorInfo in resolver.hints { if displayName == tmpFactorInfo.displayName { selectedHint = tmpFactorInfo as? PhoneMultiFactorInfo } } PhoneAuthProvider.provider() .verifyPhoneNumber(with: selectedHint!, uiDelegate: nil, multiFactorSession: resolver .session) { verificationID, error in if error != nil { print( "Multi factor start sign in failed. Error: \(error.debugDescription)" ) } else { self.showTextInputPrompt( withMessage: "Verification code for \(selectedHint?.displayName ?? "")", completionBlock: { userPressedOK, verificationCode in let credential: PhoneAuthCredential? = PhoneAuthProvider.provider() .credential(withVerificationID: verificationID!, verificationCode: verificationCode!) let assertion: MultiFactorAssertion? = PhoneMultiFactorGenerator .assertion(with: credential!) resolver.resolveSignIn(with: assertion!) { authResult, error in if error != nil { print( "Multi factor finanlize sign in failed. Error: \(error.debugDescription)" ) } else { self.navigationController?.popViewController(animated: true) } } } ) } } } ) } else { self.showMessagePrompt(error.localizedDescription) return } // ... return } // User is signed in // ... }
Objective-C
[[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (isMFAEnabled && error && error.code == FIRAuthErrorCodeSecondFactorRequired) { FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey]; NSMutableString *displayNameString = [NSMutableString string]; for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) { [displayNameString appendString:tmpFactorInfo.displayName]; [displayNameString appendString:@" "]; } [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Select factor to sign in\n%@", displayNameString] completionBlock:^(BOOL userPressedOK, NSString *_Nullable displayName) { FIRPhoneMultiFactorInfo* selectedHint; for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) { if ([displayName isEqualToString:tmpFactorInfo.displayName]) { selectedHint = (FIRPhoneMultiFactorInfo *)tmpFactorInfo; } } [FIRPhoneAuthProvider.provider verifyPhoneNumberWithMultiFactorInfo:selectedHint UIDelegate:nil multiFactorSession:resolver.session completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { if (error) { [self showMessagePrompt:error.localizedDescription]; } else { [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Verification code for %@", selectedHint.displayName] completionBlock:^(BOOL userPressedOK, NSString *_Nullable verificationCode) { FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID verificationCode:verificationCode]; FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential]; [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error) { [self showMessagePrompt:error.localizedDescription]; } else { NSLog(@"Multi factor finanlize sign in succeeded."); } }]; }]; } }]; }]; } else if (error) { // ... return; } // User successfully signed in. Get user data from the FIRUser object if (authResult == nil) { return; } FIRUser *user = authResult.user; // ... }];
架空の電話番号でテストする
Firebase コンソールを使用して、開発用の架空の電話番号を設定できます。架空の電話番号でテストすると、次のメリットがあります。
- 使用量の割り当てを消費することなく電話番号認証をテストできます。
- 実際の SMS メッセージを送信することなく電話番号認証をテストできます。
- 同じ電話番号で連続してテストを実施してもスロットルが発生しません。このため、レビュー担当者が偶発的にテストに同じ電話番号を使用しても、アプリストアのレビュー プロセス中に拒否されるリスクが最小限に抑えられます。
- 追加の作業を必要とせずに開発環境で簡単にテストできます。たとえば、Google Play 開発者サービスを使用せずに iOS シミュレータや Android Emulator で開発できます。
- セキュリティ チェックによるブロックが生じない統合テストを作成できます。通常、本番環境の実際の電話番号にはセキュリティ チェックが適用されます。
架空の電話番号は、次の要件を満たしている必要があります。
- 間違いなく架空で、存在していない電話番号を使用します。Firebase Authentication では、実際のユーザーが使用している既存の電話番号をテスト番号として設定することはできません。1 つの選択肢としては、+1 650-555-3434 のように、555 プレフィックス付きの番号を米国のテスト電話番号として使用します。
- 長さなどの制約に関して、正しい形式の電話番号を使用する必要があります。番号には、実際のユーザーの電話番号と同じ検証が行われます。
- 開発用に最大 10 個の電話番号を追加できます。
- 推測が困難なテスト用の電話番号やコードを使用し、頻繁に変更してください。
架空の電話番号と確認コードを作成する
- Firebase コンソールで、[Authentication] セクションを開きます。
- [Sign-in method] タブで、[電話番号] をまだ有効にしていない場合は有効にします。
- [テスト用の電話番号] アコーディオン メニューを開きます。
- テストする電話番号を入力します(例: +1 650-555-3434)。
- 特定の番号用の 6 桁の確認コードを入力します(例: 654321)。
- 番号を追加します。必要に応じて、電話番号の対応する行にカーソルを合わせてゴミ箱アイコンをクリックすると、電話番号とそのコードを削除できます。
手動テスト
架空の電話番号をアプリケーションで直接使用できます。これにより、割り当ての問題やスロットルを発生させることなく、開発段階で手動テストを実施できます。また、Google Play 開発者サービスをインストールすることなく、iOS シミュレータや Android Emulator から直接テストすることもできます。
架空の電話番号を指定して確認コードを送信しても、実際の SMS は送信されません。代わりに、以前に構成した確認コードを入力してログインを完了する必要があります。
ログインが完了すると、その電話番号を使用して Firebase ユーザーが作成されます。そのユーザーの動作やプロパティは実際の電話番号のユーザーと同じであり、また同じ方法で Realtime Database や Cloud Firestore などのサービスにアクセスできます。このプロセス中に作成された ID トークンには、実際の電話番号のユーザーと同じ署名が含まれます。
別の選択肢としては、アクセス制限をさらに厳格にする場合、これらのユーザーにカスタム クレームを介してテスト役割を設定して、架空のユーザーとして区別します。
統合テスト
Firebase Authentication には、手動テスト以外にも、スマートフォン認証テスト用の統合テストの作成に役立つ API が用意されています。これらの API は、ウェブの reCAPTCHA の要件や iOS のサイレント プッシュ通知を無効にすることで、アプリの確認を無効にします。これにより、これらのフローで自動テストを実施でき、実装が容易になります。また、Android で即時確認のフローをテストするための機能も備わっています。
iOS では、verifyPhoneNumber
を呼び出す前に appVerificationDisabledForTesting
設定を TRUE
に設定する必要があります。これは、バックグラウンドで APNs トークンを要求したりサイレント プッシュ通知を送信することなく処理されたりするため、シミュレータでのテストが容易になります。これにより、reCAPTCHA フォールバック フローも無効になります。
アプリの確認が無効になっている場合、架空の電話番号を使用するとログインが完了しません。この API では、架空の電話番号のみ使用できます。
Swift
let phoneNumber = "+16505554567" // This test verification code is specified for the given test phone number in the developer console. let testVerificationCode = "123456" Auth.auth().settings.isAppVerificationDisabledForTesting = TRUE PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate:nil) { verificationID, error in if (error) { // Handles error self.handleError(error) return } let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID ?? "", verificationCode: testVerificationCode) Auth.auth().signInAndRetrieveData(with: credential) { authData, error in if (error) { // Handles error self.handleError(error) return } _user = authData.user }]; }];
Objective-C
NSString *phoneNumber = @"+16505554567"; // This test verification code is specified for the given test phone number in the developer console. NSString *testVerificationCode = @"123456"; [FIRAuth auth].settings.appVerificationDisabledForTesting = YES; [[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) { if (error) { // Handles error [self handleError:error]; return; } FIRAuthCredential *credential = [FIRPhoneAuthProvider credentialWithVerificationID:verificationID verificationCode:testVerificationCode]; [FIRAuth auth] signInWithAndRetrieveDataWithCredential:credential completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { if (error) { // Handles error [self handleError:error]; return; } _user = user; }]; }];
付録: 実装入れ替えなしで電話番号ログインを使用する
Firebase Authentication は、メソッドの実装入れ替えを使用して、アプリの APNs トークンの自動取得、Firebase がアプリに送信するサイレント プッシュ通知の処理、検証中に reCAPTCHA による確認ページからリダイレクトされるカスタム スキームの自動インターセプトを行います。
実装入れ替えを使用しない場合は、FirebaseAppDelegateProxyEnabled
フラグをアプリの Info.plist ファイルに追加し、それを NO
に設定することで入れ替えを無効化できます。このフラグを NO
に設定すると、Firebase Cloud Messaging を含む他の Firebase 製品の実装入れ替えも無効になります。
実装入れ替えを無効にする場合は、明示的に APNs デバイス トークン、プッシュ通知、カスタム スキームのリダイレクト URL を Firebase Authentication に渡す必要があります。
また、SwiftUI アプリケーションをビルドする場合も、明示的に APNs デバイス トークン、プッシュ通知、カスタム スキームのリダイレクト URL を Firebase Authentication に渡す必要があります。
APNs デバイス トークンを取得するには、application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
メソッドを実装し、その中でデバイス トークンを Auth
の setAPNSToken(_:type:)
メソッドに渡します。
Swift
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // Pass device token to auth Auth.auth().setAPNSToken(deviceToken, type: .unknown) // Further handling of the device token if needed by the app // ... }
Objective-C
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { // Pass device token to auth. [[FIRAuth auth] setAPNSToken:deviceToken type:FIRAuthAPNSTokenTypeProd]; // Further handling of the device token if needed by the app. }
プッシュ通知を処理するには、application(_:didReceiveRemoteNotification:fetchCompletionHandler:):
メソッドで、Auth
の canHandleNotification(_:)
メソッドを呼び出して Firebase Authentication 関連の通知を確認します。
Swift
func application(_ application: UIApplication, didReceiveRemoteNotification notification: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { if Auth.auth().canHandleNotification(notification) { completionHandler(.noData) return } // This notification is not auth related; it should be handled separately. }
Objective-C
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // Pass notification to auth and check if they can handle it. if ([[FIRAuth auth] canHandleNotification:notification]) { completionHandler(UIBackgroundFetchResultNoData); return; } // This notification is not auth related; it should be handled separately. }
カスタム スキームのリダイレクト URL を処理するには、application(_:open:options:)
メソッドを実装し、URL を Auth
の canHandleURL(_:)
メソッドに渡します。
Swift
func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool { if Auth.auth().canHandle(url) { return true } // URL not auth related; it should be handled separately. }
Objective-C
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { if ([[FIRAuth auth] canHandleURL:url]) { return YES; } // URL not auth related; it should be handled separately. }
SwiftUI または UISceneDelegate
を使用している場合、リダイレクト URL を処理するには、scene(_:openURLContexts:)
メソッドを実装し、URL を Auth
の canHandleURL(_:)
メソッドに渡します。
Swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { for urlContext in URLContexts { let url = urlContext.url _ = Auth.auth().canHandle(url) } // URL not auth related; it should be handled separately. }
Objective-C
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts { for (UIOpenURLContext *urlContext in URLContexts) { [FIRAuth.auth canHandleURL:urlContext.url]; // URL not auth related; it should be handled separately. } }
次のステップ
ユーザーが初めてログインすると、新しいユーザー アカウントが作成され、ユーザーがログイン時に使用した認証情報(ユーザー名とパスワード、電話番号、または認証プロバイダ情報)にアカウントがリンクされます。この新しいアカウントは Firebase プロジェクトの一部として保存され、ユーザーのログイン方法にかかわらず、プロジェクトのすべてのアプリでユーザーを識別するために使用できます。
-
アプリでは、
User
オブジェクトからユーザーの基本的なプロフィール情報を取得できます。ユーザーを管理するをご覧ください。 Firebase Realtime Database と Cloud Storage のセキュリティ ルールでは、ログイン済みユーザーの一意のユーザー ID を
auth
変数から取得し、それを使用して、ユーザーがアクセスできるデータを制御できます。
既存のユーザー アカウントに認証プロバイダの認証情報をリンクすることで、ユーザーは複数の認証プロバイダを使用してアプリにログインできるようになります。
ユーザーのログアウトを行うには、signOut:
を呼び出します。
Swift
let firebaseAuth = Auth.auth() do { try firebaseAuth.signOut() } catch let signOutError as NSError { print("Error signing out: %@", signOutError) }
Objective-C
NSError *signOutError; BOOL status = [[FIRAuth auth] signOut:&signOutError]; if (!status) { NSLog(@"Error signing out: %@", signOutError); return; }
さまざまな認証エラーに対応できるようにエラー処理コードを追加することもできます。エラーの処理をご覧ください。