Call the Microsoft Graph API from an iOS application

This guide shows how a native iOS application (Swift) can call APIs that require access tokens from the Microsoft Azure Active Directory (Azure AD) v2.0 endpoint. The guide explains how to obtain access tokens and use them in calls to the Microsoft Graph API and other APIs.

After you complete the exercises in this guide, your application can call a protected API from any company or organization that has Azure AD. Your application can make protected API calls by using personal accounts like outlook.com, live.com, and others, as well as work or school accounts.

Prerequisites

  • XCode version 10.x is required for the sample that is created in this guide. You can download XCode from the iTunes website.
  • The Carthage dependency manager is required for package management.

How this guide works

How this guide works

In this guide, the sample application enables an iOS application to query the Microsoft Graph API or a web API that accepts tokens from the Azure AD v2.0 endpoint. For this scenario, a token is added to HTTP requests by using the Authorization header. Token acquisition and renewal are handled by the Microsoft Authentication Library (MSAL).

Handle token acquisition for access to protected web APIs

After the user authenticates, the sample application receives a token. The token is used to query the Microsoft Graph API or a web API that is secured by the Azure AD v2.0 endpoint.

APIs, such as Microsoft Graph, require an access token to allow access to specific resources. Tokens are required to read a user’s profile, access a user’s calendar, send an email, and so on. Your application can request an access token by using MSAL and specifying API scopes. The access token is added to the HTTP Authorization header for every call that is made against the protected resource.

MSAL manages caching and refreshing access tokens for you, so your application doesn't need to.

Libraries

This guide uses the following library:

Library Description
MSAL.framework Microsoft Authentication Library Preview for iOS

Setting up your iOS application

This section provides step-by-step instructions for creating a new project to demonstrate how to integrate an iOS application (Swift) with Sign-In with Microsoft so it can query Web APIs that require a token.

Prefer to download this sample's XCode project instead? Download a project and skip to the Configuration step to configure the code sample before executing.

Install Carthage to download and build MSAL

Carthage package manager is used during the preview period of MSAL – it integrates with XCode while maintaining the ability for Microsoft to make changes to the library.

  • Download and install the latest release of Carthage here.

Creating your application

  1. Open Xcode and select Create a new Xcode project.
  2. Select iOS > Single view Application and select Next.
  3. Give a product name and select Next.
  4. Select a folder to create your app and click Create

Build the MSAL Framework

Follow the instructions below to pull and then build the latest version of MSAL libraries using Carthage:

  1. Open the bash terminal and go to the app's root folder.
  2. Copy the below and paste in the bash terminal to create a ‘Cartfile’ file:
echo "github \"AzureAD/microsoft-authentication-library-for-objc\" \"master\"" > Cartfile
  1. Copy and paste the below. This command fetches dependencies into a Carthage/Checkouts folder, then builds the MSAL library:
carthage update

The process above is used to download and build the Microsoft Authentication Library (MSAL). MSAL handles acquiring, caching and refreshing user tokens used to access APIs protected by the Azure Active Directory v2.0.

Add the MSAL framework to your application

  1. In Xcode, open the General tab.
  2. Go to the Linked Frameworks and Libraries section and select +.
  3. Select Add other....
  4. Select Carthage > Build > iOS > MSAL.framework and then select Open. You should see MSAL.framework added to the list.
  5. Go to the Build Phases tab, select the + icon, and then select New Run Script Phase.
  6. Add the following contents to the script area:
/usr/local/bin/carthage copy-frameworks
  1. Add the following to Input Files by clicking +:
$(SRCROOT)/Carthage/Build/iOS/MSAL.framework

Creating your application’s UI

A Main.storyboard file should automatically be created as a part of your project template. Follow the instructions below to create the app UI:

  1. Control+click Main.storyboard to bring up the contextual menu, and then click: Open As > Source Code
  2. Replace the <scenes> node with the code below:
 <scenes>
    <scene sceneID="tne-QT-ifu">
        <objects>
            <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="MSALiOS" customModuleProvider="target" sceneMemberID="viewController">
                <layoutGuides>
                    <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                    <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                </layoutGuides>
                <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                    <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                    <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                    <subviews>
                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Microsoft Authentication Library" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ifd-fu-zjm">
                            <rect key="frame" x="64" y="28" width="246" height="21"/>
                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                            <fontDescription key="fontDescription" type="system" pointSize="17"/>
                            <nil key="textColor"/>
                            <nil key="highlightedColor"/>
                        </label>
                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Logging" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="98g-dc-BPL">
                            <rect key="frame" x="16" y="277" width="62" height="21"/>
                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                            <fontDescription key="fontDescription" type="system" pointSize="17"/>
                            <nil key="textColor"/>
                            <nil key="highlightedColor"/>
                        </label>
                        <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2rX-Vv-T1i">
                            <rect key="frame" x="87" y="100" width="200" height="30"/>
                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                            <state key="normal" title="Call Microsoft Graph API"/>
                            <connections>
                                <action selector="callGraphButton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Kx0-JL-Bv9"/>
                            </connections>
                        </button>
                        <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qXW-2z-J7K">
                            <rect key="frame" x="16" y="324" width="343" height="291"/>
                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                            <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                            <accessibility key="accessibilityConfiguration">
                                <accessibilityTraits key="traits" updatesFrequently="YES"/>
                            </accessibility>
                            <fontDescription key="fontDescription" type="system" pointSize="14"/>
                            <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                        </textView>
                        <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u4-b8-vmX">
                            <rect key="frame" x="137" y="138" width="100" height="30"/>
                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                            <state key="normal" title="Sign Out"/>
                            <connections>
                                <action selector="signoutButton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="kZT-P8-0Zy"/>
                            </connections>
                        </button>
                    </subviews>
                    <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                </view>
                <connections>
                    <outlet property="loggingText" destination="qXW-2z-J7K" id="uqO-Yw-AsK"/>
                    <outlet property="signoutButton" destination="9u4-b8-vmX" id="OCh-qk-ldv"/>
                </connections>
            </viewController>
            <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
        </objects>
        <point key="canvasLocation" x="140" y="137.18140929535232"/>
    </scene>
</scenes>

Use the Microsoft Authentication Library (MSAL) to get a token for the Microsoft Graph API

Open ViewController.swift and replace the code with:

import UIKit
import MSAL

// A View Controller that will respond to the events of the Storyboard.
class ViewController: UIViewController, UITextFieldDelegate, URLSessionDelegate {

    // Replace Your_Application_Id_Here with the client ID you received in the portal. The below is for running the demo only.
    let kClientID = "Your_Application_Id_Here"

    // These settings you don't need to edit unless you wish to attempt deeper scenarios with the app.
    let kGraphURI = "https://graph.microsoft.com/v1.0/me/"
    let kScopes: [String] = ["https://graph.microsoft.com/user.read"]
    let kAuthority = "https://login.microsoftonline.com/common"

    var accessToken = String()
    var applicationContext : MSALPublicClientApplication?

    @IBOutlet weak var loggingText: UITextView!
    @IBOutlet weak var signoutButton: UIButton!

    /**
        Setup public client application in viewDidLoad
    */

    override func viewDidLoad() {

        super.viewDidLoad()

        do {

            /**
             Initialize a MSALPublicClientApplication with a given clientID and authority
             - clientId:            The clientID of your application, you should get this from the app portal.
             - authority:           A URL indicating a directory that MSAL can use to obtain tokens. In Azure AD
                                    it is of the form https://<instance/<tenant>, where <instance> is the
                                    directory host (e.g. https://login.microsoftonline.com) and <tenant> is a
                                    identifier within the directory itself (e.g. a domain associated to the
                                    tenant, such as contoso.onmicrosoft.com, or the GUID representing the
                                    TenantID property of the directory)
             - error                The error that occurred creating the application object, if any, if you're
                                    not interested in the specific error pass in nil.
             */

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

            let authority = try MSALAuthority(url: authorityURL)
            self.applicationContext = try MSALPublicClientApplication(clientId: kClientID, authority: authority)

        } catch let error {
            self.loggingText.text = "Unable to create Application Context \(error)"
        }
    }

    override func viewWillAppear(_ animated: Bool) {

        super.viewWillAppear(animated)
        signoutButton.isEnabled = !self.accessToken.isEmpty
    }

    /**
     This button will invoke the authorization flow.
    */

    @IBAction func callGraphButton(_ sender: UIButton) {

        if self.currentAccount() == nil {
            // 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()
        } else {
            self.acquireTokenSilently()
        }
    }

    func acquireTokenInteractively() {

        guard let applicationContext = self.applicationContext else { return }

        applicationContext.acquireToken(forScopes: kScopes) { (result, error) in

            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
            }

            self.accessToken = result.accessToken!
            self.updateLogging(text: "Access token is \(self.accessToken)")
            self.updateSignoutButton(enabled: true)
            self.getContentWithToken()
        }
    }

    func acquireTokenSilently() {

        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.
         */

        applicationContext.acquireTokenSilent(forScopes: kScopes, account: self.currentAccount()) { (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
                    && nsError.code == MSALErrorCode.interactionRequired.rawValue) {

                    DispatchQueue.main.async {
                        self.acquireTokenInteractively()
                    }

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

    func currentAccount() -> MSALAccount? {

        guard let applicationContext = self.applicationContext else { return nil }

        // We retrieve our current account by getting the first account from cache
        // In multi-account applications, account should be retrieved by home account identifier or username instead
        do {

            let cachedAccounts = try applicationContext.allAccounts()

            if !cachedAccounts.isEmpty {
                return cachedAccounts.first
            }

        } catch let error as NSError {

            self.updateLogging(text: "Didn't find any accounts in cache: \(error)")
        }

        return nil
    }

More Information

Getting a user token interactively

Calling the acquireToken method results in a browser window prompting the user to sign in. Applications usually require a user to sign in interactively the first time they need to access a protected resource, or when a silent operation to acquire a token fails (e.g. the user’s password expired).

Getting a user token silently

The acquireTokenSilent method handles token acquisitions and renewal without any user interaction. After acquireToken is executed for the first time, acquireTokenSilent is the method commonly used to obtain tokens used to access protected resources for subsequent calls - as calls to request or renew tokens are made silently.

Eventually, acquireTokenSilent will fail – e.g. the user has signed out, or has changed their password on another device. When MSAL detects that the issue can be resolved by requiring an interactive action, it fires an MSALErrorCode.interactionRequired exception. Your application can handle this exception in two ways:

  1. Make a call against acquireToken immediately, which results in prompting the user to sign in. This pattern is usually used in online applications where there is no offline content in the application available for the user. The sample application generated by this guided setup uses this pattern: you can see it in action the first time you execute the application. Because no user ever used the application, applicationContext.allAccounts().first will contain a null value, and an MSALErrorCode.interactionRequired exception will be thrown. The code in the sample then handles the exception by calling acquireToken resulting in prompting the user to sign in.

  2. Applications can also make a visual indication to the user that an interactive sign-in is required, so the user can select the right time to sign in, or the application can retry acquireTokenSilent at a later time. This is usually used when the user can use other functionality of the application without being disrupted - for example, there is offline content available in the application. In this case, the user can decide when they want to sign in to access the protected resource, or to refresh the outdated information, or your application can decide to retry acquireTokenSilent when network is restored after being unavailable temporarily.

Call the Microsoft Graph API using the token you just obtained

Add the new method below to ViewController.swift. This method is used to make a GET request against the Microsoft Graph API using an HTTP Authorization header:

 func getContentWithToken() {

        // Specify the Graph API endpoint
        let url = URL(string: kGraphURI)
        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()
    }

Making a REST call against a protected API

In this sample application, the getContentWithToken() method is used to make an HTTP GET request against a protected resource that requires a token and then return the content to the caller. This method adds the acquired token in the HTTP Authorization header. For this sample, the resource is the Microsoft Graph API me endpoint – which displays the user's profile information.

Set up sign-out

Add the following method to ViewController.swift to sign out the user:

 @IBAction func signoutButton(_ sender: UIButton) {

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

            try applicationContext.remove(account)
            self.loggingText.text = ""
            self.signoutButton.isEnabled = false

        } catch let error as NSError {

            self.updateLogging(text: "Received error signing account out: \(error)")
        }
    }

}

More info on sign-out

The signoutButton method removes the user from the MSAL user cache – this will effectively tell MSAL to forget the current user so a future request to acquire a token will only succeed if it is made to be interactive.

Although the application in this sample supports a single user, MSAL supports scenarios where multiple accounts can be signed in at the same time – an example is an email application where a user has multiple accounts.

Register the callback

Once the user authenticates, the browser redirects the user back to the application. Follow the steps below to register this callback:

  1. Open AppDelegate.swift and import MSAL:
import MSAL
  1. Add the following method to your AppDelegate class to handle callbacks:
// @brief Handles inbound URLs. Checks if the URL matches the redirect URI for a pending AppAuth
// authorization request and if so, will look for the code in the response.

func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {

    print("Received callback!")

    MSALPublicClientApplication.handleMSALResponse(url)

    return true
}

Register your application

You can register your application in either of two ways, as described in the next two sections.

Option 1: Express mode

Now you need to register your application in the Microsoft Application Registration Portal:

  1. Register your application via the Microsoft Application Registration Portal.
  2. Enter a name for your application and your email.
  3. Make sure the option for Guided Setup is checked.
  4. Follow the instructions to obtain the application ID and paste it into your code.

Option 2: Advanced mode

  1. Go to Microsoft Application Registration Portal.
  2. Enter a name for your application.
  3. Make sure the option for Guided Setup is unchecked.
  4. Select Add Platform and then select Native Application.
  5. Select Save.
  6. Go back to Xcode. In ViewController.swift, replace the line starting with 'let kClientID' with the application ID you just registered:
let kClientID = "Your_Application_Id_Here"
  1. Control+click Info.plist to bring up the contextual menu, and then click: Open As > Source Code
  2. Under the dict root node, add the following:
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>msal[Your_Application_Id_Here]</string>
        </array>
    </dict>
</array>
  1. Replace [Your_Application_Id_Here] with the Application Id you just registered

Test querying the Microsoft Graph API from your iOS application

To run the code in the simulator, press Command + R.

Test your application in the simulator

When you're ready to test, select Call Microsoft Graph API. When prompted, enter your username and password.

The first time that you sign in to your application, you're prompted to provide your consent to allow the application to access your profile and to sign you in:

Provide your consent for application access

View application results

After you sign in, you should see your user profile information returned by the Microsoft Graph API call in the Logging section.

More information about scopes and delegated permissions

The Microsoft Graph API requires the user.read scope to read a user's profile. This scope is automatically added by default in every application that's registered on the registration portal. Other APIs for Microsoft Graph, as well as custom APIs for your back-end server, might require additional scopes. The Microsoft Graph API requires the Calendars.Read scope to list the user’s calendars.

To access the user’s calendars in the context of an application, add the Calendars.Read delegated permission to the application registration information. Then, add the Calendars.Read scope to the acquireTokenSilent call.

Note

The user might be prompted for additional consents as you increase the number of scopes.

Help and support

If you need help, want to report an issue, or want to learn more about your support options, see the following article: