如果您已透過 Identity Platform 升級至 Firebase 驗證,即可將簡訊多重驗證新增至 iOS 應用程式。
多重驗證可提高應用程式的安全性。攻擊者通常也會竊取密碼和社群媒體帳戶,但是要攔截簡訊更困難。
事前準備
請至少啟用一個支援多重驗證功能的提供者。每個供應商都支援多重驗證,不包括手機驗證、匿名驗證和 Apple Game Center。
確認應用程式正在驗證使用者的電子郵件地址。必須進行電子郵件驗證。這可以防止惡意人士使用他人擁有的電子郵件註冊服務,並新增第二個因素來鎖定真實的擁有者。
啟用多重驗證
開啟 Firebase 控制台的「Authentication」>「Sign-in method」頁面。
在「進階」部分中,啟用「簡訊多重驗證」。
請一併輸入要測試應用程式的電話號碼。 雖然非必要,但強烈建議您註冊測試電話號碼,避免在開發期間發生節流限制。
如果您尚未授權應用程式的網域,請在 Firebase 控制台的「Authentication」>「Settings」頁面將該網域加入許可清單。
驗證應用程式
Firebase 需要驗證簡訊要求是否來自您的應用程式。驗證方式有兩種:
靜音 APN 通知:當您首次登入使用者時,Firebase 可以傳送無訊息推播通知到使用者的裝置。如果應用程式收到通知,驗證作業就會繼續進行。請注意,從 iOS 8.0 開始,您不需要要求使用者允許推播通知使用這個方法。
reCAPTCHA 驗證:如果您無法傳送無訊息通知 (例如使用者停用背景重新整理,或在 iOS 模擬器中測試應用程式),您可以使用 reCAPTCHA。在許多情況下,reCAPTCHA 會自動解決,使用者完全不需要進行互動。
使用靜音通知
如何啟用 APN 通知與 Firebase 搭配使用:
在 Xcode 中,為專案啟用推播通知。
使用 Firebase 控制台上傳 APN 驗證金鑰 (您所做的變更將自動轉移至 Google Cloud Firebase)。如果您沒有 APN 驗證金鑰,請參閱使用 FCM 設定 APN,瞭解如何取得金鑰。
開啟 Firebase 控制台。
前往「專案設定」。
選取「雲端通訊」分頁標籤。
在「APNs authentication key」(APN 驗證金鑰) 下方的「iOS app configuration」(iOS 應用程式設定) 區段中,按一下 [Upload] (上傳)。
選取金鑰。
新增該金鑰的金鑰 ID。您可以在 Apple Developer Member Center 的「Certificates, Identifiers & Profiles」下方找到金鑰 ID。
按一下「上傳」。
如果您已有 APN 憑證,可改為上傳憑證。
使用 reCAPTCHA 驗證
如何為用戶端 SDK 啟用 reCAPTCHA:
在 Xcode 中開啟專案設定。
按一下左側樹狀檢視中的專案名稱。
從「Targets」(目標) 部分選取您的應用程式。
選取「資訊」分頁標籤。
展開「網址類型」部分。
按一下「+」按鈕。
在「網址配置」欄位中輸入復原的用戶端 ID。您可以在
GoogleService-Info.plist
設定檔中找到這個值為REVERSED_CLIENT_ID
。
完成後,設定應如下所示:
您可以視需要自訂應用程式在顯示 reCAPTCHA 時,顯示 SFSafariViewController
或 UIWebView
的方式。方法是建立符合 FIRAuthUIDelegate
通訊協定的自訂類別,並傳送至 verifyPhoneNumber:UIDelegate:completion:
。
選擇註冊模式
您可以選擇應用程式是否需要多重驗證,以及使用者註冊的方式和時間。常見的模式包括:
註冊使用者的第二重驗證在註冊過程中。如果應用程式需要所有使用者的多重驗證,請使用這個方法。請注意,帳戶必須有已驗證的電子郵件地址才能註冊第二重驗證方式,因此註冊流程必須配合這種方式。
提供可略過的選項,讓使用者在註冊期間註冊第二個步驟。如果應用程式希望鼓勵但不需要多重驗證,則建議使用此方法。
能夠從使用者帳戶或設定檔管理頁面 (而非註冊畫面) 新增第二重驗證方式。這樣可盡量減少註冊過程中的阻礙,同時讓有安全性考量的使用者可以使用多重要素驗證功能。
如果使用者想要存取安全性需求較高的功能,則需要逐步新增第二個驗證步驟。
註冊第二重驗證方式
如何為使用者註冊新的次要驗證條件:
重新驗證使用者。
請使用者輸入電話號碼。
為使用者取得多重要素工作階段:
Swift
authResult.user.multiFactor.getSessionWithCompletion() { (session, error) in // ... }
目標-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. }
目標-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. }];
雖然並非必要,但最佳做法是事先告知使用者他們將收到簡訊,且須支付一般費率。
verifyPhoneNumber()
方法會使用無訊息推播通知,在背景啟動應用程式驗證程序。如果無法使用無聲推播通知,系統會改為發出 reCAPTCHA 驗證問題。讓系統傳送簡訊驗證碼後,要求使用者驗證驗證碼。然後使用其回應建構
PhoneAuthCredential
:Swift
// Ask user for the verification code. Then: let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationId, verificationCode: verificationCode)
目標-C
// Ask user for the SMS verification code. Then: FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID verificationCode:kPhoneSecondFactorVerificationCode];
初始化斷言物件:
Swift
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
目標-C
FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
完成註冊程序。您也可以選擇為第二個因素指定顯示名稱。由於電話號碼在驗證流程期間會遮蓋 (例如 +1******1234),因此這對具有多個次要驗證者的使用者來說非常實用。
Swift
// Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. user.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in // ... }
目標-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) { // ... }];
以下程式碼顯示第二重驗證註冊的完整範例:
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
// ...
}
}
})
目標-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) {
// ...
}];
}];
}];
恭喜!您已成功為使用者註冊第二重驗證。
透過次要驗證方式登入使用者
如何透過雙重簡訊驗證功能登入使用者帳戶:
使用第一個因素登入使用者,然後擷取需要多重驗證的錯誤。這項錯誤包含解析器、註冊雙重驗證提示,以及用來證明使用者已透過第一個因素成功驗證的基礎工作階段。
舉例來說,如果使用者的第一個驗證方法是電子郵件地址和密碼:
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. } }
目標-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. } }];
如果使用者的第一個因素是 OAuth 等聯合提供者,請在呼叫
getCredentialWith()
後擷取錯誤。如果使用者已註冊多個次要因子,請詢問他們要使用的次要因數。您可以使用
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. }
目標-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. }
目標-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. } }];
簡訊驗證碼發送後,要求使用者驗證代碼,並使用該代碼建構
PhoneAuthCredential
:Swift
// Ask user for the verification code. Then: let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationId!, verificationCode: verificationCodeFromUser)
目標-C
// Ask user for the SMS verification code. Then: FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID verificationCode:verificationCodeFromUser];
使用憑證初始化斷言物件:
Swift
let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
目標-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. }
目標-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.
}
}
}
}
}
目標-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 以程式輔助方式管理多重驗證使用者。