教學課程:從iOS或macOS應用程式登入使用者並呼叫 Microsoft Graph
在本教學課程中,您會建置與 Microsoft 身分識別平台 整合的 iOS 或 macOS 應用程式,以簽署使用者並取得存取令牌來呼叫 Microsoft Graph API。
當您完成本教學課程時,您的應用程式會接受個人 Microsoft 帳戶的登入(包括 outlook.com、live.com 和其他帳戶),以及使用 Microsoft Entra ID 的任何公司或組織的工作或學校帳戶。 本教學課程適用於 iOS 和 macOS 應用程式。 這兩個平台之間的某些步驟不同。
在本教學課程中:
- 在 Xcode 中 建立 iOS 或 macOS 應用程式專案
- 在 Microsoft Entra 系統管理中心註冊應用程式
- 新增程式代碼以支援使用者登入和註銷
- 新增程式代碼以呼叫 Microsoft Graph API
- 測試應用程式
必要條件
教學課程應用程式的運作方式
本教學課程中的應用程式可以登入使用者,並代表他們從 Microsoft Graph 取得數據。 此數據是透過需要授權且受到 Microsoft 身分識別平台 保護的受保護 API 來存取。
更明確地說:
- 您的應用程式會透過瀏覽器或 Microsoft Authenticator 登入使用者。
- 使用者接受應用程式所要求的許可權。
- 您的應用程式會針對 Microsoft Graph API 發出存取令牌。
- 存取令牌包含在 Web API 的 HTTP 要求中。
- 處理 Microsoft Graph 回應。
此範例會使用 Microsoft 驗證連結庫 (MSAL) 來實作驗證。 MSAL 會自動更新令牌、在裝置上的其他應用程式之間傳遞單一登錄,以及管理帳戶。
如果您想要下載在本教學課程中建置之應用程式的完整版本,您可以在 GitHub 上找到這兩個版本:
- iOS 程式代碼範例 (GitHub)
- macOS 程式代碼範例 (GitHub)
建立新專案
- 開啟 Xcode,然後選取 [建立新的 Xcode 專案]。
- 針對 iOS 應用程式,選取 [iOS>單一檢視應用程式],然後選取 [下一步]。
- 針對 macOS 應用程式,選取 [macOS>Cocoa 應用程式],然後選取 [下一步]。
- 提供產品名稱。
- 將 [ 語言] 設定為 [Swift ],然後選取 [ 下一步]。
- 選取資料夾以建立您的應用程式,然後選取 [ 建立]。
註冊應用程式
提示
本文中的步驟可能會根據您從開始的入口網站稍有不同。
- 以至少應用程式開發人員身分登入 Microsoft Entra 系統管理中心。
- 如果您有多個租使用者的存取權,請使用頂端功能表中的 [設定] 圖示,切換至您想要從 [目錄 + 訂用帳戶] 功能表註冊應用程式的租使用者。
- 流覽至 [身分>識別應用程式> 應用程式註冊]。
- 選取新增註冊。
- 輸入應用程式的 [ 名稱 ]。 您的應用程式使用者可能會看到此名稱,而且您稍後可以加以變更。
- 在支持帳戶類型下,選取任何組織目錄中的 [帳戶] (任何 Microsoft Entra 目錄 - 多租使用者) 和個人 Microsoft 帳戶 (例如 Skype、Xbox)。
- 選取註冊。
- 在 [管理] 底下,選取 [驗證>][新增平臺>iOS/macOS]。
- 輸入專案的套件組合識別碼。 如果下載程式代碼範例,則套件組合識別碼為
com.microsoft.identitysample.MSALiOS
。 如果您要建立自己的專案,請在 Xcode 中選取您的項目,然後開啟 [ 一般 ] 索引標籤。套件組合標識碼會出現在 [ 身分 識別] 區段中。 - 選取 [設定],然後儲存 MSAL 組態出現在 [MSAL 組態] 頁面中,以便稍後設定應用程式時輸入它。
- 選取完成。
新增 MSAL
選擇下列其中一種方式,在您的應用程式中安裝 MSAL 連結庫:
CocoaPods \(英文\)
如果您使用 CocoaPods,請先在與專案的 .xcodeproj 檔案相同的資料夾中建立名為 podfile 的空白檔案來安裝
MSAL
。 將下列內容新增至 podfile:use_frameworks! target '<your-target-here>' do pod 'MSAL' end
將取代
<your-target-here>
為您的項目名稱。在終端機視窗中,流覽至包含您所建立並執行
pod install
Podfile 的資料夾,以安裝 MSAL 連結庫。關閉 Xcode 並開啟
<your project name>.xcworkspace
以在 Xcode 中重載專案。
迦太基
如果您使用 Carthage,請將它新增至 Cartfile 來安裝MSAL
:
github "AzureAD/microsoft-authentication-library-for-objc" "master"
從終端機視窗中,在與更新 的 Cartfile 相同的目錄中,執行下列命令,讓 Carthage 更新專案中的相依性。
iOS:
carthage update --platform iOS
macOS:
carthage update --platform macOS
手動
您也可以使用 Git Submodule,或查看最新版本,以作為應用程式中的架構使用。
新增應用程式註冊
接下來,我們會將您的應用程式註冊新增至您的程序代碼。
首先,將下列 import 語句新增至 ViewController.swift 檔案的頂端,以及 AppDelegate.swift 或 SceneDelegate.swift:
import MSAL
接下來,將下列程式代碼新增至 ViewController.swift ,再新增至 viewDidLoad()
:
// Update the below to your client ID. The below is for running the demo only
let kClientID = "Your_Application_Id_Here"
let kGraphEndpoint = "https://graph.microsoft.com/" // the Microsoft Graph endpoint
let kAuthority = "https://login.microsoftonline.com/common" // this authority allows a personal Microsoft account and a work or school account in any organization's Azure AD tenant to sign in
let kScopes: [String] = ["user.read"] // request permission to read the profile of the signed-in user
var accessToken = String()
var applicationContext : MSALPublicClientApplication?
var webViewParameters : MSALWebviewParameters?
var currentAccount: MSALAccount?
您修改的唯一值是指派給 kClientID
做為 應用程式識別碼的值。 此值是您在本教學課程開頭的步驟中儲存的 MSAL 組態數據的一部分,以註冊應用程式。
設定 Xcode 項目設定
將新的金鑰鏈群組新增至項目 簽署和功能。 密鑰鏈群組應該位於 com.microsoft.adalcache
iOS 和 com.microsoft.identity.universalstorage
macOS 上。
僅限 iOS,請設定 URL 配置
在此步驟中,您將註冊 CFBundleURLSchemes
,讓用戶可以在登入后重新導向回應用程式。 順便說一句, LSApplicationQueriesSchemes
也可讓您的應用程式使用 Microsoft Authenticator。
在 Xcode 中,開啟 Info.plist 作為原始碼檔案,並在 區段內 <dict>
新增下列內容。 將取代 [BUNDLE_ID]
為您先前使用的值。 如果您下載程式代碼,套件組合識別碼為 com.microsoft.identitysample.MSALiOS
。 如果您要建立自己的專案,請在 Xcode 中選取您的項目,然後開啟 [ 一般 ] 索引標籤。套件組合標識碼會出現在 [ 身分 識別] 區段中。
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>msauth.[BUNDLE_ID]</string>
</array>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>msauthv2</string>
<string>msauthv3</string>
</array>
僅限 macOS,設定應用程式沙箱
- 移至 Xcode 專案 設定 功能索引>標籤>應用程式沙箱
- 選取 [傳出 連線(用戶端) 複選框。
建立應用程式的UI
現在建立 UI,其中包含呼叫 Microsoft Graph API 的按鈕、另一個要註銷的按鈕,以及將下列程式代碼新增至 類別來查看一些輸出的 ViewController
文字檢視:
iOS UI
var loggingText: UITextView!
var signOutButton: UIButton!
var callGraphButton: UIButton!
var usernameLabel: UILabel!
func initUI() {
usernameLabel = UILabel()
usernameLabel.translatesAutoresizingMaskIntoConstraints = false
usernameLabel.text = ""
usernameLabel.textColor = .darkGray
usernameLabel.textAlignment = .right
self.view.addSubview(usernameLabel)
usernameLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
usernameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10.0).isActive = true
usernameLabel.widthAnchor.constraint(equalToConstant: 300.0).isActive = true
usernameLabel.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Add call Graph button
callGraphButton = UIButton()
callGraphButton.translatesAutoresizingMaskIntoConstraints = false
callGraphButton.setTitle("Call Microsoft Graph API", for: .normal)
callGraphButton.setTitleColor(.blue, for: .normal)
callGraphButton.addTarget(self, action: #selector(callGraphAPI(_:)), for: .touchUpInside)
self.view.addSubview(callGraphButton)
callGraphButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
callGraphButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 120.0).isActive = true
callGraphButton.widthAnchor.constraint(equalToConstant: 300.0).isActive = true
callGraphButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Add sign out button
signOutButton = UIButton()
signOutButton.translatesAutoresizingMaskIntoConstraints = false
signOutButton.setTitle("Sign Out", for: .normal)
signOutButton.setTitleColor(.blue, for: .normal)
signOutButton.setTitleColor(.gray, for: .disabled)
signOutButton.addTarget(self, action: #selector(signOut(_:)), for: .touchUpInside)
self.view.addSubview(signOutButton)
signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
signOutButton.topAnchor.constraint(equalTo: callGraphButton.bottomAnchor, constant: 10.0).isActive = true
signOutButton.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
signOutButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
let deviceModeButton = UIButton()
deviceModeButton.translatesAutoresizingMaskIntoConstraints = false
deviceModeButton.setTitle("Get device info", for: .normal);
deviceModeButton.setTitleColor(.blue, for: .normal);
deviceModeButton.addTarget(self, action: #selector(getDeviceMode(_:)), for: .touchUpInside)
self.view.addSubview(deviceModeButton)
deviceModeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
deviceModeButton.topAnchor.constraint(equalTo: signOutButton.bottomAnchor, constant: 10.0).isActive = true
deviceModeButton.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
deviceModeButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Add logging textfield
loggingText = UITextView()
loggingText.isUserInteractionEnabled = false
loggingText.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(loggingText)
loggingText.topAnchor.constraint(equalTo: deviceModeButton.bottomAnchor, constant: 10.0).isActive = true
loggingText.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0).isActive = true
loggingText.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true
loggingText.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 10.0).isActive = true
}
func platformViewDidLoadSetup() {
NotificationCenter.default.addObserver(self,
selector: #selector(appCameToForeGround(notification:)),
name: UIApplication.willEnterForegroundNotification,
object: nil)
}
@objc func appCameToForeGround(notification: Notification) {
self.loadCurrentAccount()
}
macOS UI
var callGraphButton: NSButton!
var loggingText: NSTextView!
var signOutButton: NSButton!
var usernameLabel: NSTextField!
func initUI() {
usernameLabel = NSTextField()
usernameLabel.translatesAutoresizingMaskIntoConstraints = false
usernameLabel.stringValue = ""
usernameLabel.isEditable = false
usernameLabel.isBezeled = false
self.view.addSubview(usernameLabel)
usernameLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 30.0).isActive = true
usernameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10.0).isActive = true
// Add call Graph button
callGraphButton = NSButton()
callGraphButton.translatesAutoresizingMaskIntoConstraints = false
callGraphButton.title = "Call Microsoft Graph API"
callGraphButton.target = self
callGraphButton.action = #selector(callGraphAPI(_:))
callGraphButton.bezelStyle = .rounded
self.view.addSubview(callGraphButton)
callGraphButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
callGraphButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
callGraphButton.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
// Add sign out button
signOutButton = NSButton()
signOutButton.translatesAutoresizingMaskIntoConstraints = false
signOutButton.title = "Sign Out"
signOutButton.target = self
signOutButton.action = #selector(signOut(_:))
signOutButton.bezelStyle = .texturedRounded
self.view.addSubview(signOutButton)
signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
signOutButton.topAnchor.constraint(equalTo: callGraphButton.bottomAnchor, constant: 10.0).isActive = true
signOutButton.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
signOutButton.isEnabled = false
// Add logging textfield
loggingText = NSTextView()
loggingText.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(loggingText)
loggingText.topAnchor.constraint(equalTo: signOutButton.bottomAnchor, constant: 10.0).isActive = true
loggingText.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0).isActive = true
loggingText.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true
loggingText.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -10.0).isActive = true
loggingText.widthAnchor.constraint(equalToConstant: 500.0).isActive = true
loggingText.heightAnchor.constraint(equalToConstant: 300.0).isActive = true
}
func platformViewDidLoadSetup() {}
接下來,在類別內 ViewController
,將 方法取代 viewDidLoad()
為:
override func viewDidLoad() {
super.viewDidLoad()
initUI()
do {
try self.initMSAL()
} catch let error {
self.updateLogging(text: "Unable to create Application Context \(error)")
}
self.loadCurrentAccount()
self.platformViewDidLoadSetup()
}
使用 MSAL
初始化 MSAL
在類別中 ViewController
,新增 initMSAL
方法:
func initMSAL() throws {
guard let authorityURL = URL(string: kAuthority) else {
self.updateLogging(text: "Unable to create authority URL")
return
}
let authority = try MSALAADAuthority(url: authorityURL)
let msalConfiguration = MSALPublicClientApplicationConfig(clientId: kClientID, redirectUri: nil, authority: authority)
self.applicationContext = try MSALPublicClientApplication(configuration: msalConfiguration)
self.initWebViewParams()
}
仍在類別中 ViewController
,並在 方法之後 initMSAL
,新增 initWebViewParams
方法:
iOS 程式代碼:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters(authPresentationViewController: self)
}
macOS 程式代碼:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters()
}
處理登入回呼 (僅限 iOS)
開啟 AppDelegate.swift 檔案。 若要在登入之後處理回呼,請將 新增 MSALPublicClientApplication.handleMSALResponse
至 appDelegate
類別,如下所示:
// Inside AppDelegate...
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.swift 中。 如果您支援UISceneDelegate和UIApplicationDelegate以與舊版iOS相容,MSAL回呼必須放入這兩個檔案中。
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)
}
取得令牌
現在,我們可以實作應用程式的UI處理邏輯,並透過MSAL以互動方式取得令牌。
MSAL 會公開兩個主要方法來取得令牌: acquireTokenSilently()
和 acquireTokenInteractively()
。
acquireTokenSilently()
只要帳戶存在,就會嘗試登入使用者並取得令牌,而不需要用戶互動。acquireTokenSilently()
需要有效的MSALAccount
,這可以使用其中一個 MSAL 的帳戶列舉 API 來擷取。 本教學課程使用applicationContext.getCurrentAccount(with: msalParameters, completionBlock: {})
來擷取目前的帳戶。acquireTokenInteractively()
嘗試登入使用者時,一律會顯示UI。 它可以在瀏覽器中使用會話 Cookie,或在 Microsoft 驗證器中使用帳戶來提供互動式 SSO 體驗。
將下列程式碼新增至 ViewController
類別:
func getGraphEndpoint() -> String {
return kGraphEndpoint.hasSuffix("/") ? (kGraphEndpoint + "v1.0/me/") : (kGraphEndpoint + "/v1.0/me/");
}
@objc func callGraphAPI(_ sender: AnyObject) {
self.loadCurrentAccount { (account) in
guard let currentAccount = account else {
// We check to see if we have a current logged in account.
// If we don't, then we need to sign someone in.
self.acquireTokenInteractively()
return
}
self.acquireTokenSilently(currentAccount)
}
}
typealias AccountCompletion = (MSALAccount?) -> Void
func loadCurrentAccount(completion: AccountCompletion? = nil) {
guard let applicationContext = self.applicationContext else { return }
let msalParameters = MSALParameters()
msalParameters.completionBlockQueue = DispatchQueue.main
applicationContext.getCurrentAccount(with: msalParameters, completionBlock: { (currentAccount, previousAccount, error) in
if let error = error {
self.updateLogging(text: "Couldn't query current account with error: \(error)")
return
}
if let currentAccount = currentAccount {
self.updateLogging(text: "Found a signed in account \(String(describing: currentAccount.username)). Updating data for that account...")
self.updateCurrentAccount(account: currentAccount)
if let completion = completion {
completion(self.currentAccount)
}
return
}
self.updateLogging(text: "Account signed out. Updating UX")
self.accessToken = ""
self.updateCurrentAccount(account: nil)
if let completion = completion {
completion(nil)
}
})
}
以互動方式取得令牌
下列代碼段會藉由建立 MSALInteractiveTokenParameters
物件並呼叫 acquireToken
,第一次取得令牌。 接下來,您會新增下列程序代碼:
MSALInteractiveTokenParameters
使用範圍建立 。- 使用已建立的參數呼叫
acquireToken()
。 - 處理錯誤。 如需詳細資訊,請參閱 適用於 iOS 和 macOS 的 MSAL 錯誤處理指南。
- 處理成功的案例。
將下列程式碼加入 ViewController
類別。
func acquireTokenInteractively() {
guard let applicationContext = self.applicationContext else { return }
guard let webViewParameters = self.webViewParameters else { return }
// #1
let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
parameters.promptType = .selectAccount
// #2
applicationContext.acquireToken(with: parameters) { (result, error) in
// #3
if let error = error {
self.updateLogging(text: "Could not acquire token: \(error)")
return
}
guard let result = result else {
self.updateLogging(text: "Could not acquire token: No result returned")
return
}
// #4
self.accessToken = result.accessToken
self.updateLogging(text: "Access token is \(self.accessToken)")
self.updateCurrentAccount(account: result.account)
self.getContentWithToken()
}
}
promptType
的 MSALInteractiveTokenParameters
屬性會設定驗證和同意提示行為。 支援下列值:
.promptIfNecessary
(預設值) - 只有在需要時,才會提示使用者。 SSO 體驗取決於 Webview 中是否有 Cookie,以及帳戶類型。 如果有多個使用者登入,則會顯示帳戶選擇體驗。 這是預設行為。.selectAccount
- 如果未指定任何使用者,驗證 Webview 會顯示目前已登入的帳戶清單,供用戶選取。.login
- 要求使用者在 Webview 中驗證。 如果您指定此值,一次只能登入一個帳戶。.consent
- 要求使用者同意要求目前的一組範圍。
以無訊息方式取得令牌
若要以無訊息方式取得更新的令牌,請將下列程式代碼新增至 ViewController
類別。 它會建立 MSALSilentTokenParameters
物件並呼叫 acquireTokenSilent()
:
func acquireTokenSilently(_ account : MSALAccount!) {
guard let applicationContext = self.applicationContext else { return }
/**
Acquire a token for an existing account silently
- forScopes: Permissions you want included in the access token received
in the result in the completionBlock. Not all scopes are
guaranteed to be included in the access token returned.
- account: An account object that we retrieved from the application object before that the
authentication flow will be locked down to.
- completionBlock: The completion block that will be called when the authentication
flow completes, or encounters an error.
*/
let parameters = MSALSilentTokenParameters(scopes: kScopes, account: account)
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) {
DispatchQueue.main.async {
self.acquireTokenInteractively()
}
return
}
}
self.updateLogging(text: "Could not acquire token silently: \(error)")
return
}
guard let result = result else {
self.updateLogging(text: "Could not acquire token: No result returned")
return
}
self.accessToken = result.accessToken
self.updateLogging(text: "Refreshed Access token is \(self.accessToken)")
self.updateSignOutButton(enabled: true)
self.getContentWithToken()
}
}
呼叫 Microsoft Graph API
一旦您有令牌,您的應用程式就可以在 HTTP 標頭中使用它,對 Microsoft Graph 提出授權要求:
標頭索引鍵 | value |
---|---|
授權 | <持有人存取令牌> |
將下列程式碼新增至 ViewController
類別:
func getContentWithToken() {
// Specify the Graph API endpoint
let graphURI = getGraphEndpoint()
let url = URL(string: graphURI)
var request = URLRequest(url: url!)
// Set the Authorization header for the request. We use Bearer tokens, so we specify Bearer + the token we got from the result
request.setValue("Bearer \(self.accessToken)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
self.updateLogging(text: "Couldn't get graph result: \(error)")
return
}
guard let result = try? JSONSerialization.jsonObject(with: data!, options: []) else {
self.updateLogging(text: "Couldn't deserialize result JSON")
return
}
self.updateLogging(text: "Result from Graph: \(result))")
}.resume()
}
若要深入瞭解 Microsoft Graph API,請參閱 Microsoft Graph API 。
使用 MSAL 進行註銷
接下來,新增註銷的支援。
重要
使用 MSAL 註銷會從應用程式移除使用者的所有已知資訊,以及在裝置設定允許時移除其裝置上的使用中會話。 您也可以選擇性地從瀏覽器註銷使用者。
若要新增註銷功能,請在 類別內 ViewController
新增下列程序代碼。
@objc func signOut(_ sender: AnyObject) {
guard let applicationContext = self.applicationContext else { return }
guard let account = self.currentAccount else { return }
do {
/**
Removes all tokens from the cache for this application for the provided account
- account: The account to remove from the cache
*/
let signoutParameters = MSALSignoutParameters(webviewParameters: self.webViewParameters!)
signoutParameters.signoutFromBrowser = false // set this to true if you also want to signout from browser or webview
applicationContext.signout(with: account, signoutParameters: signoutParameters, completionBlock: {(success, error) in
if let error = error {
self.updateLogging(text: "Couldn't sign out account with error: \(error)")
return
}
self.updateLogging(text: "Sign out completed successfully")
self.accessToken = ""
self.updateCurrentAccount(account: nil)
})
}
}
啟用令牌快取
根據預設,MSAL 會在 iOS 或 macOS 金鑰鏈中快取您應用程式的令牌。
若要啟用權杖快取:
- 確定您的應用程式已正確簽署
- 移至您的 Xcode 項目 設定 功能索引>標籤>[啟用金鑰鏈共用]
- 選取 + 並輸入下列 其中一個 Keychain 群組:
- Ios:
com.microsoft.adalcache
- macOS:
com.microsoft.identity.universalstorage
- Ios:
新增協助程式方法
將下列協助程式方法新增至 ViewController
類別,以完成範例。
iOS UI:
func updateLogging(text : String) {
if Thread.isMainThread {
self.loggingText.text = text
} else {
DispatchQueue.main.async {
self.loggingText.text = text
}
}
}
func updateSignOutButton(enabled : Bool) {
if Thread.isMainThread {
self.signOutButton.isEnabled = enabled
} else {
DispatchQueue.main.async {
self.signOutButton.isEnabled = enabled
}
}
}
func updateAccountLabel() {
guard let currentAccount = self.currentAccount else {
self.usernameLabel.text = "Signed out"
return
}
self.usernameLabel.text = currentAccount.username
}
func updateCurrentAccount(account: MSALAccount?) {
self.currentAccount = account
self.updateAccountLabel()
self.updateSignOutButton(enabled: account != nil)
}
macOS UI:
func updateLogging(text : String) {
if Thread.isMainThread {
self.loggingText.string = text
} else {
DispatchQueue.main.async {
self.loggingText.string = text
}
}
}
func updateSignOutButton(enabled : Bool) {
if Thread.isMainThread {
self.signOutButton.isEnabled = enabled
} else {
DispatchQueue.main.async {
self.signOutButton.isEnabled = enabled
}
}
}
func updateAccountLabel() {
guard let currentAccount = self.currentAccount else {
self.usernameLabel.stringValue = "Signed out"
return
}
self.usernameLabel.stringValue = currentAccount.username ?? ""
self.usernameLabel.sizeToFit()
}
func updateCurrentAccount(account: MSALAccount?) {
self.currentAccount = account
self.updateAccountLabel()
self.updateSignOutButton(enabled: account != nil)
}
僅限 iOS:取得其他裝置資訊
使用下列程式代碼來讀取目前的裝置設定,包括裝置是否已設定為共用:
@objc func getDeviceMode(_ sender: AnyObject) {
if #available(iOS 13.0, *) {
self.applicationContext?.getDeviceInformation(with: nil, completionBlock: { (deviceInformation, error) in
guard let deviceInfo = deviceInformation else {
self.updateLogging(text: "Device info not returned. Error: \(String(describing: error))")
return
}
let isSharedDevice = deviceInfo.deviceMode == .shared
let modeString = isSharedDevice ? "shared" : "private"
self.updateLogging(text: "Received device info. Device is in the \(modeString) mode.")
})
} else {
self.updateLogging(text: "Running on older iOS. GetDeviceInformation API is unavailable.")
}
}
多帳戶應用程式
此應用程式是針對單一帳戶案例所建置。 MSAL 也支援多帳戶案例,但需要更多應用程式工作。 您必須建立UI,以協助用戶選取想要用於每個需要令牌之動作的帳戶。 或者,您的應用程式可以實作啟發學習法,藉由從 MSAL 查詢所有帳戶來選取要使用的帳戶。 例如,請參閱 accountsFromDeviceForParameters:completionBlock:
API
測試您的應用程式
建置應用程式並將其部署至測試裝置或模擬器。 您應該能夠登入並取得 Microsoft Entra 識別碼或個人 Microsoft 帳戶的令牌。
使用者第一次登入您的應用程式時,Microsoft 身分識別會提示他們同意所要求的許可權。 雖然大部分的使用者都能夠同意,但某些 Microsoft Entra 租使用者已停用使用者同意,這需要系統管理員代表所有使用者同意。 若要支援此案例,請註冊您的應用程式範圍。
登入之後,應用程式會顯示從 Microsoft Graph /me
端點傳回的數據。
下一步
深入瞭解在多部分案例系列中建置可呼叫受保護Web API的行動應用程式。