將應用程式遷移到 iOS 和 macOS 的 MSAL

Azure Active Directory 認證函式庫(ADAL Objective-C)是為了透過 v1.0 端點支援 Microsoft Entra 帳號而建立的。

適用於 iOS 和 macOS 的 Microsoft Authentication Library(MSAL)旨在透過 Microsoft 身分識別平台(先前稱為 Azure AD v2.0 端點)搭配所有 Microsoft 身分運作,例如 Microsoft Entra 帳戶、個人 Microsoft 帳戶和 Azure AD B2C 帳戶。

Microsoft 身分識別平台 與 Azure AD v1.0 有幾個主要差異。 本文強調這些差異,並提供從 ADAL 遷移應用程式到 MSAL 的指引。

ADAL 與 MSAL 應用程式功能差異

誰可以登入

  • ADAL 僅支援工作及學校帳號——也就是 Microsoft Entra 帳號。
  • MSAL 支援個人Microsoft帳號(MSA)帳號,如 Hotmail.com、Outlook.com 和 Live.com。
  • MSAL 支援工作與學校帳號,以及 Azure AD B2C 帳號。

標準合規性

  • Microsoft 身分識別平台 遵循 OAuth 2.0 與 OpenId Connect 標準。
  • Microsoft 身分識別平台可讓您動態要求權限。 應用程式只能在需要時請求權限,並根據應用程式需要時請求更多權限。 欲了解更多資訊,請參閱 權限與同意。

ADAL 與 MSAL 函式庫的差異

MSAL 公開 API 反映了 Azure AD v1.0 與 Microsoft 身分識別平台 之間的幾個關鍵差異。

MSALPublicClientApplication 取代 ADAuthenticationContext

ADAuthenticationContext 是 ADAL 應用程式建立的第一個物件。 它是 ADAL 的一個實例。 應用程式會為每個 Microsoft Entra 雲端與租戶(權限)組合建立一個新的實例ADAuthenticationContext。 同樣 ADAuthenticationContext 的方法也可以用來取得多個公共客戶端應用程式的代幣。

在 MSAL 中,主要互動是透過一個 MSALPublicClientApplication 物件進行,該物件是以 OAuth 2.0 公共客戶端為模型。 一個實例MSALPublicClientApplication可用於與多個 Microsoft Entra 雲端及租戶互動,無需為每個權限建立新實例。 對大多數應用程式來說,一個 MSALPublicClientApplication 實例就足夠了。

範圍取代資源

在 ADAL 中,應用程式必須提供資源識別碼,例如https://graph.microsoft.com從 Azure AD v1.0 端點取得權杖。 資源可以在應用程式清單中定義多個範圍,或 oAuth2Permissions 並理解這些權限。 這讓用戶端應用程式能向該資源請求特定範圍的令牌,這些範圍在應用程式註冊時已預先定義。

在 MSAL 中,應用程式針對每個請求提供的是一組範圍,而不是單一的資源識別碼。 作用域是一個資源識別碼,後接一個權限名稱,格式為 resource/permission。 例如, https://graph.microsoft.com/user.read

在 MSAL 中,有兩種方式可提供範圍:

  • 提供一份應用程式所需的所有權限清單。 例如:

    @[@"https://graph.microsoft.com/directory.read", @"https://graph.microsoft.com/directory.write"]

    在這種情況下,應用程式會請求 directory.read and directory.write 權限。 如果使用者先前未曾針對此應用程式同意授予這些權限,系統會要求使用者同意。 應用程式也可能獲得使用者已同意的額外權限。 使用者只會被提示同意新權限或尚未授予的權限。

  • /.default瞄準鏡。

這是每個應用程式的內建範圍。 它指的是應用程式註冊時所設定的靜態權限清單。 其行為與 resource 的行為類似。 這在遷移時很有用,可以確保維持類似的範圍與使用者體驗。

要使用 /.default 範圍,請附加 /.default 到資源識別碼。 例如: https://graph.microsoft.com/.default 。 如果您的資源以斜線 (/) 結尾,您仍應附加 /.default,包括開頭的正斜線,因此產生的範圍中會包含雙正斜線 (//)。

你可以在 權限與範圍中閱讀更多關於如何使用「/.default」範圍的資訊。

支援不同的 WebView 類型與瀏覽器

ADAL 僅支援 iOS 版的 UIWebView/WKWebView,macOS 版則支援 WebView。 iOS 版 MSAL 在要求授權碼時支援更多顯示 Web 內容的選項,且不再支援 UIWebView;這可提升使用者體驗與安全性。

預設情況下,iOS 上的 MSAL 使用 ASWebAuthenticationSession,這是蘋果推薦用於 iOS 12+ 裝置認證的網頁元件。 它透過應用程式與 Safari 瀏覽器之間的 Cookie 分享,提供單一登入(SSO)的好處。

你可以根據應用程式需求和想要的最終使用者體驗,選擇使用不同的網頁元件。 更多選項請參閱 支援的網頁檢視類型 。

從 ADAL 移轉至 MSAL 時,WKWebView 可提供在 iOS 和 macOS 上與 ADAL 最相似的使用者體驗。 我們鼓勵你如果可能的話,遷移到 ASWebAuthenticationSession iOS 版本。 對於 macOS,我們鼓勵你使用 WKWebView.

帳號管理 API 的差異

當你呼叫 ADAL 方法 acquireToken() 或 acquireTokenSilent() 時,你會收到一個 ADUserInformation 物件,其中包含來自 id_token 的宣告清單,而 id_token 代表正在驗證的帳戶。 此外,ADUserInformation 會根據 upn 索賠傳回一個 userId。 在初次以互動方式取得權杖後,ADAL 期望開發人員在所有靜默呼叫中提供 userId。

ADAL 沒有提供 API 來擷取已知使用者身份。 它依賴應用程式來儲存和管理這些帳號。

MSAL 提供一組 API,方便列出所有 MSAL 已知的帳號,而無需取得憑證。

與 ADAL 類似,MSAL 會傳回帳戶資訊,其中包含來自 id_token 的宣告清單。 它是 MSALResult 物件內部的 MSALAccount 物件的一部分。

MSAL 提供一組 API 來移除帳號,讓被移除的帳號無法被應用程式存取。 帳號被移除後,後續的代幣獲取通話會提示用戶進行互動式代幣獲取。 帳號移除只會針對啟動該程式的用戶端應用程式,並不會從裝置上運行的其他應用程式或系統瀏覽器中移除該帳號。 這確保使用者即使登出單一應用程式,仍能持續在裝置上享有 SSO 體驗。

此外,MSAL 也會回傳一個帳號識別碼,之後可靜默請求令牌。 然而,帳號識別碼(透過 identifier 物件中的 MSALAccount 屬性存取)無法顯示,你無法推測它的格式,也不應嘗試解讀或解析它。

遷移帳號快取

從 ADAL 遷移時,應用程式通常會儲存 ADAL 的 userId,但其中沒有 MSAL 所需的 identifier。 作為一次性移轉步驟,應用程式可使用 ADAL 的 userId,透過下列 API 查詢 MSAL 帳戶:

- (nullable MSALAccount *)accountForUsername:(nonnull NSString *)username error:(NSError * _Nullable __autoreleasing * _Nullable)error;

此 API 會讀取 MSAL 與 ADAL 的快取,透過 ADAL 使用者 ID(UPN)尋找帳號。

如果帳號被找到,開發者應該用該帳號進行無聲代幣取得。 第一次無聲令牌取得將有效升級帳號,開發者會在 MSAL 結果中獲得一個相容 MSAL 帳號識別碼(identifier)。 之後,僅應透過以下 API 使用 identifier 進行帳號查詢:

- (nullable MSALAccount *)accountForIdentifier:(nonnull NSString *)identifier error:(NSError * _Nullable __autoreleasing * _Nullable)error;

雖然在 MSAL 中,所有作業都可以繼續使用 ADAL 的 userId,但由於 userId 是以 UPN 為基礎,因此會受到多項限制,因而導致不佳的使用者體驗。 例如,如果 UPN 變更,使用者必須重新登入。 我們建議所有應用程式都使用不可顯示帳號 identifier 來進行所有操作。

閱讀更多關於 快取狀態遷移的資訊。

代幣取得變更

MSAL 引入了一些代幣取得呼叫的變更:

  • 就像 ADAL 一樣, acquireTokenSilent 總是會產生無聲請求。
  • 與 ADAL 不同,acquireToken一律會產生可供使用者操作的 UI,無論是透過網頁檢視還是 Microsoft Authenticator 應用程式。 根據 webview/Microsoft Authenticator 中的 SSO 狀態,使用者可能會被要求輸入其憑證。
  • 在 ADAL 中,acquireToken 搭配 AD_PROMPT_AUTO 時,會先嘗試以靜默方式取得權杖,只有在靜默請求失敗時才會顯示 UI。 在 MSAL 中,這種邏輯可以透過先呼叫 acquireTokenSilent ,且只有 acquireToken 在靜默採集失敗時才呼叫來達成。 這讓開發者在開始互動式代幣取得前,能自訂使用者體驗。

錯誤處理差異

MSAL 能更清楚區分應用程式可處理的錯誤與需要使用者介入的錯誤。 開發者必須處理的錯誤數量有限:

  • MSALErrorInteractionRequired使用者必須進行互動式請求。 這可能由多種原因引起,例如認證會話過期、條件存取政策變更、刷新權杖過期或被撤銷、快取中無有效權杖等。
  • MSALErrorServerDeclinedScopes:該要求未完全完成,且部分權限範圍未獲授予存取權限。 這可能是因為使用者拒絕同意一個或多個範圍所致。

處理清單中MSALError其他所有錯誤則是可選的。 你可以利用這些錯誤中的資訊來改善使用者體驗。

關於 MSAL 錯誤處理的更多資訊,請參閱使用 MSAL 處理例外與錯誤 。

經紀人支援

MSAL 從 0.3.0 版本開始,提供使用 Microsoft Authenticator 應用程式的經紀式認證支援。 Microsoft Authenticator 也支援條件存取(Conditional Access)情境。 條件存取情境的例子包括裝置合規政策,要求使用者透過 Intune 註冊裝置或向 Microsoft Entra ID 註冊以取得憑證。 以及行動應用程式管理(MAM)條件存取政策,這些政策要求應用程式在取得令牌前必須證明合規。

若要為您的應用程式啟用代理程式:

  1. 為應用程式註冊一個與代理相容的重定向 URI 格式。 代理商相容的重定向 URI 格式為 msauth.<app.bundle.id>://auth。 用你應用程式的套件 ID 來替換 <app.bundle.id> 。 如果你是從 ADAL 遷移而來,且你的應用程式原本就支援 broker,那麼你不需要另外做任何事。 你之前的重定向 URI 完全相容於 MSAL,所以你可以直接跳到第三步。

  2. 將應用程式的重定向 URI 方案加入你的 info.plist 檔案。 預設的 MSAL 重定向 URI 格式為 msauth.<app.bundle.id>。 例如:

    <key>CFBundleURLSchemes</key>
    <array>
        <string>msauth.<app.bundle.id></string>
    </array>
    
  3. 在您的應用程式 Info.plist 的 LSApplicationQueriesSchemes 項目下加入以下配置:

    <key>LSApplicationQueriesSchemes</key>
    <array>
         <string>msauthv2</string>
         <string>msauthv3</string>
    </array>
    
  4. 在您的 AppDelegate.m 檔案中新增以下內容以處理回撥:Objective-C:

    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options`
    {
        return [MSALPublicClientApplication handleMSALResponse:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]];
    }
    

    Swift:

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String)
    }
    

企業對企業 (B2B)

在 ADAL 裡,你會為每個租戶建立獨立的實例 ADAuthenticationContext ,應用程式會請求 token。 這在 MSAL 中已不再是必要條件。 在 MSAL 中,你可以建立單一實例MSALPublicClientApplication,並用於任何 Microsoft Entra 雲端及組織,只要指定不同的 acquireToken 和 acquireTokenSilent 呼叫權限。

SSO 與其他 SDK 合作

MSAL for iOS 可透過與 ADAL Objective-C 2.7.x+ 共用的統一快取來實現單點登入(SSO)。

SSO 是透過 iOS 的鑰匙串分享實現的,且僅能在同一 Apple 開發者帳號發佈的應用程式之間使用。

透過 iOS 的 SSO 鑰匙串分享是唯一無聲的 SSO 類型。

在 macOS 上,MSAL 可與其他以 MSAL for iOS 和 macOS 為基礎的應用程式,以及以 ADAL Objective-C 為基礎的應用程式達成單一登入(SSO)。

iOS 上的 MSAL 也支援另外兩種類型的 SSO:

  • 透過網頁瀏覽器進行單點登入(SSO)。 iOS 版 MSAL 支援 ASWebAuthenticationSession,透過裝置上其他應用程式及特別是 Safari 瀏覽器間共享的 Cookie 提供 SSO。
  • 透過驗證代理程式進行單一登入(SSO)。 在 iOS 裝置上,Microsoft Authenticator 扮演認證代理的角色。 它可以遵循條件存取政策,例如要求符合規範的裝置,並為註冊的裝置提供單點登入(SSO)。 從 0.3.0 版本開始的 MSAL SDK 預設支援代理。

Intune MAM SDK

Intune MAM SDK 從 11.1.2 版本起支援 iOS MSAL

MSAL 和 ADAL 在同一個應用程式裡

ADAL 2.7.0 版本及以上版本無法與 MSAL 在同一應用程式中共存。 主要原因是因為共享子模組的共通程式碼。 因為 Objective-C 不支援命名空間,如果你在應用程式中同時加入 ADAL 和 MSAL 框架,會產生兩個同一個類別的實例。 無法保證哪一個會在運行時被選中。 如果兩個 SDK 使用相同版本的衝突類別,你的應用程式可能仍然能正常運作。 不過,如果是不同版本,應用程式可能會遇到難以診斷的意外崩潰。

不支援在同一個生產應用程式中執行 ADAL 和 MSAL。 不過,如果你只是測試並遷移使用者從 ADAL Objective-C 到 MSAL for iOS 和 macOS,可以繼續使用 ADAL Objective-C 2.6.10。 這是唯一能在同一應用程式中支援 MSAL 的版本。 此 ADAL 版本不會有新功能更新,因此應僅用於遷移與測試用途。 你的應用程式不應該長期依賴 ADAL 和 MSAL 共存。

ADAL 和 MSAL 不支援在同一應用程式中共存。 多個應用程式間的 ADAL 與 MSAL 共存功能完全支援。

實際遷移步驟

應用程式註冊遷移

你不需要更改現有的 Microsoft Entra 應用程式就能切換到 MSAL 並啟用 Microsoft Entra 帳號。 不過,如果你的 ADAL 應用程式不支援代理驗證,你需要先註冊一個新的重定向 URI,才能切換到 MSAL。

重定向 URI 應為以下格式: msauth.<app.bundle.id>://auth。 用你應用程式的套件 ID 來替換 <app.bundle.id> 。 在 Microsoft Entra 系統管理中心 指定重定向 URI。

僅在 iOS 上,若要支援基於憑證的認證,需在您的應用程式及 Microsoft Entra 系統管理中心 中註冊一個額外的重定向 URI,格式如下:msauth://code/<broker-redirect-uri-in-url-encoded-form>。 例如, msauth://code/msauth.com.microsoft.mybundleId%3A%2F%2Fauth

我們建議所有應用程式都註冊兩個重定向 URI。

如果您想要新增對累加同意的支援,請在應用程式註冊中的 API 權限 索引標籤下,選取您的應用程式已設定要要求存取的 API 和權限。

如果你是從 ADAL 遷移過來,並且想同時支援 Microsoft Entra ID 和 MSA 帳號,你現有的應用程式註冊必須更新以支援兩者。 我們不建議你立即更新現有的生產應用程式,以支援 Microsoft Entra ID 和 MSA。 相反地,請建立另一個支援 Microsoft Entra ID 和 MSA 的客戶端 ID 進行測試,並在確認所有情境正常後,更新現有應用程式。

將 MSAL 加入你的應用程式

你可以使用你偏好的套件管理工具,將 MSAL SDK 加入你的應用程式。 詳細 說明請見此處。

更新你應用程式的 Info.plist 檔案

僅限 iOS 版本,請將應用程式的重定向 URI 方案加入 info.plist 檔案。 對於與 ADAL broker 相容的應用程式,它應該已經存在。 預設的 MSAL 重定向 URI 格式為: msauth.<app.bundle.id>。

<key>CFBundleURLSchemes</key>
<array>
    <string>msauth.<app.bundle.id></string>
</array>

在您的應用程式的 Info.plist 中,於 LSApplicationQueriesSchemes 下加入以下項目。

<key>LSApplicationQueriesSchemes</key>
<array>
     <string>msauthv2</string>
     <string>msauthv3</string>
</array>

更新你的 AppDelegate 代碼

僅限 iOS 版本,請在您的 AppDelegate.m 檔案中新增以下內容:

Objective-C:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options`
{
    return [MSALPublicClientApplication handleMSALResponse:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]];
}

Swift:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String)
}

如果你用的是 Xcode 11,應該把 MSAL 回調放進 SceneDelegate 檔案裡。 如果您支援 UISceneDelegate 和 UIApplicationDelegate 以與舊版 iOS 相容,則須在這兩個檔案中加入 MSAL 的回呼功能。

Objective-C:

 - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
 {
     UIOpenURLContext *context = URLContexts.anyObject;
     NSURL *url = context.URL;
     NSString *sourceApplication = context.options.sourceApplication;
     
     [MSALPublicClientApplication handleMSALResponse:url sourceApplication:sourceApplication];
 }

Swift:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        
        guard let urlContext = URLContexts.first else {
            return
        }
        
        let url = urlContext.url
        let sourceApp = urlContext.options.sourceApplication
        
        MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: sourceApp)
    }

這讓 MSAL 能處理來自代理者與網頁元件的回應。 在 ADAL 中不需要這麼做,因為它會自動對 App Delegate 方法進行「swizzle」。 手動新增的選項出錯率較低,且能讓應用程式有更多控制權。

啟用令牌快取

預設情況下,MSAL 會將你應用程式的代幣快取在 iOS 或 macOS 的鑰匙串中。

啟用代幣快取:

  1. 確保你的申請書有妥善簽署
  2. 前往你的 Xcode Project 設定>功能標籤,>啟用鑰匙串共享
  3. 點擊 + 並輸入以下 鑰匙圈群組 項目:3.a 若為 iOS,請輸入 com.microsoft.adalcache 3.b 若為 macOS,請輸入 com.microsoft.identity.universalstorage

建立 MSALPublicClientApplication,並改用其 acquireToken 和 acquireTokenSilent 呼叫

您可以使用以下程式碼建立 MSALPublicClientApplication :

Objective-C:

NSError *error = nil;
MSALPublicClientApplicationConfig *configuration = [[MSALPublicClientApplicationConfig alloc] initWithClientId:@"<your-client-id-here>"];
    
MSALPublicClientApplication *application =
[[MSALPublicClientApplication alloc] initWithConfiguration:configuration
                                                     error:&error];

Swift:

let config = MSALPublicClientApplicationConfig(clientId: "<your-client-id-here>")
do {
  let application = try MSALPublicClientApplication(configuration: config)
  // continue on with application
            
} catch let error as NSError {
  // handle error here
}

接著呼叫帳號管理 API,看看快取裡有沒有其他帳號:

Objective-C:

NSString *accountIdentifier = nil /*previously saved MSAL account identifier */;
NSError *error = nil;
MSALAccount *account = [application accountForIdentifier:accountIdentifier error:&error];

Swift:

// definitions that need to be initialized
let application: MSALPublicClientApplication!
let accountIdentifier: String! /*previously saved MSAL account identifier */

do {
  let account = try application.account(forIdentifier: accountIdentifier)
  // continue with account usage
} catch let error as NSError {
  // handle error here
}

或查看所有帳戶:

Objective-C:

NSError *error = nil;
NSArray<MSALAccount *> *accounts = [application allAccounts:&error];

Swift:

let application: MSALPublicClientApplication!
do {
  let accounts = try application.allAccounts()
  // continue with account usage
} catch let error as NSError {
  // handle error here
}

如果找到帳號,請呼叫 MSAL acquireTokenSilent API:

Objective-C:

MSALSilentTokenParameters *silentParameters = [[MSALSilentTokenParameters alloc] initWithScopes:@[@"<your-resource-here>/.default"] account:account];
    
[application acquireTokenSilentWithParameters:silentParameters
                              completionBlock:^(MSALResult *result, NSError *error)
{
    if (result)
    {
        NSString *accessToken = result.accessToken;
        // Use your token
    }
    else
    {
        // Check the error
        if ([error.domain isEqual:MSALErrorDomain] && error.code == MSALErrorInteractionRequired)
        {
            // Interactive auth will be required
        }
            
        // Other errors may require trying again later, or reporting authentication problems to the user
    }
}];

Swift:

let application: MSALPublicClientApplication!
let account: MSALAccount!
        
let silentParameters = MSALSilentTokenParameters(scopes: ["<your-resource-here>/.default"], 
                                                 account: account)
application.acquireTokenSilent(with: silentParameters) {
  (result: MSALResult?, error: Error?) in
  if let accessToken = result?.accessToken {
     // use accessToken
  }
  else {
    // Check the error
    guard let error = error else {
      assert(true, "callback should contain a valid result or error")
      return
    }
    
    let nsError = error as NSError
    if (nsError.domain == MSALErrorDomain
        && nsError.code == MSALError.interactionRequired.rawValue) {
      // Interactive auth will be required
    }
                
    // Other errors may require trying again later, or reporting authentication problems to the user
  }
}

下一步

了解更多關於認證流程與應用情境的資訊