Call the Microsoft Graph API from an iOS app

This guide demonstrates how a native iOS application (Swift) can get an access token and call the Microsoft Graph API or other APIs that require access tokens from Azure Active Directory v2 endpoint.

At the end of this guide, your application will be able to call a protected API using personal accounts (including outlook.com, live.com, and others) as well as work and school accounts from any company or organization that has Azure Active Directory.

Pre-requisites

  • XCode 8.x is required for this guide. You can download XCode from here.
  • Carthage for package management

How this guide works

How this guide works

The sample application created by this guide enables an iOS application to query the Microsoft Graph API or a Web API that accepts tokens from Azure Active Directory v2 endpoint. For this scenario, a token is added to HTTP requests via the Authorization header. Token acquisition and renewal is handled by the Microsoft Authentication Library (MSAL).

Handling token acquisition for accessing protected Web APIs

After the user authenticates, the sample application receives a token that can be used to query the Microsoft Graph API or a Web API secured by Microsoft Azure Active Directory v2.

APIs such as Microsoft Graph require an access token to allow accessing specific resources – for example, to read a user’s profile, access user’s calendar or send an email. Your application can request an access token using MSAL by specifying API scopes. This access token is then added to the HTTP Authorization header for every call 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 how to create 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 click Next
  3. Give a product name and click 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.

Add the MSAL framework to your application

  1. In Xcode, open the General tab
  2. Go to the Linked Frameworks and Libraries section and click +
  3. Select Add other…
  4. Select: Carthage > Build > iOS > MSAL.framework and click Open. You should see MSAL.framework added to the list.
  5. Go to Build Phases tab, and click + icon, choose 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

class ViewController: UIViewController, UITextFieldDelegate, URLSessionDelegate {

    let kClientID = "Your_Application_Id_Here"
    let kAuthority = "https://login.microsoftonline.com/common/v2.0"

    let kGraphURI = "https://graph.microsoft.com/v1.0/me/"
    let kScopes: [String] = ["https://graph.microsoft.com/user.read"]

    var accessToken = String()
    var applicationContext = MSALPublicClientApplication.init()

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

     // This button will invoke the call to the Microsoft Graph API. It uses the
     // built in Swift libraries to create a connection.

    @IBAction func callGraphButton(_ sender: UIButton) {


        do {

            // We check to see if we have a current logged in user. If we don't, then we need to sign someone in.
            // We throw an interactionRequired so that we trigger the interactive signin.

            if  try self.applicationContext.users().isEmpty {
                throw NSError.init(domain: "MSALErrorDomain", code: MSALErrorCode.interactionRequired.rawValue, userInfo: nil)
            } else {

            // Acquire a token for an existing user silently

            try self.applicationContext.acquireTokenSilent(forScopes: self.kScopes, user: applicationContext.users().first) { (result, error) in

                    if error == nil {
                        self.accessToken = (result?.accessToken)!
                        self.loggingText.text = "Refreshing token silently)"
                        self.loggingText.text = "Refreshed Access token is \(self.accessToken)"

                        self.signoutButton.isEnabled = true;
                        self.getContentWithToken()

                    } else {
                        self.loggingText.text = "Could not acquire token silently: \(error ?? "No error information" as! Error)"

                    }
                }
            }
        }  catch let 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 error.code == MSALErrorCode.interactionRequired.rawValue {

                self.applicationContext.acquireToken(forScopes: self.kScopes) { (result, error) in
                        if error == nil {
                            self.accessToken = (result?.accessToken)!
                            self.loggingText.text = "Access token is \(self.accessToken)"
                            self.signoutButton.isEnabled = true;
                            self.getContentWithToken()

                        } else  {
                            self.loggingText.text = "Could not acquire token: \(error ?? "No error information" as! Error)"
                        }
                }

            }

        } catch {

            // This is the catch all error.

            self.loggingText.text = "Unable to acquire token. Got error: \(error)"

        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        do {
             // Initialize a MSALPublicClientApplication with a given clientID and authority
            self.applicationContext = try MSALPublicClientApplication.init(clientId: kClientID, authority: kAuthority)
        } catch {
            self.loggingText.text = "Unable to create Application Context. Error: \(error)"
        }
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewWillAppear(_ animated: Bool) {

        if self.accessToken.isEmpty {
            signoutButton.isEnabled = false; 
        }
    }
}

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.users().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() {

    let sessionConfig = URLSessionConfiguration.default

    // 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")
    let urlSession = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)

    urlSession.dataTask(with: request) { data, response, error in
        let result = try? JSONSerialization.jsonObject(with: data!, options: [])
                    if result != nil {

                self.loggingText.text = result.debugDescription
            }
        }.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) {

    do {

        // Removes all tokens from the cache for this application for the provided user
        // first parameter:   The user to remove from the cache

        try self.applicationContext.remove(self.applicationContext.users().first)
        self.signoutButton.isEnabled = false;

    } catch let error {
        self.loggingText.text = "Received error signing user 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
}

Create an application (Express)

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

Add your application registration information to your solution (Advanced)

  1. Go to Microsoft Application Registration Portal
  2. Enter a name for your application and your email
  3. Make sure the option for Guided Setup is unchecked
  4. Click Add Platform, then select Native Application and click Save
  5. 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>
            <string>auth</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

Press Command + R to run the code in the simulator.

Sample screen shot

When you're ready to test, tap ‘Call Microsoft Graph API’ and you will be prompted to type your username and password.

The first time you sign in to your application, you will be presented with a consent screen similar to the below, where you need to explicitly accept:

Consent Screen

Expected results

You should see 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 the user's profile. This scope is automatically added by default in every application being registered on our registration portal. Some other APIs for Microsoft Graph as well as custom APIs for your backend server may require additional scopes. For example, for Microsoft Graph, the scope Calendars.Read is required to list the user’s calendars. In order to access the user’s calendar in a context of an application, you need to add the Calendars.Read delegated permission to the application registration’s information and then add the Calendars.Read scope to the acquireTokenSilent call. The user may be prompted for additional consents as you increase the number of scopes.

Help & Support

If you need help, report an issue or need to know about your support options, please see the article below: