Руководство. Вход пользователей и вызов Microsoft Graph из приложения iOS или macOS

В этом руководстве показано, как создать приложение iOS или macOS, которое интегрируется с платформой удостоверений Майкрософт для реализации входа пользователей, и как получить маркер доступа для вызова API Microsoft Graph.

После завершения работы с руководством приложение принимает входы личных учетных записей Майкрософт (включая outlook.com, live.com и другие) и рабочие или учебные учетные записи из любой компании или организации, которая использует идентификатор Microsoft Entra. Этот учебник применим к приложениям iOS и macOS. Некоторые шаги для этих двух платформ отличаются.

В этом руководстве рассматриваются следующие темы:

  • создание проекта приложения для iOS или macOS в Xcode;
  • Регистрация приложения в Центре администрирования Microsoft Entra
  • добавление кода для поддержки входа и выхода пользователей;
  • добавление кода для вызова API Microsoft Graph.
  • Тестирование приложения

Необходимые компоненты

Принципы работы приложения из учебника

Снимок экрана: способ работы примера приложения, созданного этим руководством.

В этом учебнике приложение может авторизовать пользователя и получать данные из Microsoft Graph от его имени. Доступ к этим данным осуществляется через защищенный API (API Microsoft Graph в данном случае), который требует авторизации и защищается платформа удостоверений Майкрософт.

В частности:

  • Ваше приложение входит в систему пользователя через браузер или Microsoft Authenticator.
  • Конечный пользователь принимает разрешения, запрошенные приложением.
  • Приложение выдается маркер доступа для API Microsoft Graph.
  • Маркер доступа включен в HTTP-запрос к веб-API.
  • Затем обрабатывается ответ Microsoft Graph.

В этом примере используется библиотека аутентификации Майкрософт (MSAL) для проведения проверки подлинности. MSAL автоматически обновляет маркеры, обеспечивает единый вход для других приложений на устройстве, а также управляет учетными записями.

Если вы хотите скачать готовую версию приложения, созданного в этом руководстве, обе версии можно найти в GitHub:

Создание нового проекта

  1. Запустите Xcode и щелкните Create a new Xcode project (Создать новый проект Xcode).
  2. Для приложений iOS выберите iOS>Приложение с одним представлением, а затем Далее.
  3. Для приложений macOS выберите macOS>Cocoa App, а затем Далее.
  4. Укажите название продукта.
  5. Установите для параметра Язык значение Swift и выберите Далее.
  6. Выберите папку для создания приложения и нажмите кнопку Создать.

Регистрация приложения

Совет

Действия, описанные в этой статье, могут немного отличаться на портале, с который вы начинаете работу.

  1. Войдите в Центр администрирования Microsoft Entra как минимум разработчик приложений.
  2. Если у вас есть доступ к нескольким клиентам, используйте значок "Параметры" в верхнем меню, чтобы переключиться на клиент, в котором вы хотите зарегистрировать приложение из меню каталогов и подписок.
  3. Перейдите к приложениям> удостоверений>Регистрация приложений.
  4. Выберите Создать регистрацию.
  5. Введите значение Name (Имя) для приложения. Пользователи приложения могут видеть это имя. Вы можете изменить его позже.
  6. Выберите учетные записи в любом каталоге организации (любой каталог Microsoft Entra — Multitenant) и личных учетных записей Майкрософт (например, Skype, Xbox) в поддерживаемых типах учетных записей.
  7. Выберите Зарегистрировать.
  8. В разделе Управление выберите Проверка подлинности>Добавить платформу>iOS/macOS.
  9. Введите идентификатор пакета проекта. Если скачан пример кода, идентификатор пакета имеет значение com.microsoft.identitysample.MSALiOS. Если вы создаете собственный проект, выберите проект в Xcode и откройте вкладку "Общие ". Идентификатор пакета отображается в разделе "Удостоверение ".
  10. Щелкните Настройка и сохраните конфигурацию MSAL, отображаемую на странице Конфигурация MSAL, чтобы указать ее при последующей настройке приложения.
  11. Нажмите кнопку Готово.

Добавление MSAL

Выберите один из следующих способов установки библиотеки MSAL в приложении:

CocoaPods

  1. Если вы используете CocoaPods, установите MSAL, сначала создав пустой файл podfile в той же папке проекта, что и ваш файл .xcodeproj. Добавьте следующий в файл podfile:

    use_frameworks!
    
    target '<your-target-here>' do
       pod 'MSAL'
    end
    
  2. Замените <your-target-here> именем проекта.

  3. В окне терминала перейдите в папку, где находится podfile, который вы создали, и запустите pod install, чтобы установить библиотеку MSAL.

  4. Чтобы перезагрузить проект в XCode закройте его и откройте <your project name>.xcworkspace.

Carthage

Если вы используете Carthage, установите MSAL, добавив в Cartfile:

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

В окне терминала в том же каталоге, в котором находится обновленный Cartfile, выполните следующую команду, чтобы Carthage обновил зависимости в вашем проекте.

iOS.

carthage update --platform iOS

macOS:

carthage update --platform macOS

Вручную

Установить библиотеку также можно с помощью подмодуля Git. Кроме того, вы можете извлечь последний выпуск чтобы использовать его в качестве платформы в своем приложении.

Добавление регистрации приложения

Затем мы добавим регистрацию приложения в код.

Сначала добавьте следующую инструкцию импорта в начало файла ViewController.swift и AppDelegate.swift или SceneDelegate.swift:

import MSAL

Затем добавьте следующий код в ViewController.swift перед 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?

Единственное значение, которое вы изменяете, — это значение, назначенное kClientID идентификатору приложения. Это значение является частью данных конфигурации MSAL, сохраненных на шаге в начале этого руководства, чтобы зарегистрировать приложение.

Настройка параметров проекта Xcode

Добавьте новую группу цепочки ключей в раздел Подписывание и возможности для проекта. Группа цепочки ключей должна иметь значение com.microsoft.adalcache в iOS и com.microsoft.identity.universalstorage в macOS.

Пользовательский интерфейс Xcode, отображающий настройку группы цепочки ключей.

(Только для iOS) Настройка схем URL-адресов

На этом шаге вы зарегистрируете CFBundleURLSchemes, чтобы перенаправить пользователя обратно в приложение после входа в систему. Кстати, LSApplicationQueriesSchemes также позволяет вашему приложению использовать Microsoft Authenticator.

В Xcode откройте Info.plist как файл исходного кода и добавьте следующее в раздел <dict>. Замените [BUNDLE_ID] ранее использованным значением. Если вы загрузили код, идентификатор пакета будет com.microsoft.identitysample.MSALiOS. Если вы создаете собственный проект, выберите проект в Xcode и откройте вкладку "Общие ". Идентификатор пакета отображается в разделе "Удостоверение ".

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

(Только для macOS) Настройка песочницы приложения

  1. Перейдите к параметрам проекта Xcode > выберите вкладку Возможности>Песочница приложения.
  2. Выберите флажок Исходящие подключения (Клиент).

Создание пользовательского интерфейса для приложения

Теперь создайте пользовательский интерфейс, в который включены кнопки для вызова Microsoft Graph API, для выхода и для текстового представления для просмотра некоторых результатов, добавив следующий код в класс ViewController:

Пользовательский интерфейс 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()
}

Пользовательский интерфейс 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() {}

Затем, также внутри класса ViewController следует заменить метод viewDidLoad() на:

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

Использование MSAL

Инициализация MSAL

Добавьте метод initMSAL в класс 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()
    }

По-прежнему в классе ViewController и после метода initMSAL добавьте метод initWebViewParams:

код iOS:

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

код macOS:

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

(Только для iOS) Работа с обратным вызовом входа

Откройте файл AppDelegate.swift. Чтобы обработать обратный вызов после входа, добавьте MSALPublicClientApplication.handleMSALResponse в класс appDelegate, например:

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

Если вы используете Xcode 11, то вместо этого в файл SceneDelegate.swift следует поместить обратный вызов MSAL. Если UISceneDelegate и UIApplicationDelegate поддерживаются для совместимости с более старыми версиями iOS, то обратный вызов MSAL потребуется поместить в оба файла.

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

Получение маркеров

Теперь мы можем реализовать логику обработки пользовательского интерфейса приложения и интерактивно получать маркеры через MSAL.

MSAL предоставляет два основных метода получения маркеров: acquireTokenSilently() и acquireTokenInteractively().

  • acquireTokenSilently() пытается выполнить вход и получить маркеры без какого-либо взаимодействия с пользователем, пока учетная запись существует. acquireTokenSilently() требует допустимого MSALAccount, который можно получить с помощью одного из API перечисления учетных записей MSAL. Этот учебник использует applicationContext.getCurrentAccount(with: msalParameters, completionBlock: {}) для извлечения текущей учетной записи.

  • Метод acquireTokenInteractively() всегда отображает пользовательский интерфейс при попытке входа пользователя. Он может использовать файлы cookie сеанса в браузере или учетную запись в Microsoft Authenticator для реализации интерактивного единого входа.

Добавьте в класс 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)
            }
        })
    }

Интерактивное получение маркера

Приведенный ниже фрагмент кода первый раз возвращает маркер путем создания объекта MSALInteractiveTokenParameters и вызова acquireToken. Затем добавьте код, который:

  1. Создает MSALInteractiveTokenParameters с областями.
  2. Вызывает acquireToken() с созданными параметрами.
  3. Обрабатывает ошибки. Дополнительные сведения см. в руководстве по обработке ошибок MSAL для iOS и macOS.
  4. Обрабатывает успешные выходные данные.

Добавьте в класс 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()
    }
}

Свойство promptType объекта MSALInteractiveTokenParameters настраивает поведение запроса аутентификации и согласия. Поддерживаются следующие значения:

  • .promptIfNecessary (по умолчанию) — пользователю предлагается только при необходимости. Процесс единого входа определяется наличием файлов cookie в веб-представлении и типом учетной записи. При входе в систему нескольких пользователей отображается возможность выбора учетной записи. Это поведение по умолчанию.
  • .selectAccount — если пользователь не указан, в веб-представлении аутентификации отображается список текущих учетных записей, выполнивших вход, для выбора пользователем.
  • .login — требует, чтобы пользователь прошел аутентификацию в веб-представлении. Если указано это значение, то одновременно может быть выполнен вход только одной учетной записи.
  • .consent — требует, чтобы пользователь предоставил согласие на текущий набор областей для запроса.

Автоматическое получение маркера

Чтобы получить обновленный маркер без вывода сообщений, добавьте следующий код в класс ViewController. Он создает MSALSilentTokenParameters объект и вызывает 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()
        }
    }

Вызов API Microsoft Graph

После получения маркера приложение может его использовать в заголовке HTTP, чтобы отправить авторизованный запрос к Microsoft Graph:

ключ заголовка значение
Авторизация Носитель <маркер доступа>

Добавьте в класс 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()
    }

Дополнительные сведения приведены на странице Microsoft Graph.

Использование MSAL для выхода

Теперь добавим поддержку функции выхода.

Внимание

Если функция выхода реализована с помощью MSAL, из приложения удаляется вся известная информация о пользователе, а также прекращается сеанс на устройстве пользователя, если это разрешено настройками устройства. При необходимости можно также выполнить выход пользователя в браузере.

Чтобы добавить функцию выхода, добавьте следующий код в класс 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)
            })

        }
    }

Включение кэширования маркеров

По умолчанию MSAL кэширует маркеры приложения в цепочку ключей iOS или macOS.

Чтобы включить кэширования маркеров:

  1. Убедитесь, что приложение подписано правильно
  2. Перейдите к параметрам проекта Xcode, а затем выберите >Вкладка "Возможности">Включить общий доступ к цепочке ключей.
  3. Выберите + и введите одну из следующих групп цепочк ключей:
    • iOS: com.microsoft.adalcache
    • macOS: com.microsoft.identity.universalstorage

Добавление вспомогательных методов

Чтобы выполнить пример, добавьте в класс ViewController следующие вспомогательные методы.

Пользовательский интерфейс 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)
    }

Пользовательский интерфейс 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)
    }

Получение дополнительных сведений об устройстве (только iOS)

Используйте следующий код для получения сведений о текущей конфигурации устройства, в том числе, о том, настроен ли общий доступ на устройстве:

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

Приложения для нескольких учетных записей

Это приложение создано для сценария с одной учетной записью. MSAL также поддерживает сценарии с несколькими учетными записями, но это требует дополнительной работы с приложениями. Необходимо создать пользовательский интерфейс, чтобы помочь пользователям выбрать учетную запись, которую они хотят использовать для каждого действия, для которого требуются маркеры. Кроме того, приложение может реализовать эвристику для выбора нужной учетной записи с помощью запроса всех учетных записей из MSAL. См. пример API accountsFromDeviceForParameters:completionBlock:.

Тестирование приложения

Выполните сборку и развертывание приложения на тестовом устройстве или в симуляторе. Вы должны иметь возможность войти и получить маркеры для идентификатора Microsoft Entra или личных учетных записей Майкрософт.

При первом входе пользователя в приложение от платформы удостоверений Майкрософт появится запрос на предоставление согласия на запрашиваемые разрешения. Хотя большинство пользователей способны предоставить согласие, некоторые клиенты Microsoft Entra отключили согласие пользователя, что требует от имени всех пользователей администраторам согласия. Для поддержки этого сценария зарегистрируйте области приложения.

После выполнения входа в приложении будут отображаться данные, возвращенные из конечной точки Microsoft Graph /me.

Следующие шаги

Из нашей серии сценариев узнайте, как создавать мобильные приложения, вызывающие защищенные веб-API.