Esercitazione: Concedere l'accesso agli utenti e chiamare Microsoft Graph da un'app iOS o macOS
In questa esercitazione si creerà un'app iOS o macOS che si integra con Microsoft Identity Platform per consentire l'accesso degli utenti e ottenere un token di accesso per chiamare l'API Microsoft Graph.
Dopo aver completato l'esercitazione, l'applicazione accetta gli accessi di account Microsoft personali (inclusi outlook.com, live.com e altri) e account aziendali o dell'istituto di istruzione di qualsiasi azienda o organizzazione che usa Microsoft Entra ID. Questa esercitazione è applicabile alle app iOS e macOS. Alcuni passaggi sono diversi tra le due piattaforme.
Contenuto dell'esercitazione:
- Creare un nuovo progetto di app iOS o macOS in Xcode
- Registrare l'app nell'interfaccia di amministrazione di Microsoft Entra
- Aggiungere il codice per supportare l'accesso e la disconnessione
- Aggiungere il codice per chiamare l'API Microsoft Graph
- Testare l'app
Prerequisiti
Funzionamento dell'app dell'esercitazione
L'app in questa esercitazione consentirà agli utenti di accedere e recuperare i dati da Microsoft Graph per loro conto. Questi dati sono accessibili tramite un'API protetta (API Microsoft Graph in questo caso) che richiede l'autorizzazione ed è protetta da Microsoft Identity Platform.
In particolare:
- L'app accede all'utente tramite un browser o Microsoft Authenticator.
- L'utente finale accetta le autorizzazioni richieste dall'applicazione.
- L'app viene emessa un token di accesso per l'API Microsoft Graph.
- Il token di accesso è incluso nella richiesta HTTP all'API Web.
- Elaborare la risposta di Microsoft Graph.
Questo esempio usa Microsoft Authentication Library (MSAL) per implementare l'autenticazione. MSAL rinnoverà automaticamente i token, distribuirà l'accesso Single Sign-On (SSO) tra le altre app nel dispositivo e gestirà gli account.
Per scaricare una versione completa dell'app creata in questa esercitazione, è possibile trovare entrambe le versioni in GitHub:
- Esempio di codice iOS (GitHub)
- Esempio di codice macOS (GitHub)
Crea un nuovo progetto
- Aprire Xcode e selezionare Create a new Xcode project.
- Per le app iOS selezionare iOS>App visualizzazione singola e quindi Avanti.
- Per le app macOS selezionare macOS>App Cocoa e scegliere Avanti.
- Specificare un nome di prodotto.
- Impostare il linguaggio su Swift e selezionare Avanti.
- Selezionare una cartella per creare l'app e fare clic su Create (Crea).
Registrare l'applicazione
Suggerimento
I passaggi descritti in questo articolo possono variare leggermente in base al portale da cui si inizia.
- Accedere all'Interfaccia di amministrazione di Microsoft Entra almeno come sviluppatore di applicazioni.
- Se si ha accesso a più tenant, usare l’icona Impostazioni nel menu in alto per passare al tenant in cui si desidera registrare l’applicazione nel menu Directory e sottoscrizioni.
- Passare a Identità>Applicazioni>Registrazioni app.
- Seleziona Nuova registrazione.
- Immettere un nome per l'applicazione. Tale nome, che potrebbe essere visualizzato dagli utenti dell'app, può essere modificato in un secondo momento.
- Selezionare Account in qualsiasi directory organizzativa (qualsiasi directory Di Microsoft Entra - Multi-tenant) e account Microsoft personali (ad esempio Skype, Xbox) in Tipi di account supportati.
- Selezionare Registra.
- In Gestisci selezionare Autenticazione>Aggiungi una piattaforma>iOS/macOS.
- Immettere l'ID del bundle del progetto. Se è stato scaricato l'esempio di codice, l'ID bundle è
com.microsoft.identitysample.MSALiOS
. Se si sta creando un progetto personalizzato, selezionare il progetto in Xcode e aprire la scheda Generale . L'identificatore del bundle viene visualizzato nella sezione Identità . - Selezionare Configura e salvare la Configurazione MSAL visualizzata nella pagina Configurazione MSAL in modo da poterla immettere durante la configurazione dell'app in seguito.
- Selezionare Fatto.
Aggiungere MSAL
Scegliere uno dei modi seguenti per installare la libreria MSAL nell'app:
CocoaPods
Se usi CocoaPods, installa
MSAL
prima di tutto creando un file vuoto denominato podfile nella stessa cartella del file con estensione xcodeproj del progetto. Aggiungere quanto segue al podfile:use_frameworks! target '<your-target-here>' do pod 'MSAL' end
Sostituire
<your-target-here>
con il nome del progetto.In una finestra del terminale passare alla cartella contenente il podfile creato ed eseguito
pod install
per installare la libreria MSAL.Chiudere Xcode e aprire
<your project name>.xcworkspace
per ricaricare il progetto in Xcode.
Carthage
Se si usa Carthage, installarlo MSAL
aggiungendolo al cartfile:
github "AzureAD/microsoft-authentication-library-for-objc" "master"
Da una finestra del terminale, nella stessa directory del cartfile aggiornato, eseguire il comando seguente per fare in modo che Carthage aggiorni le dipendenze nel progetto.
iOS:
carthage update --platform iOS
macOS:
carthage update --platform macOS
Manualmente
È anche possibile usare Git Submodule o eseguire il checkout della versione più recente da usare come framework nell'applicazione.
Aggiungere la registrazione dell'app
Aggiungere quindi la registrazione dell'app al codice.
Aggiungere prima di tutto l'istruzione import seguente all'inizio del file ViewController.swift e AppDelegate.swift o SceneDelegate.swift:
import MSAL
Aggiungere quindi il codice seguente a ViewController.swift prima di :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?
L'unico valore modificato è il valore assegnato a kClientID
come ID applicazione. Questo valore fa parte dei dati di configurazione MSAL salvati durante il passaggio all'inizio di questa esercitazione per registrare l'applicazione.
Configurare le impostazioni del progetto Xcode
Aggiungere un nuovo gruppo di kaychain al progetto Signing & Capabilities. Il gruppo di keychain deve essere com.microsoft.adalcache
in iOS e com.microsoft.identity.universalstorage
in macOS.
Configurare gli schemi URL (solo per iOS)
In questo passaggio si eseguirà la registrazione CFBundleURLSchemes
in modo che l'utente possa essere reindirizzato all'app dopo l'accesso. LSApplicationQueriesSchemes
consente inoltre all'app di usare Microsoft Authenticator.
In Xcode aprire Info.plist come file di codice sorgente e aggiungere quanto segue all'interno della <dict>
sezione. Sostituire [BUNDLE_ID]
con il valore usato in precedenza. Se è stato scaricato il codice, l'identificatore del bundle è com.microsoft.identitysample.MSALiOS
. Se si sta creando un progetto personalizzato, selezionare il progetto in Xcode e aprire la scheda Generale . L'identificatore del bundle viene visualizzato nella sezione Identità .
<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>
Configurare Sandbox app (solo per macOS)
- Passare alla scheda Impostazioni >progetto Xcode (Sandbox>app)
- Selezionare la casella di controllo Connessioni in uscita (client).
Creare l'interfaccia utente dell'app
Creare ora un'interfaccia utente che include un pulsante per chiamare l'API Microsoft Graph, un'altra per disconnettersi e una visualizzazione testo per visualizzare un output aggiungendo il codice seguente alla ViewController
classe :
Interfaccia utente 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()
}
Interfaccia utente 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() {}
A questo punto, sempre all'interno della classe ViewController
, sostituire il metodo viewDidLoad()
con:
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()
}
Usare MSAL
Inizializzare MSAL
Aggiungere il initMSAL
metodo alla ViewController
classe :
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()
}
Sempre nella ViewController
classe e dopo il initMSAL
metodo aggiungere il initWebViewParams
metodo :
Codice iOS:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters(authPresentationViewController: self)
}
Codice macOS:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters()
}
Gestire il callback di accesso (solo iOS)
Aprire il file AppDelegate.swift . Per gestire il callback dopo l'accesso, aggiungere MSALPublicClientApplication.handleMSALResponse
alla classe appDelegate
nel modo seguente:
// 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)
}
Se usi Xcode 11, devi invece inserire il callback MSAL in SceneDelegate.swift . Se si supportano sia UISceneDelegate che UIApplicationDelegate per la compatibilità con le versioni precedenti di iOS, il callback MSAL deve essere inserito in entrambi i file.
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)
}
Acquisire i token
A questo punto, è possibile implementare la logica di elaborazione dell'interfaccia utente dell'applicazione e ottenere i token in modo interattivo tramite MSAL.
MSAL espone due metodi principali per ottenere i token: acquireTokenSilently()
e acquireTokenInteractively()
.
acquireTokenSilently()
tenta di accedere a un utente e ottenere token senza interazione dell'utente, purché sia presente un account.acquireTokenSilently()
richiede un oggetto validoMSALAccount
che può essere recuperato usando una delle API di enumerazione dell'account MSAL. Questa esercitazione usaapplicationContext.getCurrentAccount(with: msalParameters, completionBlock: {})
per recuperare l'account corrente.acquireTokenInteractively()
visualizza sempre l'interfaccia utente durante il tentativo di accesso dell'utente. Potrebbe usare i cookie di sessione nel browser o un account di Microsoft Authenticator per offrire un'esperienza di accesso SSO interattiva.
Aggiungere il codice seguente alla classe 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)
}
})
}
Ottenere un token in modo interattivo
Il frammento di codice seguente consente di recuperare un token per la prima volta creando un oggetto MSALInteractiveTokenParameters
e chiamando acquireToken
. Aggiungere quindi il codice seguente:
- Creare
MSALInteractiveTokenParameters
con ambiti. - Chiamare
acquireToken()
con i parametri creati. - Gestire gli errori. Per altri dettagli, vedere la guida per la gestione degli errori MSAL per iOS e macOS.
- Gestire il caso di esito positivo.
Aggiungere il codice seguente alla classe 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()
}
}
La proprietà promptType
di MSALInteractiveTokenParameters
consente di configurare il comportamento dell'autenticazione e della richiesta di consenso. Sono supportati i valori seguenti:
.promptIfNecessary
(impostazione predefinita): la richiesta utente viene visualizzata solo se necessario. L'esperienza di Single Sign-On è determinata dalla presenza di cookie nella webview e dal tipo di account. Se più utenti hanno eseguito l'accesso, viene visualizzata l'esperienza di selezione dell'account. Questo è il comportamento predefinito..selectAccount
: se non è specificato alcun utente, nella webview di autenticazione viene visualizzato un elenco degli account attualmente connessi che l'utente può selezionare..login
: richiede all'utente di eseguire l'autenticazione nella webview. Se si specifica questo valore, l'accesso è consentito a un solo account alla volta..consent
: richiede all'utente di dare il consenso per il set corrente di ambiti per la richiesta.
Ottenere un token in modo invisibile all'utente
Per acquisire un token aggiornato in modo invisibile all'utente, aggiungere il codice seguente alla classe ViewController
. Crea un oggetto MSALSilentTokenParameters
e chiama 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()
}
}
Chiamare l'API Microsoft Graph
Dopo aver ottenuto un token, l'app può usarlo nell'intestazione HTTP per effettuare una richiesta autorizzata a Microsoft Graph:
Chiave dell'intestazione | value |
---|---|
Autorizzazione | Bearer <access-token> |
Aggiungere il codice seguente alla classe 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()
}
Per altre informazioni sull'API Microsoft Graph, vedere API Microsoft Graph.
Usare MSAL per la disconnessione
A questo punto occorre aggiungere il supporto per la disconnessione.
Importante
La disconnessione con MSAL rimuove tutte le informazioni note su un utente da questa applicazione e rimuove anche una sessione attiva nel dispositivo dell'utente se consentito dalla configurazione del dispositivo. Facoltativamente, è anche possibile disconnettere l'utente dal browser.
Per aggiungere la funzionalità di disconnessione, aggiungere il codice seguente nella classe 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)
})
}
}
Abilitare la memorizzazione nella cache dei token
Per impostazione predefinita, MSAL memorizza nella cache i token dell'app nel keychain iOS o macOS.
Per abilitare la memorizzazione nella cache dei token:
- Assicurarsi che l'applicazione sia firmata correttamente
- Passare alla scheda Impostazioni progetto Xcode >Funzionalità>Abilita condivisione keychain
- Selezionare + e immettere uno dei gruppi keychain seguenti:
- iOS:
com.microsoft.adalcache
- macOS:
com.microsoft.identity.universalstorage
- iOS:
Aggiungere metodi helper
Per completare l'esempio, aggiungere i metodi helper seguenti alla classe ViewController
.
Interfaccia utente iOS:
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)
}
Interfaccia utente macOS:
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)
}
Solo iOS: ottenere informazioni aggiuntive sul dispositivo
Usare il codice seguente per eseguire la lettura della configurazione del dispositivo corrente e verificare se il dispositivo è configurato come condiviso:
@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.")
}
}
Applicazioni con più account
Questa app è stata sviluppata per uno scenario con un singolo account. MSAL supporta anche scenari con più account, ma richiede più applicazioni. È necessario creare l'interfaccia utente per aiutare gli utenti a selezionare l'account da usare per ogni azione che richiede token. In alternativa, l'app può implementare un'euristica per selezionare l'account da usare tramite una query su tutti gli account da MSAL. Vedere ad esempio l'API accountsFromDeviceForParameters:completionBlock:
Testare l'app
Compilare e distribuire l'app in un dispositivo di test o un simulatore. Dovrebbe essere possibile accedere e ottenere i token per gli account Microsoft Entra ID o Microsoft personali.
La prima volta che un utente accede all'app, verrà richiesto all'identità Microsoft di fornire il consenso alle autorizzazioni richieste. Sebbene la maggior parte degli utenti sia in grado di fornire il consenso, alcuni tenant di Microsoft Entra hanno disabilitato il consenso dell'utente, che richiede agli amministratori di fornire il consenso per conto di tutti gli utenti. Per supportare questo scenario, registrare gli ambiti dell'app.
Dopo l'accesso, l'app visualizzerà i dati restituiti dall'endpoint /me
di Microsoft Graph.
Passaggi successivi
Altre informazioni sulla creazione di app per dispositivi mobili che chiamano API Web protette nella serie di scenari in più parti.