使用 Azure AD B2C 在自己的 iOS Swift 应用中启用身份验证

重要

自 2025 年 5 月 1 日起,Azure AD B2C 将不再可供新客户购买。 在我们的常见问题解答中了解详细信息

本文介绍如何将 Azure Active Directory B2C (Azure AD B2C) 身份验证添加到自己的 iOS Swift 移动应用程序。 了解如何将 iOS Swift 应用程序与 适用于 iOS 的Microsoft身份验证库(MSAL)集成。

将本文与 在示例 iOS Swift 应用程序中配置身份验证配合使用,将示例 iOS Swift 应用替换为你自己的 iOS Swift 应用。 完成本文中的说明后,应用程序将通过 Azure AD B2C 接受登录。

先决条件

请查看在示例 iOS Swift 应用中使用 Azure AD B2C 配置身份验证中的前提条件和集成说明。

创建 iOS Swift 应用项目

如果还没有 iOS Swift 应用程序,请执行以下步骤来设置新项目:

  1. 打开 Xcode,然后选择“ 文件>新建>项目”。
  2. 对于 iOS 应用,选择 iOS>应用,然后选择“ 下一步”。
  3. 对于 为您的新项目选择选项,请提供以下内容:
    1. 产品名称,例如 MSALiOS
    2. 组织标识符,例如 contoso.com
    3. 对于 界面,请选择 情节提要
    4. 对于 生命周期,请选择 UIKit 应用代理
    5. 对于 语言,请选择 Swift
  4. 选择“下一步”。
  5. 选择要在其中创建应用的文件夹,然后选择“ 创建”。

步骤 1:安装 MSAL 库

  1. 使用 CocoaPods 安装 MSAL 库。 在项目的 .xcodeproj 文件所在的同一文件夹中,如果 podfile 文件不存在,请创建一个空文件并将其命名为 podfile。 将以下代码添加到 podfile 文件:

    use_frameworks!
    
    target '<your-target-here>' do
       pod 'MSAL'
    end
    
  2. <your-target-here>替换为您的项目名称(例如MSALiOS)。 有关详细信息,请参阅 Podfile 语法参考

  3. 在终端窗口中,转到包含 podfile 文件的文件夹,然后运行 Pod 安装 以安装 MSAL 库。

  4. 运行pod install命令后,将创建一个名为“<你的项目名称>.xcworkspace”的文件。 若要在 Xcode 中重新加载项目,请关闭 Xcode,然后打开<项目 name.xcworkspace> 文件。

步骤 2:设置应用 URL 方案

当用户进行身份验证时,Azure AD B2C 使用在 Azure AD B2C 应用程序注册上配置的重定向 URI 将授权代码发送到应用。

MSAL 默认重定向 URI 格式为 msauth.[Your_Bundle_Id]://auth。 例如 msauth.com.microsoft.identitysample.MSALiOS://auth,其中 msauth.com.microsoft.identitysample.MSALiOS 是 URL 方案。

在此步骤中,使用 CFBundleURLSchemes 数组注册 URL 方案。 你的应用程序会侦听 URL 方案,以捕获来自 Azure AD B2C 的回调。

在 Xcode 中,以源代码文件的形式打开 Info.plist 文件 。 在本 <dict> 部分中,添加以下 XML 代码片段:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>msauth.com.microsoft.identitysample.MSALiOS</string>
        </array>
    </dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>msauthv2</string>
    <string>msauthv3</string>
</array>

步骤 3:添加身份验证代码

示例代码由类UIViewController组成。 该类:

  • 定义用户界面的结构。
  • 包含有关 Azure AD B2C 标识提供者的信息。 应用使用此信息与 Azure AD B2C 建立信任关系。
  • 包含身份验证代码,用于对用户进行身份验证、获取令牌并对其进行验证。

选择用户将在其中进行身份验证的 UIViewController。 在你的 GitHub UIViewController中,将代码与 GitHub 中提供的代码合并。

步骤 4:配置 iOS Swift 应用

添加身份验证代码后,使用 Azure AD B2C 设置配置 iOS Swift 应用。 Azure AD B2C 标识提供者设置是在 UIViewController 上一部分选择的类中配置的。

若要了解如何配置 iOS Swift 应用,请参阅 使用 Azure AD B2C 在示例 iOS Swift 应用中配置身份验证

步骤 5:运行和测试移动应用

  1. 使用 连接的 iOS 设备的模拟器生成并运行项目。
  2. 选择 “登录”,然后使用 Azure AD B2C 本地或社交帐户注册或登录。
  3. 成功进行身份验证后,将在导航栏中看到显示名称。

步骤 6:自定义代码构建基块

本部分介绍为 iOS Swift 应用启用身份验证的代码构建基块。 它列出了 UIViewController 的方法,并讨论了如何自定义代码。

步骤 6.1:实例化公共客户端应用程序

公共客户端应用程序不受信任,无法安全地保留应用程序机密,并且它们没有客户端机密。 在 viewDidLoad 中,使用公共客户端应用程序对象实例化 MSAL。

以下 Swift 代码片段演示如何使用 MSALPublicClientApplicationConfig 配置对象初始化 MSAL。

配置对象提供有关 Azure AD B2C 环境的信息。 例如,它提供客户端 ID、重定向 URI 和权限认证来构建针对 Azure AD B2C 的身份验证请求。 有关配置对象的信息,请参阅 “配置示例移动应用”。

do {

    let signinPolicyAuthority = try self.getAuthority(forPolicy: self.kSignupOrSigninPolicy)
    let editProfileAuthority = try self.getAuthority(forPolicy: self.kEditProfilePolicy)
    
    let pcaConfig = MSALPublicClientApplicationConfig(clientId: kClientID, redirectUri: kRedirectUri, authority: signinPolicyAuthority)
    pcaConfig.knownAuthorities = [signinPolicyAuthority, editProfileAuthority]
    
    self.applicationContext = try MSALPublicClientApplication(configuration: pcaConfig)
    self.initWebViewParams()
    
    } catch {
        self.updateLoggingText(text: "Unable to create application \(error)")
    }

该方法 initWebViewParams 配置 交互式身份验证 体验。

以下 Swift 代码片段使用系统 Web 视图初始化 webViewParameters 类成员。 有关详细信息,请参阅 自定义适用于 iOS/macOS 的浏览器和 WebView

func initWebViewParams() {
    self.webViewParameters = MSALWebviewParameters(authPresentationViewController: self)
    self.webViewParameters?.webviewType = .default
}

步骤 6.2:启动交互式授权请求

交互式授权请求是一个流,提示用户使用系统 Web 视图注册或登录。 当用户选择 “登录 ”按钮时, authorizationButton 将调用该方法。

方法 authorizationButton 准备 MSALInteractiveTokenParameters 对象,以包含有关授权请求的相关数据。 该acquireToken 方法使用MSALInteractiveTokenParameters通过系统 Web 视图对用户进行身份验证。

以下代码片段演示如何启动交互式授权请求:

let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: self.webViewParameters!)
parameters.promptType = .selectAccount
parameters.authority = authority

applicationContext.acquireToken(with: parameters) { (result, error) in

// On error code    
guard let result = result else {
    self.updateLoggingText(text: "Could not acquire token: \(error ?? "No error information" as! Error)")
    return
}

// On success code
self.accessToken = result.accessToken
self.updateLoggingText(text: "Access token is \(self.accessToken ?? "Empty")")
}

在用户完成授权流程后,无论成功还是失败,结果都将传递到方法的acquireToken

该方法 acquireToken 返回 resulterror 对象。 使用此闭包可以:

  • 在身份验证完成后,使用信息更新移动应用 UI。
  • 使用访问令牌调用 Web API 服务。
  • 处理身份验证错误(例如,当用户取消登录流时)。

步骤 6.3:调用 Web API

若要调用 基于令牌的授权 Web API,应用需要有效的访问令牌。 此应用程序的功能如下:

  1. 获取对 Web API 终结点拥有所需权限(范围)的访问令牌。
  2. 使用以下格式,在 HTTP 请求的授权标头中将该访问令牌作为持有者令牌进行传递:
Authorization: Bearer <access-token>

当用户 以交互方式进行身份验证时,应用会在关闭时 acquireToken 获取访问令牌。 对于后续的 Web API 调用,请使用静默获取令牌(acquireTokenSilent)方法,如本节中所述。

该方法 acquireTokenSilent 执行以下动作:

  1. 它尝试从令牌缓存中提取具有所请求作用域的访问令牌。 如果令牌存在且尚未过期,则返回令牌。
  2. 如果令牌缓存中不存在令牌或令牌已过期,MSAL 库将尝试使用刷新令牌获取新的访问令牌。
  3. 如果刷新令牌不存在或已过期,则返回异常。 在这种情况下,应提示用户 以交互方式登录

以下代码片段演示如何获取访问令牌:

do {

// Get the authority using the sign-in or sign-up user flow
let authority = try self.getAuthority(forPolicy: self.kSignupOrSigninPolicy)

// Get the current account from the application context
guard let thisAccount = try self.getAccountByPolicy(withAccounts: applicationContext.allAccounts(), policy: kSignupOrSigninPolicy) else {
    self.updateLoggingText(text: "There is no account available!")
    return
}

// Configure the acquire token silent parameters
let parameters = MSALSilentTokenParameters(scopes: kScopes, account:thisAccount)
parameters.authority = authority
parameters.loginHint = "username"

// Acquire token silent
self.applicationContext.acquireTokenSilent(with: parameters) { (result, error) in
    if let error = error {
        
        let nsError = error as NSError
        
        // interactionRequired means we need to ask the user to sign in. This usually happens
        // when the user's Refresh Token is expired or if the user has changed their password
        // among other possible reasons.
        
        if (nsError.domain == MSALErrorDomain) {
            
            if (nsError.code == MSALError.interactionRequired.rawValue) {
                
                // Start an interactive authorization code
                // Notice we supply the account here. This ensures we acquire token for the same account
                // as we originally authenticated.
                
                ...
            }
        }
        
        self.updateLoggingText(text: "Could not acquire token: \(error)")
        return
    }
    
    guard let result = result else {
        
        self.updateLoggingText(text: "Could not acquire token: No result returned")
        return
    }
    
    // On success, set the access token to the accessToken class member. 
    // The callGraphAPI method uses the access token to call a web API  
    self.accessToken = result.accessToken
    ...
}
} catch {
self.updateLoggingText(text: "Unable to construct parameters before calling acquire token \(error)")
}

该方法 callGraphAPI 检索访问令牌并调用 Web API,如下所示:

@objc func callGraphAPI(_ sender: UIButton) {
    guard let accessToken = self.accessToken else {
        self.updateLoggingText(text: "Operation failed because could not find an access token!")
        return
    }
    
    let sessionConfig = URLSessionConfiguration.default
    sessionConfig.timeoutIntervalForRequest = 30
    let url = URL(string: self.kGraphURI)
    var request = URLRequest(url: url!)
    request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
    let urlSession = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)
    
    self.updateLoggingText(text: "Calling the API....")
    
    urlSession.dataTask(with: request) { data, response, error in
        guard let validData = data else {
            self.updateLoggingText(text: "Could not call API: \(error ?? "No error information" as! Error)")
            return
        }
        
        let result = try? JSONSerialization.jsonObject(with: validData, options: [])
        
        guard let validResult = result as? [String: Any] else {
            self.updateLoggingText(text: "Nothing returned from API")
            return
        }
        
        self.updateLoggingText(text: "API response: \(validResult.debugDescription)")
        }.resume()
}

步骤 6.4:注销用户

使用 MSAL 注销会从应用程序中删除有关用户的所有已知信息。 使用注销方法注销用户并更新 UI。 例如,可以隐藏受保护的 UI 元素、隐藏注销按钮或显示登录按钮。

以下代码片段演示如何注销用户:

@objc func signoutButton(_ sender: UIButton) {
do {
    
    
    let thisAccount = try self.getAccountByPolicy(withAccounts: applicationContext.allAccounts(), policy: kSignupOrSigninPolicy)
    
    if let accountToRemove = thisAccount {
        try applicationContext.remove(accountToRemove)
    } else {
        self.updateLoggingText(text: "There is no account to signing out!")
    }
    
    ...
    
} catch  {
    self.updateLoggingText(text: "Received error signing out: \(error)")
}
}

后续步骤

学习如何做到: