Tutorial: Masuk pengguna dan memanggil Microsoft Graph dari aplikasi iOS atau macOS

Dalam tutorial ini, Anda membuat aplikasi iOS atau macOS yang terintegrasi dengan platform identitas Microsoft untuk menandatangani pengguna dan mendapatkan token akses untuk memanggil Microsoft Graph API.

Setelah menyelesaikan tutorial, aplikasi Anda menerima rincian masuk akun Microsoft pribadi (termasuk outlook.com, live.com, dan lainnya) dan akun kantor atau sekolah dari perusahaan atau organisasi mana pun yang menggunakan ID Microsoft Entra. Tutorial ini berlaku untuk aplikasi iOS dan macOS. Beberapa langkah berbeda antara kedua platform.

Dalam tutorial ini:

  • Membuat proyek app iOS atau macOS di Xcode
  • Mendaftarkan aplikasi di pusat admin Microsoft Entra
  • Menambahkan kode untuk mendukung upaya masuk dan keluar pengguna
  • Menambahkan kode untuk memanggil Microsoft Graph API
  • Menguji aplikasi

Prasyarat

Cara kerja aplikasi tutorial

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

Aplikasi dalam tutorial ini dapat memasukkan pengguna dan mendapatkan data dari Microsoft Graph atas nama mereka. Data ini diakses melalui API yang dilindungi (Microsoft Graph API dalam hal ini) yang memerlukan otorisasi dan dilindungi oleh platform identitas Microsoft.

Lebih spesifik:

  • Aplikasi Anda masuk ke pengguna baik melalui browser atau Microsoft Authenticator.
  • Pengguna akhir menerima izin yang diminta aplikasi Anda.
  • Aplikasi Anda mengeluarkan token akses untuk Microsoft Graph API.
  • Token akses disertakan dalam permintaan HTTP ke API web.
  • Memproses respons Microsoft Graph.

Sampel ini menggunakan Pustaka Autentikasi Microsoft (MSAL) untuk menerapkan Autentikasi. MSAL akan memperbarui token secara otomatis, memberikan akses menyeluruh (SSO) antara aplikasi lain di perangkat, dan mengelola akun.

Jika ingin mengunduh versi lengkap aplikasi yang Anda buat dalam tutorial ini, Anda dapat menemukan kedua versi di GitHub:

Membuat proyek baru

  1. Buka Xcode dan pilih Buat proyek Xcode baru.
  2. Untuk aplikasi iOS, pilih iOS>Aplikasi tampilan tunggal, lalu pilih Berikutnya.
  3. Untuk aplikasi macOS, pilih MacOS>Aplikasi Cocoa, lalu pilih Berikutnya.
  4. Memberikan nama produk.
  5. Atur Bahasa ke Swift dan pilih Berikutnya.
  6. Pilih folder untuk membuat aplikasi Anda, lalu pilih Buat.

Mendaftarkan aplikasi

Tip

Langkah-langkah dalam artikel ini mungkin sedikit berbeda berdasarkan portal tempat Anda memulai.

  1. Masuk ke pusat admin Microsoft Entra setidaknya sebagai Pengembang Aplikasi.
  2. Jika Anda memiliki akses ke beberapa penyewa, gunakan ikon Pengaturan di menu atas untuk beralih ke penyewa tempat Anda ingin mendaftarkan aplikasi dari menu Direktori + langganan.
  3. Telusuri Aplikasi >Identitas>Pendaftaran aplikasi.
  4. Pilih Pendaftaran baru.
  5. Masukkan Nama untuk aplikasi Anda. Pengguna aplikasi mungkin melihat nama ini, dan Anda dapat mengubahnya nanti.
  6. Pilih Akun di direktori organisasi apa pun (Direktori Microsoft Entra apa pun - Multipenyewa) dan akun Microsoft pribadi (misalnya Skype, Xbox) di bawah Jenis akun yang didukung.
  7. Pilih Daftarkan.
  8. Di bawah Kelola, pilih Autentikasi>Tambahkan platform>iOS/macOS.
  9. Memasukkan ID Bundle proyek Anda. Jika sudah mengunduh sampel kode, ID Bundel adalah com.microsoft.identitysample.MSALiOS. Jika membuat proyek Anda sendiri, pilih proyek Anda di Xcode dan buka tab Umum. Pengidentifikasi bundel akan muncul di bagian Identitas.
  10. Pilih Konfigurasi dan simpan Konfigurasi MSAL yang muncul di halaman Konfigurasi MSAL, sehingga Anda dapat memasukkannya saat mengonfigurasi aplikasi nanti.
  11. Pilih Selesai.

Menambahkan MSAL

Pilih salah satu cara berikut untuk menginstal pustaka MSAL di aplikasi Anda:

CocoaPods

  1. Jika Anda menggunakan CocoaPods, instal MSAL dengan membuat file kosong yang disebut terlebih dahulu di folder yang sama dengan file proyek Anda. Tambahkan yang berikut ke Podfile:

    use_frameworks!
    
    target '<your-target-here>' do
       pod 'MSAL'
    end
    
  2. Ganti <your-target-here> dengan nama proyek Anda.

  3. Di jendela terminal, navigasi ke folder yang berisi podfile yang Anda buat dan jalankan pod install untuk menginstal pustaka MSAL.

  4. Tutup Xcode dan buka <your project name>.xcworkspace untuk memuat ulang proyek di Xcode.

Carthage

Jika Anda menggunakan Carthage, instal MSAL dengan menambahkannya ke Cartfile:

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

Dari jendela terminal, dalam direktori yang sama dengan Cartfile yang diperbarui, jalankan perintah berikut agar Carthage memperbarui dependensi dalam proyek Anda.

iOS:

carthage update --platform iOS

macOS:

carthage update --platform macOS

Secara manual…

Anda juga dapat menggunakan Git Submodule, atau melihat rilis terbaru untuk digunakan sebagai kerangka kerja dalam aplikasi Anda.

Menambahkan pendaftaran aplikasi

Selanjutnya, kami menambahkan pendaftaran aplikasi Anda ke kode Anda.

Pertama, tambahkan pernyataan impor berikut ke bagian atas file ViewController.swift dan AppDelegate.swift atau SceneDelegate.swift:

import MSAL

Selanjutnya, tambahkan kode berikut ke ViewController.swift sebelum ke 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?

Satu-satunya nilai yang Anda ubah adalah nilai yang ditetapkan untuk kClientID menjadi ID Aplikasi Anda. Nilai ini adalah bagian dari data Konfigurasi MSAL yang Anda simpan selama langkah di awal tutorial ini untuk mendaftarkan aplikasi.

Mengonfigurasi pengaturan proyek Xcode

Tambahkan grup rantai kunci baru ke proyek Penandatanganan & Kemampuan. Grup rantai kunci harus berupa com.microsoft.adalcache di iOS dan com.microsoft.identity.universalstorage di macOS.

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

Khusus untuk iOS, konfigurasikan skema URL

Dalam langkah ini, Anda akan mendaftarkan CFBundleURLSchemes sehingga pengguna dapat diarahkan kembali ke aplikasi setelah masuk. Omong-omong, LSApplicationQueriesSchemes juga memungkinkan aplikasi Anda menggunakan Microsoft Authenticator.

Di Xcode, buka Info.plist sebagai file kode sumber, dan tambahkan hal berikut di dalam bagian <dict>. Ganti [BUNDLE_ID] dengan nilai yang sebelumnya Anda gunakan. Jika Anda mengunduh kode, pengidentifikasi bundel adalah com.microsoft.identitysample.MSALiOS. Jika membuat proyek Anda sendiri, pilih proyek Anda di Xcode dan buka tab Umum. Pengidentifikasi bundel akan muncul di bagian Identitas.

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

Khusus untuk macOS, konfigurasikan Kotak Pasir Aplikasi

  1. Buka Pengaturan Proyek Xcode Anda >tab Kemampuan>Kotak Pasir Aplikasi
  2. Centang kotak Koneksi Keluar (Klien).

Membuat antarmuka pengguna aplikasi

Sekarang, buat antarmuka pengguna yang menyertakan tombol untuk memanggil Microsoft Graph API, buat yang lain untuk keluar, dan tampilan teks untuk melihat beberapa output dengan menambahkan kode berikut ke kelas ViewController:

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

Antarmuka pengguna 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() {}

Selanjutnya, juga di dalam kelas ViewController, ganti metode viewDidLoad() dengan:

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

Menggunakan MSAL

Menginisialisasi MSAL

Ke kelas ViewController, tambahkan metode initMSAL:

    func initMSAL() throws {

        guard let authorityURL = URL(string: kAuthority) else {
            self.updateLogging(text: "Unable to create authority URL")
            return
        }

        let authority = try MSALAADAuthority(url: authorityURL)

        let msalConfiguration = MSALPublicClientApplicationConfig(clientId: kClientID, redirectUri: nil, authority: authority)
        self.applicationContext = try MSALPublicClientApplication(configuration: msalConfiguration)
        self.initWebViewParams()
    }

Masih di ViewController kelas dan setelah metode initMSAL, tambahkan metode initWebViewParams:

Kode iOS:

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

Kode macOS:

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

Menangani panggilan balik kredensial masuk (khusus iOS)

Open file AppDelegate.swift. Untuk menangani panggilan balik setelah masuk, tambahkan MSALPublicClientApplication.handleMSALResponse ke kelas appDelegate seperti ini:

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

Jika menggunakan Xcode 11, Anda harus melakukan panggilan balik MSAL ke dalam file SceneDelegate.swift sebagai gantinya. Jika Anda mendukung UISceneDelegate dan UIApplicationDelegate untuk kompatibilitas dengan iOS yang lebih lama, panggilan balik MSAL harus ditempatkan ke dalam kedua 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)
    }

Mendapatkan Token

Sekarang, kami dapat menerapkan logika pemrosesan antarmuka pengguna aplikasi dan mendapatkan token secara interaktif melalui MSAL.

MSAL mengekspos dua metode utama untuk mendapatkan token: acquireTokenSilently() dan acquireTokenInteractively().

  • acquireTokenSilently() berupaya memasukkan pengguna dan mendapatkan token tanpa interaksi pengguna selama akun tersedia. acquireTokenSilently() memerlukan , yang valid MSALAccountyang dapat diambil dengan menggunakan salah satu API enumerasi akun MSAL. Tutorial ini menggunakan applicationContext.getCurrentAccount(with: msalParameters, completionBlock: {}) untuk mengambil akun saat ini.

  • acquireTokenInteractively() selalu menampilkan antarmuka pengguna ketika mencoba untuk memasukkan pengguna. Ini dapat menggunakan cookie sesi di browser atau akun di pengautentikasi Microsoft untuk memberikan pengalaman interaktif-SSO.

Tambahkan kode berikut ke kelas 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)
            }
        })
    }

Mendapatkan token secara interaktif

Cuplikan kode berikut mendapatkan token untuk pertama kalinya dengan membuat objek MSALInteractiveTokenParameters dan memanggil acquireToken. Selanjutnya Anda menambahkan kode yang:

  1. Membuat MSALInteractiveTokenParameters dengan cakupan.
  2. Memanggil acquireToken() dengan parameter yang dibuat.
  3. Menangani kesalahan. Untuk detail selengkapnya, lihat panduan penanganan kesalahan MSAL untuk iOS dan macOS.
  4. Menangani kasus yang berhasil.

Tambahkan kode berikut ke kelas 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()
    }
}

Properti promptType dari MSALInteractiveTokenParameters mengonfigurasi autentikasi dan perilaku permintaan persetujuan. Nilai berikut ini didukung:

  • .promptIfNecessary (default) - Pengguna hanya diminta jika perlu. Pengalaman SSO ditentukan oleh kehadiran cookie di tampilan web, dan jenis akun. Jika beberapa pengguna masuk, pengalaman pemilihan akun akan diberikan. Ini adalah perilaku default.
  • .selectAccount - Jika tidak ada pengguna yang ditentukan, tampilan web autentikasi memberikan daftar akun yang saat ini masuk untuk dipilih pengguna.
  • .login - Mengharuskan pengguna untuk mengautentikasi di tampilan web. Hanya satu akun yang dapat masuk pada satu waktu jika Anda menentukan nilai ini.
  • .consent - Mengharuskan pengguna untuk menyetujui serangkaian cakupan saat ini untuk permintaan.

Mendapatkan token secara diam-diam

Untuk memperoleh token yang diperbarui secara diam-diam, tambahkan kode berikut ke kelas ViewController. Tindakan ini akan membuat objek MSALSilentTokenParameters dan memanggil 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()
        }
    }

Menghubungi Microsoft Graph API

Setelah mendapatkan token, aplikasi Anda dapat menggunakannya di header HTTP untuk membuat permintaan resmi ke Microsoft Graph:

tombol header value
Otorisasi <Token akses> pembawa

Tambahkan kode berikut ke kelas 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()
    }

Lihat Microsoft Graph API untuk mempelajari selengkapnya tentang Microsoft Graph API.

Menggunakan MSAL untuk Keluar

Selanjutnya, tambahkan dukungan untuk keluar.

Penting

Keluar dengan MSAL menghapus semua informasi yang diketahui tentang pengguna dari aplikasi, serta menghapus sesi aktif di perangkat mereka ketika diizinkan oleh konfigurasi perangkat. Anda juga dapat secara opsional menandatangani pengguna dari browser.

Untuk menambahkan kemampuan keluar, tambahkan kode berikut di dalam kelas 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)
            })

        }
    }

Mengaktifkan penembolokan token

Secara default, MSAL membuat cache token aplikasi Anda di rantai kunci iOS atau macOS.

Untuk mengaktifkan penembolokan token:

  1. Pastikan aplikasi Anda ditandatangani dengan benar
  2. Buka Pengaturan Proyek Xcode Anda >tab Kemampuan>Aktifkan Berbagi Rantai Kunci
  3. Pilih + dan masukkan salah satu Grup Rantai Kunci berikut:
    • iOS: com.microsoft.adalcache
    • macOS: com.microsoft.identity.universalstorage

Menambahkan metode pendukung

Tambahkan metode pendukung berikut ke kelas ViewController untuk menyelesaikan sampel.

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

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

Hanya untuk iOS: dapatkan informasi perangkat tambahan

Gunakan kode berikut untuk membaca konfigurasi perangkat saat ini, termasuk apakah perangkat dikonfigurasi sebagai dibagikan:

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

Aplikasi multi-akun

Aplikasi ini dibuat untuk skenario satu akun. MSAL juga mendukung skenario multi-akun, tetapi membutuhkan lebih banyak pekerjaan aplikasi. Anda perlu membuat UI untuk membantu pengguna memilih akun mana yang ingin mereka gunakan untuk setiap tindakan yang memerlukan token. Atau, aplikasi Anda dapat menerapkan heuristik untuk memilih akun mana yang akan digunakan dengan mengkueri semua akun dari MSAL. Misalnya, lihat accountsFromDeviceForParameters:completionBlock:API

Menguji aplikasi Anda

Buat dan sebarkan aplikasi ke perangkat pengujian atau simulator. Anda harus dapat masuk dan mendapatkan token untuk ID Microsoft Entra atau akun Microsoft pribadi.

Pertama kali pengguna masuk ke aplikasi Anda, mereka akan diminta oleh identitas Microsoft untuk menyetujui izin yang diminta. Meskipun sebagian besar pengguna mampu menyetujui, beberapa penyewa Microsoft Entra telah menonaktifkan persetujuan pengguna, yang mengharuskan admin untuk menyetujui atas nama semua pengguna. Untuk mendukung skenario ini, daftarkan cakupan aplikasi Anda.

Setelah masuk, aplikasi akan menampilkan data yang dikembalikan dari titik akhir /me Microsoft Graph.

Langkah berikutnya

Pelajari selengkapnya tentang cara membangun aplikasi seluler yang memanggil API web yang dilindungi dalam seri skenario multi-bagian kami.