Esercitazione: Eseguire l'accesso degli 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 di account aziendali o dell'istituto di istruzione provenienti da 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 (in questo caso l'API Microsoft Graph) che richiede l'autorizzazione ed è protetta da Microsoft Identity Platform.
In particolare:
- L'app consente l'accesso all'utente tramite un browser o Microsoft Authenticator.
- L'utente finale accetterà le autorizzazioni richieste dall'applicazione.
- All'app viene fornito 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
- 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 vuole registrare l'applicazione dal 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 si scarica il codice di esempio, l'ID del pacchetto è
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 si usa CocoaPods, installare
MSAL
creando prima un file vuoto denominato podfile nella stessa cartella del file del progetto.xcodeproj. Aggiungere il codice seguente a 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 che contiene il file podfile creato ed eseguire
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, installare MSAL
aggiungendolo a Cartfile:
github "AzureAD/microsoft-authentication-library-for-objc" "master"
In una finestra del terminale, nella stessa directory del file aggiornato Cartfile, eseguire il comando seguente, affinché 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
Poi aggiungere la registrazione dell'app al codice.
Aggiungere prima l'istruzione import seguente nella parte superiore 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 da modificare è quello assegnato a kClientID
che deve corrispondere all’ 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 verrà eseguita la registrazione di 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 del codice sorgente e aggiungere quanto segue all'interno della sezione<dict>
. 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 a Impostazioni progetto Xcode >scheda Funzionalità>App Sandbox
- 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 altro per la disconnessione e una visualizzazione di testo per visualizzare l'output aggiungendo il codice seguente alla classe ViewController
:
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
Alla classe ViewController
aggiungere il metodo 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()
}
Sempre nella classe ViewController
e dopo il metodo initMSAL
aggiungere il metodo initWebViewParams
:
Codice iOS:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters(authPresentationViewController: self)
}
Codice macOS:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters()
}
Gestire il callback di accesso (solo per 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 si usa Xcode 11, è invece necessario 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 eseguire l’accesso di un utente e di ottenere i token senza alcuna interazione dell'utente, purché sia presente un account.acquireTokenSilently()
richiede unMSALAccount
valido, che può essere recuperato tramite 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
. Successivamente aggiungere 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 a Impostazioni progetto Xcode >scheda Funzionalità>Abilita condivisione keychain
- Selezionare + e immettere uno dei seguenti Gruppi di keychain:
- 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 per 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 operazioni aggiuntive dalle applicazioni. È necessario creare un'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'accountsFromDeviceForParameters:completionBlock:
Testare l'app
Compilare e distribuire l'app in un dispositivo di test o un simulatore. Ora è possibile effettuare l’accesso e ottenere i token per gli account Microsoft Entra ID o Microsoft personali.
La prima volta che un utente accede all'app, Microsoft Identity gli richiederà di fornire il consenso alle autorizzazioni richieste. Anche se la maggior parte degli utenti può fornire il consenso, alcuni tenant di Microsoft Entra hanno disabilitato il consenso dell'utente, il che richiede che siano gli amministratori a 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.