Tutorial: Sign in users and call Microsoft Graph from an iOS or macOS app
In this tutorial, you build an iOS or macOS app that integrates with the Microsoft identity platform to sign users and get an access token to call the Microsoft Graph API.
When you've completed the tutorial, your application accepts sign-ins of work or school accounts from any company or organization that uses Microsoft Entra ID. This tutorial is applicable to both iOS and macOS apps. Some steps are different between the two platforms.
In this tutorial:
- Create an iOS or macOS app project in Xcode
- Register the app in the Microsoft Entra admin center
- Add code to support user sign-in and sign-out
- Add code to call the Microsoft Graph API
- Test the app
Prerequisites
How tutorial app works
The app in this tutorial can sign in users and get data from Microsoft Graph on their behalf. This data is accessed via a protected API (Microsoft Graph API in this case) that requires authorization and is protected by the Microsoft identity platform.
More specifically:
- Your app signs in the user either through a browser or the Microsoft Authenticator.
- The end user accepts the permissions your application has requested.
- Your app is issued an access token for the Microsoft Graph API.
- The access token is included in the HTTP request to the web API.
- Process the Microsoft Graph response.
This sample uses the Microsoft Authentication Library (MSAL) to implement Authentication. MSAL will automatically renew tokens and manage the account(s).
If you'd like to download a completed version of the app you build in this tutorial, you can find both versions on GitHub:
- iOS code sample (GitHub)
- macOS code sample (GitHub)
Create a new project
- Open Xcode and select Create a new Xcode project.
- For iOS apps, select iOS > Single view App and select Next.
- For macOS apps, select macOS > Cocoa App and select Next.
- Provide a product name.
- Set the Language to Swift and select Next.
- Select a folder to create your app and select Create.
Register the application
Tip
Steps in this article might vary slightly based on the portal you start from.
- Sign in to the Microsoft Entra admin center as at least an Application Developer.
- If you have access to multiple tenants, use the Settings icon in the top menu to switch to the tenant in which you want to register the application from the Directories + subscriptions menu.
- Browse to Identity > Applications > App registrations.
- Select New registration.
- Enter a Name for your application. Users of your app might see this name, and you can change it later.
- Select Accounts in any organizational directory (Any Microsoft Entra directory - Multitenant) under Supported account types.
- Select Register.
- Under Manage, select Authentication > Add a platform > iOS/macOS.
- Enter your project's Bundle ID. If downloaded the code sample, the Bundle ID is
com.microsoft.identitysample.MSALiOS
. If you're creating your own project, select your project in Xcode and open the General tab. The bundle identifier appears in the Identity section. - Select Configure and save the MSAL Configuration that appears in the MSAL configuration page so you can enter it when you configure your app later.
- Select Done.
Add MSAL
Choose one of the following ways to install the MSAL library in your app:
CocoaPods
If you're using CocoaPods, install
MSAL
by first creating an empty file called podfile in the same folder as your project's .xcodeproj file. Add the following to podfile:use_frameworks! target '<your-target-here>' do pod 'MSAL' end
Replace
<your-target-here>
with the name of your project.In a terminal window, navigate to the folder that contains the podfile you created and run
pod install
to install the MSAL library.Close Xcode and open
<your project name>.xcworkspace
to reload the project in Xcode.
Carthage
If you're using Carthage, install MSAL
by adding it to your Cartfile:
github "AzureAD/microsoft-authentication-library-for-objc" "master"
From a terminal window, in the same directory as the updated Cartfile, run the following command to have Carthage update the dependencies in your project.
iOS:
carthage update --platform iOS
macOS:
carthage update --platform macOS
Manually
You can also use Git Submodule, or check out the latest release to use as a framework in your application.
Add the app registration
Next, we add your app registration to your code.
First, add the following import statement to the top of the ViewController.swift file and either AppDelegate.swift or SceneDelegate.swift:
import MSAL
Next, add the following code to ViewController.swift before to 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://microsoftgraph.chinacloudapi.cn/" // the Microsoft Graph endpoint
let kAuthority = "https://login.partner.microsoftonline.cn/common" // this authority allows a work or school account in any organization's Azure AD tenant to sign in
let kScopes: [String] = ["https://microsoftgraph.chinacloudapi.cn/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?
The only value you modify is the value assigned to kClientID
to be your Application ID. This value is part of the MSAL Configuration data that you saved during the step at the beginning of this tutorial to register the application.
Configure Xcode project settings
Add a new keychain group to your project Signing & Capabilities. The keychain group should be com.microsoft.adalcache
on iOS and com.microsoft.identity.universalstorage
on macOS.
For iOS only, configure URL schemes
In this step, you'll register CFBundleURLSchemes
so that the user can be redirected back to the app after sign in. By the way, LSApplicationQueriesSchemes
also allows your app to make use of Microsoft Authenticator.
In Xcode, open Info.plist as a source code file, and add the following inside of the <dict>
section. Replace [BUNDLE_ID]
with the value you previously used. If you downloaded the code, the bundle identifier is com.microsoft.identitysample.MSALiOS
. If you're creating your own project, select your project in Xcode and open the General tab. The bundle identifier appears in the Identity section.
<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>
For macOS only, configure App Sandbox
- Go to your Xcode Project Settings > Capabilities tab > App Sandbox
- Select Outgoing Connections (Client) checkbox.
Create your app's UI
Now create a UI that includes a button to call the Microsoft Graph API, another to sign out, and a text view to see some output by adding the following code to the ViewController
class:
iOS UI
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 UI
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() {}
Next, also inside the ViewController
class, replace the viewDidLoad()
method with:
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()
}
Use MSAL
Initialize MSAL
To the ViewController
class, add the initMSAL
method:
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()
}
Still in the ViewController
class and after the initMSAL
method, add the initWebViewParams
method:
iOS code:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters(authPresentationViewController: self)
}
macOS code:
func initWebViewParams() {
self.webViewParameters = MSALWebviewParameters()
}
Handle the sign-in callback (iOS only)
Open the AppDelegate.swift file. To handle the callback after sign-in, add MSALPublicClientApplication.handleMSALResponse
to the appDelegate
class like this:
// 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)
}
If you are using Xcode 11, you should place MSAL callback into the SceneDelegate.swift instead. If you support both UISceneDelegate and UIApplicationDelegate for compatibility with older iOS, MSAL callback would need to be placed into both files.
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)
}
Acquire Tokens
Now, we can implement the application's UI processing logic and get tokens interactively through MSAL.
MSAL exposes two primary methods for getting tokens: acquireTokenSilently()
and acquireTokenInteractively()
.
acquireTokenSilently()
attempts to sign in a user and get tokens without user interaction as long as an account is present.acquireTokenSilently()
require a validMSALAccount
, which can be retrieved by using one of MSAL's account enumeration APIs. This tutorial usesapplicationContext.getCurrentAccount(with: msalParameters, completionBlock: {})
to retrieve the current account.acquireTokenInteractively()
always shows UI when attempting to sign in the user. It may use session cookies in the browser or an account in the Microsoft authenticator to provide an interactive-SSO experience.
Add the following code to the ViewController
class:
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)
}
})
}
Get a token interactively
The following code snippet gets a token for the first time by creating an MSALInteractiveTokenParameters
object and calling acquireToken
. Next you add code that:
- Creates
MSALInteractiveTokenParameters
with scopes. - Calls
acquireToken()
with the created parameters. - Handles errors. For more detail, refer to the MSAL for iOS and macOS error handling guide.
- Handles the successful case.
Add the following code to the ViewController
class.
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()
}
}
The promptType
property of MSALInteractiveTokenParameters
configures the authentication and consent prompt behavior. The following values are supported:
.promptIfNecessary
(default) - The user is prompted only if necessary. The SSO experience is determined by the presence of cookies in the webview, and the account type. If multiple users are signed in, account selection experience is presented. This is the default behavior..selectAccount
- If no user is specified, the authentication webview presents a list of currently signed-in accounts for the user to select from..login
- Requires the user to authenticate in the webview. Only one account may be signed-in at a time if you specify this value..consent
- Requires the user to consent to the current set of scopes for the request.
Get a token silently
To acquire an updated token silently, add the following code to the ViewController
class. It creates an MSALSilentTokenParameters
object and calls 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()
}
}
Call the Microsoft Graph API
Once you have a token, your app can use it in the HTTP header to make an authorized request to the Microsoft Graph:
header key | value |
---|---|
Authorization | Bearer <access-token> |
Add the following code to the ViewController
class:
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()
}
See Microsoft Graph API to learn more about the Microsoft Graph API.
Use MSAL for Sign-out
Next, add support for sign-out.
Important
Signing out with MSAL removes all known information about a user from the application, as well as removing an active session on their device when allowed by device configuration. You can also optionally sign user out from the browser.
To add sign-out capability, add the following code inside the ViewController
class.
@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)
})
}
}
Enable token caching
By default, MSAL caches your app's tokens in the iOS or macOS keychain.
To enable token caching:
- Ensure your application is properly signed
- Go to your Xcode Project Settings > Capabilities tab > Enable Keychain Sharing
- Select + and enter one of the following Keychain Groups:
- iOS:
com.microsoft.adalcache
- macOS:
com.microsoft.identity.universalstorage
- iOS:
Add helper methods
Add the following helper methods to the ViewController
class to complete the sample.
iOS UI:
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 UI:
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 only: get additional device information
Use following code to read current device configuration, including whether device is configured as shared:
@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.")
}
}
Multi-account applications
This app is built for a single account scenario. MSAL also supports multi-account scenarios, but it requires more application work. You need to create UI to help users select which account they want to use for each action that requires tokens. Alternatively, your app can implement a heuristic to select which account to use by querying all accounts from MSAL. For example, see accountsFromDeviceForParameters:completionBlock:
API
Test your app
Build and deploy the app to a test device or simulator. You should be able to sign in and get tokens for Microsoft Entra ID.
The first time a user signs into your app, they'll be prompted by Microsoft identity to consent to the permissions requested. While most users are capable of consenting, some Microsoft Entra tenants have disabled user consent, which requires admins to consent on behalf of all users. To support this scenario, register your app's scopes.
After you sign in, the app will display the data returned from the Microsoft Graph /me
endpoint.
Next steps
Learn more about building mobile apps that call protected web APIs in our multi-part scenario series.