iOS アプリに多要素認証を追加する
このドキュメントでは、iOS アプリに SMS 多要素認証を追加する方法について説明します。
多要素認証では、アプリのセキュリティが強化されます。攻撃者はパスワードとソーシャル アカウントを不正使用することがよくありますが、テキスト メッセージを傍受するのは、はるかに困難です。
始める前に
多要素認証をサポートするプロバイダを少なくとも 1 つ有効にします。すべてのプロバイダが、電話認証、匿名認証、Apple Game Center を除く MFA をサポートしています。
アプリでユーザーのメールアドレスが検証されていることを確認します。MFA では、メールの確認を行う必要があります。これにより、悪意のある攻撃者が所有していないメールアドレスでサービスを登録し、次に第 2 要素を追加することで実際の所有者をロックアウトすることを防ぎます。
多要素認証の有効化
Google Cloud コンソールで [Identity Platform MFA] ページに移動します。
[MFA] ページに移動[SMS ベースの多要素認証] というボックスで [有効にする] をクリックします。
アプリのテストに使用する電話番号を入力します。必要に応じて、開発中のスロットリングを回避するためにテスト用の電話番号を登録することを強くおすすめします。
アプリのドメインをまだ承認していない場合は、右側の [ドメインの追加] をクリックして許可リストに追加します。
[保存] をクリックします。
アプリの確認
Identity Platform では、SMS リクエストの送信元が自身のアプリからであることが確認される必要があります。この確認を行うには、次の 2 つの方法があります。
サイレント APNs 通知: ユーザーが初めてログインするとき、Identity Platform からユーザーのデバイスにサイレント プッシュ通知が送信されます。アプリが通知を受信した場合に、認証を進めることが可能になります。iOS 8.0 以降では、この方法を使用するために、ユーザーにプッシュ通知利用の許可を求める必要はありません。
reCAPTCHA 検証: サイレント通知を送信できない場合(たとえば、ユーザーがバックグラウンド更新を無効にしている場合や、iOS シミュレータでアプリをテストしている場合など)は、reCAPTCHA を使用できます。多くの場合、reCAPTCHA はユーザーの操作なしで自動的に解決されます。
サイレント通知の使用
Identity Platform で APNs 通知の使用を有効にするには:
Xcode で、プロジェクトのプッシュ通知を有効にします。
Firebase コンソールを使用して APNs 認証キーをアップロードします(変更内容は自動的に Google Cloud Identity Platform に引き継がれます)。APNs 認証キーをまだ持っていない場合は、FCM での APNs の構成を参照して取得方法を確認してください。
Firebase コンソールを開きます。
[Project Settings] に移動します。
[クラウド メッセージング] タブを選択します。
[APNs 認証キー] の [iOS アプリの構成] セクションで [アップロード] をクリックします。
キーを選択します。
キーのキー ID を追加します。キー ID は、Apple Developer Member Center の [Certificates, Identifiers & Profiles] で確認できます。
[アップロード] をクリックします。
APNs 証明書がある場合は、代わりに証明書をアップロードできます。
reCAPTCHA による確認の使用
クライアント SDK で reCAPTCHA を使用できるようにするには:
Xcode でプロジェクト構成を開きます。
左側のツリービューでプロジェクト名をダブルクリックします。
[ターゲット] セクションからアプリを選択します。
[情報] タブを選択します。
[URL タイプ] セクションを展開します。
[+] ボタンをクリックします。
[URL スキーム] フィールドに反転クライアント ID を入力します。この値は
GoogleService-Info.plist
構成ファイルでREVERSED_CLIENT_ID
として一覧表示されます。
完了すると、構成は次のようになります。
必要に応じて、reCAPTCHA を表示する際、アプリによる SFSafariViewController
または UIWebView
の表示方法をカスタマイズできます。これを行うには、FIRAuthUIDelegate
プロトコルに準拠するカスタムクラスを作成し、verifyPhoneNumber:UIDelegate:completion:
に渡します。
登録パターンの選択
アプリで多要素認証が必要かどうかと、ユーザーを登録する方法とタイミングを選択できます。一般的なパターンには、次のようなものがあります。
登録の一部として、ユーザーの第 2 要素を登録する。アプリがすべてのユーザーに対して多要素認証を必要とする場合は、この方法を使用します。 アカウントに 2 つ目の要素を登録するには、確認メールのアドレスが必要です。そのため、登録フローはこの要件を満たす必要があります。
登録中に第 2 要素の登録をスキップできる選択肢を用意する。多要素認証を必須とはしないが、推奨したいと考えるアプリでは、この方法が望ましい場合があります。
登録画面ではなく、ユーザーのアカウントまたはプロフィールの管理ページから第 2 要素を追加する機能を用意する。これにより、登録プロセス中の摩擦が最小限に抑えられる一方、セキュリティに敏感なユーザーは多要素認証を利用できるようになります。
セキュリティ要件が強化された機能にユーザーがアクセスする際には、第 2 要素を段階的に追加することを要求する。
第 2 要素の登録
ユーザーの新しい第 2 要素を登録するには:
ユーザーを再認証します。
ユーザーに電話番号の入力を依頼します。
ユーザーの多要素セッションを取得します。
Swift
authResult.user.multiFactor.getSessionWithCompletion() { (session, error) in // ... }
Objective-C
[authResult.user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session, NSError * _Nullable error) { // ... }];
ユーザーの電話に確認メッセージを送信します。電話番号は、先頭に
+
を付けて、他の句読点や空白文字を含めないようにします(例:+15105551234
)。Swift
// Send SMS verification code. PhoneAuthProvider.provider().verifyPhoneNumber( phoneNumber, uiDelegate: nil, multiFactorSession: session) { (verificationId, error) in // verificationId will be needed for enrollment completion. }
Objective-C
// Send SMS verification code. [FIRPhoneAuthProvider.provider verifyPhoneNumber:phoneNumber UIDelegate:nil multiFactorSession:session completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { // verificationId will be needed for enrollment completion. }];
必須ではありませんが、SMS メッセージを受信することと、その標準料金が適用されることを、あらかじめユーザーに知らせることをおすすめします。
verifyPhoneNumber()
メソッドは、サイレント プッシュ通知を使用して、バックグラウンドでアプリ検証プロセスを開始します。サイレント プッシュ通知が利用できない場合は、代わりに reCAPTCHA チャレンジが発行されます。SMS コードを送信したら、コードの確認をユーザーに依頼します。次に、そのレスポンスを使用して、
PhoneAuthCredential
をビルドします。Swift
// Ask user for the verification code. Then: let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationId, verificationCode: verificationCode)
Objective-C
// Ask user for the SMS verification code. Then: FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID verificationCode:kPhoneSecondFactorVerificationCode];
アサーション オブジェクトを初期化します。
Swift
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
Objective-C
FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
登録を完了します。必要に応じて、第 2 要素の表示名を指定することもできます。これは、認証フローで電話番号がマスクされるため(たとえば、+1******1234 など)、複数の第 2 要素があるユーザーには便利です。
Swift
// Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. user.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in // ... }
Objective-C
// Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. [authResult.user.multiFactor enrollWithAssertion:assertion displayName:nil completion:^(NSError * _Nullable error) { // ... }];
次のコードは、第 2 要素を登録するための完全な例を示しています。
Swift
let user = Auth.auth().currentUser
user?.multiFactor.getSessionWithCompletion({ (session, error) in
// Send SMS verification code.
PhoneAuthProvider.provider().verifyPhoneNumber(
phoneNumber,
uiDelegate: nil,
multiFactorSession: session
) { (verificationId, error) in
// verificationId will be needed for enrollment completion.
// Ask user for the verification code.
let credential = PhoneAuthProvider.provider().credential(
withVerificationID: verificationId!,
verificationCode: phoneSecondFactorVerificationCode)
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
// Complete enrollment. This will update the underlying tokens
// and trigger ID token change listener.
user?.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
// ...
}
}
})
Objective-C
FIRUser *user = FIRAuth.auth.currentUser;
[user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
NSError * _Nullable error) {
// Send SMS verification code.
[FIRPhoneAuthProvider.provider
verifyPhoneNumber:phoneNumber
UIDelegate:nil
multiFactorSession:session
completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
// verificationId will be needed for enrollment completion.
// Ask user for the verification code.
// ...
// Then:
FIRPhoneAuthCredential *credential =
[FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID
verificationCode:kPhoneSecondFactorVerificationCode];
FIRMultiFactorAssertion *assertion =
[FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
// Complete enrollment. This will update the underlying tokens
// and trigger ID token change listener.
[user.multiFactor enrollWithAssertion:assertion
displayName:displayName
completion:^(NSError * _Nullable error) {
// ...
}];
}];
}];
これで完了です。ユーザーの第 2 の認証要素が正常に登録されました。
第 2 要素でのユーザーのログイン
2 つの要素の SMS 確認を使用してユーザーのログインを行うには:
第 1 の要素でユーザーのログインを行うと、多要素認証が必要であることを示すエラーを検知します。このエラーには、リゾルバ、登録された第 2 要素に関するヒント、および最初の要素でユーザーが正常に認証された基礎となるセッションが含まれます。
たとえば、ユーザーの第 1 要素がメールアドレスとパスワードの場合:
Swift
Auth.auth().signIn( withEmail: email, password: password ) { (result, error) in let authError = error as NSError if authError?.code == AuthErrorCode.secondFactorRequired.rawValue { // The user is a multi-factor user. Second factor challenge is required. let resolver = authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver // ... } else { // Handle other errors such as wrong password. } }
Objective-C
[FIRAuth.auth signInWithEmail:email password:password completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) { // User is not enrolled with a second factor and is successfully signed in. // ... } else { // The user is a multi-factor user. Second factor challenge is required. } }];
ユーザーの第 1 要素が OAuth などの連携プロバイダである場合は、
getCredentialWith()
の呼び出し後にエラーを検知します。ユーザーに複数の登録された第 2 要素がある場合は、どの要素を使用するかをユーザーに確認します。マスクされた電話番号は
resolver.hints[selectedIndex].phoneNumber
で、表示名はresolver.hints[selectedIndex].displayName
で取得できます。Swift
// Ask user which second factor to use. Then: if resolver.hints[selectedIndex].factorID == PhoneMultiFactorID { // User selected a phone second factor. // ... } else if resolver.hints[selectedIndex].factorID == TotpMultiFactorID { // User selected a TOTP second factor. // ... } else { // Unsupported second factor. }
Objective-C
FIRMultiFactorResolver *resolver = (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey]; // Ask user which second factor to use. Then: FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex]; if (hint.factorID == FIRPhoneMultiFactorID) { // User selected a phone second factor. // ... } else if (hint.factorID == FIRTOTPMultiFactorID) { // User selected a TOTP second factor. // ... } else { // Unsupported second factor. }
ユーザーの電話に確認メッセージを送信します。
Swift
// Send SMS verification code. let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo PhoneAuthProvider.provider().verifyPhoneNumber( with: hint, uiDelegate: nil, multiFactorSession: resolver.session ) { (verificationId, error) in // verificationId will be needed for sign-in completion. }
Objective-C
// Send SMS verification code [FIRPhoneAuthProvider.provider verifyPhoneNumberWithMultiFactorInfo:hint UIDelegate:nil multiFactorSession:resolver.session completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { if (error != nil) { // Failed to verify phone number. } }];
SMS コードを送信したら、ユーザーにコードの確認と、コードを使用した
PhoneAuthCredential
のビルドを依頼します。Swift
// Ask user for the verification code. Then: let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationId!, verificationCode: verificationCodeFromUser)
Objective-C
// Ask user for the SMS verification code. Then: FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID verificationCode:verificationCodeFromUser];
認証情報を使用してアサーション オブジェクトを初期化します。
Swift
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
Objective-C
FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
ログインを解決します。それから、元のログイン結果にアクセスできます。これには、標準のプロバイダ固有のデータと認証情報が含まれます。
Swift
// Complete sign-in. This will also trigger the Auth state listeners. resolver.resolveSignIn(with: assertion) { (authResult, error) in // authResult will also contain the user, additionalUserInfo, optional // credential (null for email/password) associated with the first factor sign-in. // For example, if the user signed in with Google as a first factor, // authResult.additionalUserInfo will contain data related to Google provider that // the user signed in with. // user.credential contains the Google OAuth credential. // user.credential.accessToken contains the Google OAuth access token. // user.credential.idToken contains the Google OAuth ID token. }
Objective-C
// Complete sign-in. [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error != nil) { // User successfully signed in with the second factor phone number. } }];
以下のコードは、多要素ユーザーのログインの完全な例を示しています。
Swift
Auth.auth().signIn(
withEmail: email,
password: password
) { (result, error) in
let authError = error as NSError?
if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
let resolver =
authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
// Ask user which second factor to use.
// ...
// Then:
let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo
// Send SMS verification code
PhoneAuthProvider.provider().verifyPhoneNumber(
with: hint,
uiDelegate: nil,
multiFactorSession: resolver.session
) { (verificationId, error) in
if error != nil {
// Failed to verify phone number.
}
// Ask user for the SMS verification code.
// ...
// Then:
let credential = PhoneAuthProvider.provider().credential(
withVerificationID: verificationId!,
verificationCode: verificationCodeFromUser)
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
// Complete sign-in.
resolver.resolveSignIn(with: assertion) { (authResult, error) in
if error != nil {
// User successfully signed in with the second factor phone number.
}
}
}
}
}
Objective-C
[FIRAuth.auth signInWithEmail:email
password:password
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
// User is not enrolled with a second factor and is successfully signed in.
// ...
} else {
FIRMultiFactorResolver *resolver =
(FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
// Ask user which second factor to use.
// ...
// Then:
FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];
// Send SMS verification code
[FIRPhoneAuthProvider.provider
verifyPhoneNumberWithMultiFactorInfo:hint
UIDelegate:nil
multiFactorSession:resolver.session
completion:^(NSString * _Nullable verificationID,
NSError * _Nullable error) {
if (error != nil) {
// Failed to verify phone number.
}
// Ask user for the SMS verification code.
// ...
// Then:
FIRPhoneAuthCredential *credential =
[FIRPhoneAuthProvider.provider
credentialWithVerificationID:verificationID
verificationCode:kPhoneSecondFactorVerificationCode];
FIRMultiFactorAssertion *assertion =
[FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
// Complete sign-in.
[resolver resolveSignInWithAssertion:assertion
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error != nil) {
// User successfully signed in with the second factor phone number.
}
}];
}];
}
}];
これで完了です。多要素認証を使用したユーザーのログインが正常に終了しました。
次のステップ
- Admin SDK を使用して、プログラムで多要素ユーザーを管理する。