Esercitazione: Accedere 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

Screenshot of how the sample app generated by this tutorial works.

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:

Crea un nuovo progetto

  1. Aprire Xcode e selezionare Create a new Xcode project.
  2. Per le app iOS selezionare iOS>App visualizzazione singola e quindi Avanti.
  3. Per le app macOS selezionare macOS>App Cocoa e scegliere Avanti.
  4. Specificare un nome di prodotto.
  5. Impostare il linguaggio su Swift e selezionare Avanti.
  6. 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.

  1. Accedere all'interfaccia di amministrazione di Microsoft Entra come almeno uno sviluppatore di applicazioni.
  2. 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.
  3. Passare a Applicazioni> di identità>Registrazioni app.
  4. Seleziona Nuova registrazione.
  5. Immettere un nome per l'applicazione. Tale nome, che potrebbe essere visualizzato dagli utenti dell'app, può essere modificato in un secondo momento.
  6. 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.
  7. Selezionare Registra.
  8. In Gestisci selezionare Autenticazione>Aggiungi una piattaforma>iOS/macOS.
  9. 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à .
  10. Selezionare Configura e salvare la Configurazione MSAL visualizzata nella pagina Configurazione MSAL in modo da poterla immettere durante la configurazione dell'app in seguito.
  11. Seleziona Fatto.

Aggiungere MSAL

Scegliere uno dei modi seguenti per installare la libreria MSAL nell'app:

CocoaPods

  1. 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
    
  2. Sostituire <your-target-here> con il nome del progetto.

  3. In una finestra del terminale passare alla cartella contenente il podfile creato ed eseguito pod install per installare la libreria MSAL.

  4. 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.

Xcode UI displaying how the keychain group should be set up.

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)

  1. Passare alla scheda>Sandbox app per progetti Xcode Impostazioni >
  2. 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 valido MSALAccountche può essere recuperato usando una delle API di enumerazione dell'account MSAL. Questa esercitazione usa applicationContext.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:

  1. Creare MSALInteractiveTokenParameters con ambiti.
  2. Chiamare acquireToken() con i parametri creati.
  3. Gestire gli errori. Per altri dettagli, vedere la guida per la gestione degli errori MSAL per iOS e macOS.
  4. 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:

  1. Assicurarsi che l'applicazione sia firmata correttamente
  2. Passare alla scheda>Funzionalità del progetto Xcode Impostazioni >Abilita condivisione keychain
  3. Selezionare + e immettere uno dei gruppi keychain seguenti:
    • iOS: com.microsoft.adalcache
    • macOS: com.microsoft.identity.universalstorage

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. Ad esempio, vedere accountsFromDeviceForParameters:completionBlock:API

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.