使用 Microsoft 进行身份验证(Apple 平台)

如需让您的用户使用 Microsoft Azure Active Directory 等 OAuth 提供方进行 Firebase 身份验证,您可以使用 Firebase SDK 执行端到端登录流程,将基于 Web 的通用 OAuth 登录机制集成到您的应用中。

准备工作

如需让用户能够通过 Microsoft 账号(Azure Active Directory 和个人 Microsoft 账号)登录,您必须先启用 Microsoft 作为您的 Firebase 项目的登录服务提供方:

  1. 将 Firebase 添加到您的 Apple 项目
  2. Firebase 控制台中,打开 Auth 部分。
  3. 登录方法标签页中,启用 Microsoft 提供方。
  4. 将该提供方的开发者控制台中的客户端 ID客户端密钥添加至提供方配置:
    1. 如需注册 Microsoft OAuth 客户端,请按照快速入门:使用 Azure Active Directory v2.0 端点注册应用中的说明进行操作。 请注意,此端点同时支持使用 Microsoft 个人账号和 Azure Active Directory 账号进行登录。 详细了解 Azure Active Directory v2.0。
    2. 向这些提供方注册应用时,请务必将项目的 *.firebaseapp.com 网域注册为应用的重定向网域。
  5. 点击保存

使用 Firebase SDK 处理登录流程

如需使用 Firebase Apple 平台 SDK 处理登录流程,请按以下步骤操作:

  1. 将自定义网址方案 (URL scheme) 添加至 Xcode 项目中:

    1. 打开项目配置:在左侧的树状视图中双击项目名称。在目标部分中选择您的应用,然后选择信息标签页,并展开网址类型部分。
    2. 点击 + 按钮,然后将经过编码的应用 ID 添加为网址方案。您可以打开 Firebase 控制台的常规设置页面,在您的 iOS 应用部分找到经过编码的应用 ID。请将其他字段留空。

      完成上述操作后,您的配置应显示如下(但其中的值应替换为您的应用的值):

      Xcode 自定义网址方案设置界面的屏幕截图
  2. 使用提供方 ID OAuthProvider 创建一个 OAuthProvider 实例。

        var provider = OAuthProvider(providerID: "microsoft.com")
        
        FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:@"microsoft.com"];
        
  3. 可选:指定您希望通过 OAuth 请求发送的其他自定义 OAuth 参数。

        provider.customParameters = [
          "prompt": "consent",
          "login_hint": "user@firstadd.onmicrosoft.com"
        ]
        
        [provider setCustomParameters:@{@"prompt": @"consent", @"login_hint": @"user@firstadd.onmicrosoft.com"}];
        

    如需查看 Microsoft 支持的参数,请参阅 Microsoft OAuth 文档。 请注意,您不能使用 setCustomParameters 传递 Firebase 必需的参数。这些参数包括 client_idresponse_typeredirect_uristatescoperesponse_mode

    如需仅允许来自特定 Azure AD 租户的用户登录应用,可以使用 Azure AD 租户的易记域名或该租户的 GUID 标识符。为此,您可以在自定义参数对象中指定“租户”字段。

        provider.customParameters = [
          // Optional "tenant" parameter in case you are using an Azure AD
          // tenant. eg. '8eaef023-2b34-4da1-9baa-8bc8c9d6a490' or
          // 'contoso.onmicrosoft.com' or "common" for tenant-independent
          // tokens. The default value is "common".
          "tenant": "TENANT_ID"
        ]
        
        // Optional "tenant" parameter in case you are using an Azure AD tenant.
        // eg. '8eaef023-2b34-4da1-9baa-8bc8c9d6a490' or
        // 'contoso.onmicrosoft.com' or "common" for tenant-independent tokens.
        // The default value is "common".
        provider.customParameters = @{@"tenant": @"TENANT_ID"};
        
  4. 可选:指定您希望向身份验证提供方申请获取的个人资料基本信息以外的额外 OAuth 2.0 范围。

        provider.scopes = ["mail.read", "calendars.read"]
        
        [provider setScopes:@[@"mail.read", @"calendars.read"]];
        

    如需了解详情,请参阅 Microsoft 权限和许可文档

  5. 可选:如果您希望自定义应用在向用户显示 reCAPTCHA 时如何呈现 SFSafariViewControllerUIWebView,请创建一个符合 AuthUIDelegate 协议的自定义类,并将其传递给 credentialWithUIDelegate

  6. 使用 OAuth 提供方对象进行 Firebase 身份验证。

        // Replace nil with the custom class that conforms to AuthUIDelegate
        // you created in last step to use a customized web view.
        provider.getCredentialWith(nil) { credential, error in
          if error != nil {
            // Handle error.
          }
          if credential != nil {
            Auth().signIn(with: credential) { authResult, error in
              if error != nil {
                // Handle error.
              }
              // User is signed in.
              // IdP data available in authResult.additionalUserInfo.profile.
              // OAuth access token can also be retrieved:
              // (authResult.credential as? OAuthCredential)?.accessToken
              // OAuth ID token can also be retrieved:
              // (authResult.credential as? OAuthCredential)?.idToken
            }
          }
        }
        
        [provider getCredentialWithUIDelegate:nil
                                   completion:^(FIRAuthCredential *_Nullable credential, NSError *_Nullable error) {
          if (error) {
           // Handle error.
          }
          if (credential) {
            [[FIRAuth auth] signInWithCredential:credential
                                      completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) {
              if (error) {
                // Handle error.
              }
              // User is signed in.
              // IdP data available in authResult.additionalUserInfo.profile.
              // OAuth access token can also be retrieved:
              // ((FIROAuthCredential *)authResult.credential).accessToken
              // OAuth ID token can also be retrieved:
              // ((FIROAuthCredential *)authResult.credential).idToken
            }];
          }
        }];
        

    使用 OAuth 访问令牌,您可以调用 Microsoft Graph API

    例如,如需获取基本个人资料信息,您可以调用 REST API 并在 Authorization 标头中传递访问令牌:

    https://graph.microsoft.com/v1.0/me

    与 Firebase Authentication 支持的其他提供方不同,Microsoft 不提供照片网址,您必须通过 Microsoft Graph API 来请求个人资料照片的二进制数据。

    除了 OAuth 访问令牌,您还可以从 OAuthCredential 对象检索用户的 OAuth ID 令牌。ID 令牌中的 sub 声明取决于具体应用,该声明与 Firebase Auth 所使用的可通过 user.providerData[0].uid 访问的联合用户标识符不匹配。应改用 oid 声明字段。 使用 Azure AD 租户登录时,oid 声明将完全匹配。 不过,对于非租户,系统将用无效信息填充 oid 字段。对于联合 ID 4b2eabcdefghijkloid 的格式为 00000000-0000-0000-4b2e-abcdefghijkl

  7. 以上示例侧重的是登录流程。除此之外,您也可以使用 linkWithCredential 将 Microsoft 提供方与现有用户相关联。例如,您可以将多个提供方关联至同一个用户,以便使用任意一个进行登录。

        Auth().currentUser.link(withCredential: credential) { authResult, error in
          if error != nil {
            // Handle error.
          }
          // Microsoft credential is linked to the current user.
          // IdP data available in authResult.additionalUserInfo.profile.
          // OAuth access token can also be retrieved:
          // (authResult.credential as? OAuthCredential)?.accessToken
          // OAuth ID token can also be retrieved:
          // (authResult.credential as? OAuthCredential)?.idToken
        }
        
        [[FIRAuth auth].currentUser
            linkWithCredential:credential
                    completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
          if (error) {
            // Handle error.
          }
          // Microsoft credential is linked to the current user.
          // IdP data available in authResult.additionalUserInfo.profile.
          // OAuth access token can also be retrieved:
          // ((FIROAuthCredential *)authResult.credential).accessToken
          // OAuth ID token can also be retrieved:
          // ((FIROAuthCredential *)authResult.credential).idToken
        }];
        
  8. 上述模式同样适用于 reauthenticateWithCredential。对于要求用户必须在近期内登录过才能执行的敏感操作,可使用它来检索新的凭据。

        Auth().currentUser.reauthenticateWithCredential(withCredential: credential) { authResult, error in
          if error != nil {
            // Handle error.
          }
          // User is re-authenticated with fresh tokens minted and
          // should be able to perform sensitive operations like account
          // deletion and email or password update.
          // IdP data available in result.additionalUserInfo.profile.
          // Additional OAuth access token can also be retrieved:
          // (authResult.credential as? OAuthCredential)?.accessToken
          // OAuth ID token can also be retrieved:
          // (authResult.credential as? OAuthCredential)?.idToken
        }
        
        [[FIRAuth auth].currentUser
            reauthenticateWithCredential:credential
                              completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
          if (error) {
            // Handle error.
          }
          // User is re-authenticated with fresh tokens minted and
          // should be able to perform sensitive operations like account
          // deletion and email or password update.
          // IdP data available in result.additionalUserInfo.profile.
          // Additional OAuth access token can also be retrieved:
          // ((FIROAuthCredential *)authResult.credential).accessToken
          // OAuth ID token can also be retrieved:
          // ((FIROAuthCredential *)authResult.credential).idToken
        }];
        

如果您在 Firebase 控制台中启用了每个电子邮件地址一个账号设置,当用户尝试使用一个 Firebase 用户的提供方(例如 Google)中已存在的电子邮件地址登录另一个提供方(例如 Microsoft)时,系统会抛出 FIRAuthErrorCodeAccountExistsWithDifferentCredential 错误及临时 FIRAuthCredential 对象(Microsoft 凭据)。如需登录所需的提供方,用户必须首先登录现有提供方 (Google),然后再关联到之前的 FIRAuthCredential(Microsoft 凭据)。 如下所示:

  // Sign-in with an OAuth credential.
  provider.getCredentialWith(nil) { credential, error in
    // An account with the same email already exists.
    if (error as NSError?)?.code == AuthErrorCode.accountExistsWithDifferentCredential.rawValue {
      // Get pending credential and email of existing account.
      let existingAcctEmail = (error! as NSError).userInfo[AuthErrorUserInfoEmailKey] as! String
      let pendingCred = (error! as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as! AuthCredential
      // Lookup existing account identifier by the email.
      Auth.auth().fetchProviders(forEmail:existingAcctEmail) { providers, error in
        // Existing email/password account.
        if (providers?.contains(EmailAuthProviderID))! {
          // Existing password account for email. Ask user to provide the password of the
          // existing account.
          // Sign in with existing account.
          Auth.auth().signIn(withEmail:existingAcctEmail, password:password) { user, error in
            // Successfully signed in.
            if user != nil {
              // Link pending credential to account.
              Auth.auth().currentUser?.linkAndRetrieveData(with: pendingCred) { result, error in
                // ...
              }
            }
          }
        }
      }
      return
    }

    // Other errors.
    if error != nil {
      // handle the error.
      return
    }

    // Sign in with the credential.
    if credential != nil {
      Auth.auth().signInAndRetrieveData(with: credential!) { result, error in
        if error != nil {
          // handle the error.
          return
        }
      }
    }
  }

  
  // Sign-in with an OAuth credential.
  [provider getCredentialWithUIDelegate:nil
                             completion:^(FIRAuthCredential *_Nullable credential, NSError *_Nullable error) {
    // An account with the same email already exists.
    if (error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
      // Get pending credential and email of existing account.
      NSString *existingAcctEmail = error.userInfo[FIRAuthErrorUserInfoEmailKey];
      FIRAuthCredential *pendingCred = error.userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey];
      // Lookup existing account identifier by the email.
      [[FIRAuth auth] fetchProvidersForEmail:existingAcctEmail
                                 completion:^(NSArray<NSString *> *_Nullable providers,
                                              NSError *_Nullable error) {
        // Existing email/password account.
        if ( [providers containsObject:FIREmailAuthProviderID] ) {
          // Existing password account for email. Ask user to provide the password of the
          // existing account.

          // Sign in with existing account.
          [[FIRAuth auth] signInWithEmail:existingAcctEmail
                                 password:password
                               completion:^(FIRUser *user, NSError *error) {
            // Successfully signed in.
            if (user) {
              // Link pending credential to account.
              [[FIRAuth auth].currentUser linkWithCredential:pendingCred
                                                  completion:^(FIRUser *_Nullable user,
                                                               NSError *_Nullable error) {
                // ...
              }];
            }
          }];
        }
      }];
      return;
    }

    // Other errors.
    if (error) {
      // handle the error.
      return;
    }

    // Sign in with the credential.
    if (credential) {
      [[FIRAuth auth] signInAndRetrieveDataWithCredential:credential
          completion:^(FIRAuthDataResult *_Nullable authResult,
                       NSError *_Nullable error) {
        if (error) {
          // handle the error.
          return;
        }
      }];
    }
  }];
  

Firebase 支持的其他 OAuth 提供方(如 Google、Facebook 和 Twitter)可以通过基于 OAuth 访问令牌的凭据直接实现登录,Firebase Auth 则不同。由于 Firebase Auth 服务器无法验证 Microsoft 等提供方的 OAuth 访问令牌的目标设备,因此 Firebase Auth 不支持通过这些提供方直接登录。 这是一项关键的安全要求,不满足该要求的应用和网站可能会受到重放攻击的威胁。在这种情况下,为某个项目(攻击者)获取的 Microsoft OAuth 访问令牌可能被用来登录另一个项目(受害者)。 因此,Firebase Auth 改为提供另一种功能,即使用在 Firebase 控制台中配置的 OAuth 客户端 ID 和密钥来处理整个 OAuth 流程和授权代码交换。由于授权代码只能与特定客户端 ID/密钥结合使用,因此为某个项目获取的授权代码不能用于另一个项目。

如果需要在不受支持的环境中使用这些提供方,则需使用第三方 OAuth 库和 Firebase 自定义身份验证。前者用于向提供方进行身份验证,后者则用于将提供方的凭据交换成自定义令牌。

在用户首次登录后,系统会创建一个新的用户账号,并将其与该用户登录时使用的凭据(即用户名和密码、电话号码或者身份验证提供方信息)相关联。此新账号存储在您的 Firebase 项目中,无论用户采用何种方式登录,您项目中的每个应用都可以使用此账号来识别用户。

  • 在您的应用中,您可以从 User 对象获取用户的基本个人资料信息。请参阅管理用户

  • 在您的 Firebase Realtime DatabaseCloud Storage 安全规则中,您可以从 auth 变量获取已登录用户的唯一用户 ID,然后利用此 ID 来控制用户可以访问哪些数据。

您可以通过将身份验证提供方凭据关联至现有用户账号,让用户可以使用多个身份验证提供方登录您的应用。

如需将用户退出登录,请调用 signOut:

let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print("Error signing out: %@", signOutError)
}
NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status) {
  NSLog(@"Error signing out: %@", signOutError);
  return;
}

您可能还需要为所有身份验证错误添加错误处理代码。请参阅处理错误