Kurz: Přihlášení uživatelů v mobilní aplikaci pro iOS (Swift)
Toto je třetí kurz série kurzů, který vás provede přihlašováním uživatelů pomocí Microsoft Entra ID.
Než začnete, pomocí selektoru Zvolte typ nájemce v horní části této stránky vyberte typ nájemce. Microsoft Entra ID poskytuje dvě konfigurace tenantů, pracovní síly a externí. Konfigurace tenanta pracovních sil je určená pro zaměstnance, interní aplikace a další organizační prostředky. Externí tenant je určený pro vaše aplikace určené pro zákazníky.
V tomto kurzu:
- Přihlaste se uživatele.
- Odhlaste se od uživatele.
- Vytvoření uživatelského rozhraní aplikace
Požadavky
Přihlášení uživatele
Máte dvě hlavní možnosti pro přihlašování uživatelů pomocí knihovny MICROSOFT Authentication Library (MSAL) pro iOS: interaktivní nebo tiché získání tokenů.
Pokud se chcete přihlásit uživatele interaktivně, použijte následující kód:
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() } }
Vlastnost
promptType
v rámciMSALInteractiveTokenParameters
konfiguruje chování výzvy k ověření a vyjádření souhlasu. Podporují se následující hodnoty:-
.promptIfNecessary
(výchozí) – Uživateli se zobrazí výzva pouze v případě potřeby. Prostředí SSO je určeno přítomností souborů cookie ve webovém zobrazení a typem účtu. Pokud je přihlášeno více uživatelů, zobrazí se prostředí pro výběr účtu. Toto je výchozí chování. -
.selectAccount
– Pokud není zadaný žádný uživatel, zobrazí ověřovací webové zobrazení seznam aktuálně přihlášených účtů, ze které má uživatel vybírat. -
.login
– Vyžaduje, aby se uživatel ověřil ve webovém zobrazení. Pokud tuto hodnotu zadáte, může být současně přihlášen pouze jeden účet. -
.consent
– Vyžaduje, aby uživatel souhlasil s aktuální sadou oborů pro požadavek.
-
Pokud chcete uživatele přihlásit bezobslužně, použijte následující kód:
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() } }
Metoda
acquireTokenSilently
se pokusí bezobslužně získat přístupový token pro existující účet MSAL. PoužíváapplicationContext
k vyžádání tokenu se zadanými rozsahy. Pokud dojde k chybě, zkontroluje, jestli je vyžadována interakce uživatele, a pokud ano, zahájí interaktivní získání tokenu. Po úspěšném dokončení aktualizuje přístupový token, zaznamená výsledek, povolí tlačítko odhlášení a načte obsah pomocí tokenu.
Zpracujte zpětné volání přihlášení (pouze pro iOS)
Otevřete soubor AppDelegate.swift. Pokud chcete zpracovat zpětné volání po přihlášení, přidejte MSALPublicClientApplication.handleMSALResponse
do třídy appDelegate
takto:
// 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)
}
Pokud používáte Xcode 11, měli byste místo toho umístit zpětné volání MSAL do SceneDelegate.swift. Pokud podporujete JAK UISceneDelegate, tak UIApplicationDelegate kvůli kompatibilitě se starším iOSem, bude nutné zpětné volání MSAL umístit do obou souborů.
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)
}
Odhlásit uživatele
Důležitý
Odhlášení pomocí MSAL odebere všechny známé informace o uživateli z aplikace a také odstraní aktivní relaci na zařízení, pokud to umožní konfigurace zařízení. Uživatele můžete také volitelně odhlásit z prohlížeče.
Pokud chcete přidat funkci odhlášení, přidejte do třídy ViewController
následující kód.
@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)
})
}
}
Vytvoření uživatelského rozhraní aplikace
Teď vytvořte uživatelské rozhraní, které obsahuje tlačítko pro volání rozhraní Microsoft Graph API, jiného pro odhlášení a textové zobrazení, které zobrazí nějaký výstup přidáním následujícího kódu do třídy ViewController
:
Uživatelské rozhraní iOS
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()
}
uživatelské rozhraní macOS
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() {}
Dále také uvnitř třídy ViewController
nahraďte metodu viewDidLoad()
následujícím kódem:
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()
}
Další kroky
Návod: Volání chráněného webového rozhraní API v aplikaci pro iOS (Swift)
Toto je třetí kurz série kurzů, který vás provede přihlašováním uživatelů pomocí Microsoft Entra ID.
Než začnete, pomocí selektoru Zvolte typ nájemce v horní části této stránky vyberte typ nájemce. Microsoft Entra ID poskytuje dvě konfigurace tenantů, pracovní síly a externí. Konfigurace tenanta pracovních sil je určená pro zaměstnance, interní aplikace a další organizační prostředky. Externí tenant je určený pro vaše aplikace určené pro zákazníky.
V tomto kurzu:
- Přihlaste se uživatele.
- Odhlaste se od uživatele.
Požadavky
Přihlášení uživatele
Máte dvě hlavní možnosti pro přihlašování uživatelů pomocí knihovny MICROSOFT Authentication Library (MSAL) pro iOS: interaktivní nebo tiché získání tokenů.
Pokud se chcete přihlásit uživatele interaktivně, použijte následující kód:
acquireTokenInteractively() { guard let applicationContext = self.applicationContext else { return } guard let webViewParameters = self.webViewParameters else { return } updateLogging(text: "Acquiring token interactively...") let parameters = MSALInteractiveTokenParameters(scopes: Configuration.kScopes, webviewParameters: webViewParameters) parameters.promptType = .selectAccount applicationContext.acquireToken(with: parameters) { (result, error) in 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 } self.accessToken = result.accessToken self.updateLogging(text: "Access token is \(self.accessToken)") self.updateCurrentAccount(account: result.account) } }
Kód nejprve zkontroluje, jestli jsou k dispozici kontext aplikace a parametry webového zobrazení. Následně aktualizuje záznam, aby indikoval, že token získává interaktivně. Dále nastaví parametry pro interaktivní získání tokenu a určí rozsahy a parametry webového zobrazení. Nastaví také typ výzvy k výběru účtu.
Potom volá metodu
acquireToken
v kontextu aplikace s definovanými parametry. V obsluze dokončení kontroluje případné chyby. Pokud dojde k chybě, aktualizuje protokolování chybovou zprávou. V případě úspěchu načte přístupový token z výsledku, aktualizuje protokolování pomocí tokenu a aktualizuje aktuální účet.Jakmile vaše aplikace získá přístupový token, můžete načíst údaje spojené s aktuálním účtem. K tomu použijte následující fragment kódu:
let claims = result.account.accountClaims let preferredUsername = claims?["preferred_username"] as? String
Kód čte nároky z účtu přístupem k vlastnosti
accountClaims
objekturesult.account
. Potom načte hodnotu deklarace identity "preferred_username" ze slovníku deklarací identity a přiřadí ji k proměnnépreferredUsername
.Pokud chcete uživatele přihlásit bezobslužně, použijte následující kód:
func acquireTokenSilently() { self.loadCurrentAccount { (account) in guard let currentAccount = account else { self.updateLogging(text: "No token found, try to acquire a token interactively first") return } self.acquireTokenSilently(currentAccount) } }
Kód zahájí proces získání tokenů bezobslužně. Nejprve se pokusí načíst stávající účet. Pokud je nalezen aktuální účet, pokračuje se v tichém získání tokenu pomocí tohoto účtu. Pokud se nenajde žádný aktuální účet, aktualizuje protokolování tak, aby indikuje, že se nenašel žádný token, a navrhne se nejprve pokus o získání tokenu interaktivně.
V kódu výše voláme dvě funkce,
loadCurrentAccount
aacquireTokenSilently
. FunkceloadCurrentAccount
by měla mít následující kód:func loadCurrentAccount(completion: AccountCompletion? = nil) { guard let applicationContext = self.applicationContext else { return } let msalParameters = MSALParameters() msalParameters.completionBlockQueue = DispatchQueue.main // Note that this sample showcases an app that signs in a single account at a time 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.updateCurrentAccount(account: currentAccount) self.acquireTokenSilently(currentAccount) if let completion = completion { completion(self.currentAccount) } return } // If testing with Microsoft's shared device mode, see the account that has been signed out from another app. More details here: // https://docs.microsoft.com/azure/active-directory/develop/msal-ios-shared-devices if let previousAccount = previousAccount { self.updateLogging(text: "The account with username \(String(describing: previousAccount.username)) has been signed out.") } else { self.updateLogging(text: "") } self.accessToken = "" self.updateCurrentAccount(account: nil) if let completion = completion { completion(nil) } }) }
Kód používá MSAL pro iOS k načtení aktuálního účtu. Kontroluje chyby a odpovídajícím způsobem aktualizuje protokolování. Pokud se najde aktuální účet, aktualizuje ho a pokusí se získat tokeny bezobslužně. Pokud existuje předchozí účet, odhlásí se. Pokud nejsou nalezeny žádné účty, vymaže přístupový token. Nakonec provede blok dokončení, pokud je k dispozici.
Funkce
acquireTokenSilently
by měla obsahovat následující kód: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. */ updateLogging(text: "Acquiring token silently...") let parameters = MSALSilentTokenParameters(scopes: Configuration.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) } }
Tato funkce používá msAL pro iOS k tichému získání tokenu pro existující účet. Po ověření
applicationContext
protokoluje proces získání tokenu. PomocíMSALSilentTokenParameters
definuje nezbytné parametry. Pak se pokusí získat token tiše. Pokud dojde k chybám, zkontroluje požadavky na interakci uživatelů a v případě potřeby zahájí interaktivní proces. Po úspěchu aktualizuje vlastnostaccessToken
, zaznamená obnovený token a nakonec povolí tlačítko pro odhlášení.
Odhlásit uživatele
Pokud chcete odhlásit uživatele z aplikace pro iOS (Swift) pomocí MSAL, použijte následující kód:
@IBAction func signOut(_ sender: UIButton) {
guard let applicationContext = self.applicationContext else { return }
guard let account = self.currentAccount else { return }
guard let webViewParameters = self.webViewParameters else { return }
updateLogging(text: "Signing out...")
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: webViewParameters)
// If testing with Microsoft's shared device mode, trigger signout from browser. More details here:
// https://docs.microsoft.com/azure/active-directory/develop/msal-ios-shared-devices
if (self.currentDeviceMode == .shared) {
signoutParameters.signoutFromBrowser = true
} else {
signoutParameters.signoutFromBrowser = false
}
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)
})
}
}
Kód ověřuje existenci applicationContext
, currentAccount
a webViewParameters
. Potom protokoluje proces odhlášení. Kód odebere všechny tokeny z mezipaměti pro zadaný účet. V závislosti na aktuálním režimu zařízení určuje, jestli se má odhlásit z prohlížeče. Po dokončení aktualizuje protokolovací text odpovídajícím způsobem. Pokud během procesu odhlášení dojde k chybě, zaznamená chybovou zprávu. Po úspěšném odhlášení aktualizuje přístupový token na prázdný řetězec a vymaže aktuální účet.
Další kroky
Kurz: Volání chráněného webového rozhraní API v aplikaci pro iOS (Swift)