Tutorial: Inicio de sesión de los usuarios y llamada a Microsoft Graph desde una aplicación para iOS o macOS

En este tutorial creará una aplicación de iOS o macOS que se integra con la Plataforma de identidad de Microsoft para que los usuarios inicien sesión y obtengan un token de acceso para llamar a Microsoft Graph API.

Cuando haya completado el tutorial, la aplicación aceptará inicios de sesión de cuentas personales de Microsoft (incluidas Outlook.com, live.com y otras), y cuentas profesionales o educativas de cualquier empresa u organización que use Microsoft Entra ID. Este tutorial es aplicable a las aplicaciones para iOS y macOS. Algunos pasos son diferentes entre las dos plataformas.

En este tutorial, aprenderá a:

  • Crear un proyecto de aplicación de iOS o macOS en Xcode
  • Registro de la aplicación en el Centro de administración de Microsoft Entra
  • Agregar código para admitir el inicio y el cierre de sesión de usuario
  • Agregar código para llamar a Microsoft Graph API
  • Prueba de la aplicación

Prerrequisitos

Cómo funciona la aplicación tutorial

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

La aplicación de este tutorial puede iniciar la sesión de los usuarios y obtener datos de Microsoft Graph en su nombre. A estos datos se accede a través de una API protegida (Microsoft Graph API, en este caso) que requiere autorización y que está protegida por la plataforma de identidad de Microsoft.

Más concretamente:

  • La aplicación inicia la sesión del usuario mediante un explorador o Microsoft Authenticator.
  • El usuario final acepta los permisos que la aplicación solicitó.
  • Se emite un token de acceso a la aplicación para Microsoft Graph API.
  • El token de acceso se incluye en la solicitud HTTP a la API web.
  • Se procesa la respuesta de Microsoft Graph.

Este ejemplo utiliza la Biblioteca de autenticación de Microsoft (MSAL) para implementar la autenticación. MSAL renovará automáticamente los tokens, proporcionará el inicio de sesión único entre las otras aplicaciones del dispositivo y administrará las cuentas.

Si desea descargar una versión completa de la aplicación que va a crear en este tutorial, puede encontrar ambas versiones en GitHub:

Creación de un nuevo proyecto

  1. Abra Xcode y seleccione Create a new Xcode project (Crear un nuevo proyecto en Xcode).
  2. En el caso de las aplicaciones para iOS, seleccione iOS>Aplicación de vista única y seleccione Siguiente.
  3. En el caso de las aplicaciones para macOS, seleccione macOS>Aplicación de Cocoa y seleccione Siguiente.
  4. Especifique un nombre de producto.
  5. En Lenguaje, seleccione Swift y seleccione Siguiente.
  6. Seleccione una carpeta para crear la aplicación y elija Crear.

Registro de la aplicación

Sugerencia

Los pasos de este artículo pueden variar ligeramente en función del portal desde donde comienza.

  1. Inicie sesión en el Centro de administración de Microsoft Entra al menos como Desarrollador de aplicaciones.
  2. Si tiene acceso a varios inquilinos, use el icono Configuración del menú superior para cambiar al inquilino en el que desea registrar la aplicación desde el menú Directorios y suscripciones.
  3. Vaya aIdentidad>Aplicaciones>Registros de aplicaciones.
  4. Seleccione Nuevo registro.
  5. Escriba el nombre de la aplicación. Los usuarios de la aplicación pueden ver este nombre, el cual se puede cambiar más tarde.
  6. En Tipos de cuenta admitidos, seleccione Cuentas en cualquier directorio organizativo (cualquier directorio de Microsoft Entra: multiinquilino) y cuentas de Microsoft personales (como Skype o Xbox).
  7. Seleccione Registrar.
  8. En Administrar, seleccione Autenticación>Agregar una plataforma>iOS/macOS.
  9. Escriba el identificador de agrupación del proyecto. Si descargó el ejemplo de código, el id. de agrupación es com.microsoft.identitysample.MSALiOS. Si va a crear su propio proyecto, seleccione el proyecto en Xcode y abra la pestaña General. El identificador de la agrupación aparece en la sección Identidad.
  10. Seleccione Configurar y guarde la configuración de MSAL que aparece en la página Configuración de MSAL para que pueda especificarla cuando configure la aplicación más tarde.
  11. Seleccione Listo.

Adición de MSAL

Elija una de las siguientes formas de instalar la biblioteca de MSAL en su aplicación:

CocoaPods

  1. Si usa CocoaPods, instale MSAL, para lo que primero debe crear un archivo vacío llamado podfile en la misma carpeta que el archivo .xcodeproj del proyecto. Agregue lo siguiente a podfile:

    use_frameworks!
    
    target '<your-target-here>' do
       pod 'MSAL'
    end
    
  2. Reemplace <your-target-here> por el nombre del proyecto.

  3. En una ventana de terminal, vaya a la carpeta que contiene el archivo podfile que ha creado y ejecute pod install para instalar la biblioteca MSAL.

  4. Cierre Xcode y abra <your project name>.xcworkspace para volver a cargar el proyecto en Xcode.

Carthage

Si usa Carthage, instale MSAL, para lo que debe agregarlo a su Cartfile:

github "AzureAD/microsoft-authentication-library-for-objc" "master"

En una ventana de terminal, en el mismo directorio que el archivo Cartfile actualizado, ejecute el siguiente comando para que Carthage actualice las dependencias del proyecto.

iOS:

carthage update --platform iOS

macOS:

carthage update --platform macOS

Manualmente

También puede usar un submódulo de Git o consultar la versión más reciente y usarla como marco en su aplicación.

Agregue la fecha de registro de la aplicación.

A continuación, se agrega el registro de aplicaciones al código.

En primer lugar, agregue la siguiente instrucción import en la parte superior del archivo ViewController.swift y AppDelegate.swift o SceneDelegate.swift:

import MSAL

A continuación, agregue el código siguiente a ViewController.swift antes de a 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?

El único valor que debe modificar es el asignado a kClientID como Identificador de aplicación. Este valor forma parte de los datos de Configuración de MSAL que guardó en el paso del principio de este tutorial para registrar la aplicación.

Configuración de los valores del proyecto de Xcode

Agregue un nuevo grupo de cadena de claves al proyecto Signing & Capabilities (Firma y funcionalidades). El grupo de cadena de claves debe ser com.microsoft.adalcache en iOS y com.microsoft.identity.universalstorage en macOS.

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

Solo para iOS, configure los esquemas de dirección URL.

En este paso, registrará CFBundleURLSchemes para que se pueda redirigir al usuario a la aplicación después de iniciar sesión. Por cierto, LSApplicationQueriesSchemes también permite a la aplicación hacer uso de Microsoft Authenticator.

En Xcode, abra Info.plist como un archivo de código fuente y agregue lo siguiente en la sección <dict>. Reemplace [BUNDLE_ID] por el valor que usó anteriormente. Si descargó el código, el identificador de la agrupación es com.microsoft.identitysample.MSALiOS. Si va a crear su propio proyecto, seleccione el proyecto en Xcode y abra la pestaña General. El identificador de la agrupación aparece en la sección Identidad.

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

Solo para macOS, configure App Sandbox.

  1. Vaya a la configuración del proyecto de Xcode >pestaña Funcionalidades>App Sandbox
  2. Active la casilla Conexiones salientes (cliente) .

Creación de la interfaz de usuario de la aplicación

Ahora cree una interfaz de usuario que incluya un botón para llamar a Microsoft Graph API, otro para cerrar la sesión y una vista de texto para parte de la salida agregando el siguiente código a la clase ViewController:

Interfaz de usuario de 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()
}

Interfaz de usuario de 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 continuación, también en la clase ViewController, reemplace el método viewDidLoad() por:

    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()
    }

Uso de MSAL

Inicializar MSAL

Agregue el método initMSAL a la clase ViewController:

    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()
    }

Aún en la clase ViewController y después del método initMSAL, agregue el método initWebViewParams:

Código para iOS:

func initWebViewParams() {
        self.webViewParameters = MSALWebviewParameters(authPresentationViewController: self)
    }

Código para macOS:

func initWebViewParams() {
        self.webViewParameters = MSALWebviewParameters()
    }

Control de la devolución de llamada de inicio de sesión (solo iOS)

Abra el archivo AppDelegate.swift. Para controlar la devolución de llamada después de iniciar sesión, agregue MSALPublicClientApplication.handleMSALResponse a la clase appDelegate de la siguiente forma:

// 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)
}

Si usa Xcode 11, debe colocar la devolución de llamada de MSAL en SceneDelegate.swift en su lugar. Si admite UISceneDelegate y UIApplicationDelegate para la compatibilidad con sistemas operativos iOS anteriores, la devolución de llamada de MSAL debe colocarse en ambos archivos.

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)
    }

Adquisición de tokens

Ahora, podemos implementar la lógica de procesamiento de la interfaz de usuario de la aplicación y obtener los tokens interactivamente a través de MSAL.

MSAL expone dos métodos principales para obtener los tokens: acquireTokenSilently() y acquireTokenInteractively().

  • acquireTokenSilently() intenta iniciar la sesión de un usuario y obtener tokens sin interacción del usuario, siempre que haya una cuenta. acquireTokenSilently() requiere un valor de MSALAccount válido, que se puede recuperar mediante una de las API de enumeración de cuentas de MSAL. Este tutorial usa applicationContext.getCurrentAccount(with: msalParameters, completionBlock: {}) para recuperar la cuenta actual.

  • acquireTokenInteractively() siempre muestra la interfaz de usuario al intentar iniciar la sesión del usuario. Puede utilizar las cookies de sesión en el explorador o una cuenta de Microsoft Authenticator para proporcionar una experiencia de inicio de sesión único interactivo.

Agregue el siguiente código a la clase 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)
            }
        })
    }

Obtención de un token interactivamente

El siguiente fragmento de código obtiene un token por primera vez mediante la creación de un objeto MSALInteractiveTokenParameters y la llamada a acquireToken. A continuación, agrega código que:

  1. Crea MSALInteractiveTokenParameters con ámbitos.
  2. Llama a acquireToken() con los parámetros creados.
  3. Controla los errores. Para más información, consulte la guía de control de errores de MSAL para iOS y macOS.
  4. Controla el caso de éxito.

Agregue el siguiente código a la clase 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 propiedad promptType de MSALInteractiveTokenParameters configura el comportamiento de la solicitud de consentimiento y autenticación. Se admiten los valores siguientes:

  • .promptIfNecessary (valor predeterminado): solo se solicita al usuario si es necesario. La experiencia de inicio de sesión único viene determinada por la presencia de cookies en la vista web y el tipo de cuenta. Si hay varios usuarios con una sesión iniciada, se presenta la experiencia de selección de cuentas. Este es el comportamiento predeterminado.
  • .selectAccount: si no se especifica ningún usuario, la vista web de autenticación muestra una lista de las cuentas con una sesión iniciada actualmente para que el usuario realice su selección.
  • .login: requiere que el usuario se autentique en la vista web. Solo una cuenta puede iniciar sesión a la vez si se especifica este valor.
  • .consent: requiere que el usuario dé su consentimiento al conjunto de ámbitos actual de la solicitud.

Obtención de un token en silencio

Para adquirir un token actualizado en modo silencioso, agregue el código siguiente a la claseViewController. Crea un objeto MSALSilentTokenParameters y llama a 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()
        }
    }

Llamar a Microsoft Graph API

Una vez que tenga un token, la aplicación puede usarlo este valor en el encabezado de HTTP para realizar una solicitud autorizada a Microsoft Graph:

clave del encabezado value
Authorization Portador <access-token>

Agregue el siguiente código a la clase 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()
    }

Consulte Microsoft Graph API para más información acerca de Microsoft Graph API.

Uso de MSAL para cierre de sesión

A continuación, agregue compatibilidad para el cierre de sesión.

Importante

Al cerrar sesión con MSAL, se elimina de la aplicación toda la información conocida sobre un usuario, así como al eliminar una sesión activa en el dispositivo, cuando lo permite la configuración de este. Opcionalmente, también puede cerrar la sesión del usuario desde el explorador.

Para agregar la funcionalidad de cierre de sesión, agregue el siguiente código a la clase 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)
            })

        }
    }

Habilitación del almacenamiento en caché de tokens

De forma predeterminada, MSAL almacena en caché los tokens de la aplicación en el llavero de iOS o macOS.

Para habilitar el almacenamiento en caché de tokens:

  1. Asegúrese de que la aplicación está firmada correctamente.
  2. Vaya a la configuración del proyecto de Xcode >Pestaña Capabilities (Funcionalidades)>Enable Keychain Sharing (Habilitar uso compartido de la cadena de claves)
  3. Seleccione + y escriba uno de los siguientes grupos de cadenas de claves:
    • iOS: com.microsoft.adalcache
    • macOS: com.microsoft.identity.universalstorage

Adición de métodos auxiliares

Agregue los siguientes métodos auxiliares a la clase ViewController para completar el ejemplo.

Interfaz de usuario de 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)
    }

Interfaz de usuario de 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: obtención de información adicional del dispositivo

Use el código siguiente para leer la configuración actual del dispositivo, incluido el dato de si el dispositivo está configurado como compartido:

    @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.")
        }
    }

Aplicaciones de varias cuentas

Esta aplicación se ha creado para un escenario de una única cuenta. MSAL también admite escenarios de varias cuentas, pero requiere más trabajo de la aplicación. Debe crear una interfaz de usuario para ayudar a los usuarios a seleccionar qué cuenta desean usar para cada acción que requiera tokens. Como alternativa, la aplicación puede implementar un método heurístico para seleccionar qué cuenta se usará consultando todas las cuentas de MSAL. Por ejemplo, consulte la accountsFromDeviceForParameters:completionBlock:API

Prueba de la aplicación

Cree e implemente la aplicación en un emulador o dispositivo de prueba. Debe ser capaz de iniciar sesión y obtener tokens para Microsoft Entra ID o cuentas personales de Microsoft.

La primera vez que un usuario inicie sesión en la aplicación, Microsoft Identity le pedirá que consienta los permisos solicitados. Aunque la mayoría de los usuarios pueden dar su consentimiento, algunos inquilinos de Microsoft Entra tienen el consentimiento del usuario deshabilitado, lo que requiere que los administradores den su consentimiento en nombre de todos los usuarios. Para admitir este escenario, registre los ámbitos de la aplicación.

Después de iniciar sesión, esta aplicación mostrará los datos devueltos desde el punto de conexión /me de Microsoft Graph.

Pasos siguientes

Más información sobre la creación de aplicaciones móviles que llaman a las API web protegidas en nuestra serie de escenarios de varias partes.