Sign in users and call the Microsoft Graph from an iOS app

In this tutorial, you'll learn how to build an iOS application and integrate it into Microsoft identity platform. Specifically, this app will sign in a user, get an access token to call the Microsoft Graph API, and make a basic request to the Microsoft Graph API.

When you've completed the guide, your application will accept sign-ins of personal Microsoft accounts (including outlook.com, live.com, and others) and work or school accounts from any company or organization that uses Azure Active Directory.

How this guide works

Shows how the sample app generated by this tutorial works

The app in this sample will sign in users and get data on their behalf. This data will be accessed via a protected API (Microsoft Graph API in this case) that requires authorization and is also protected by Microsoft identity platform.

More specifically:

  • Your app will sign in the user either through a browser or the Microsoft Authenticator.
  • The end user will accept the permissions your application has requested.
  • Your app will be issued an access token for the Microsoft Graph API.
  • The access token will be included in the HTTP request to the web API.
  • Process the Microsoft Graph response.

This sample uses the Microsoft Authentication library (MSAL) to implement Auth. MSAL will automatically renew tokens, deliver SSO between other apps on the device, and manage the Account(s).

Prerequisites

  • XCode version 10.x is required for the sample that is created in this guide. You can download XCode from the iTunes website.
  • Microsoft Authentication Library (MSAL.framework). You can use dependency manager or add manually. There is section below with more information.

Set up your project

This tutorial will create a new project. If you want to download the completed tutorial instead, download the code.

Create a new project

  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.

Register your application

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

Register your app

  1. Go to the Azure portal > Select New registration.
  2. Enter a Name for your app > Register. Do not set a Redirect URI at this stage.
  3. In the Manage section, go to Authentication > Add a platform > iOS
    • Enter your project's Bundle ID. If you downloaded the code, this is com.microsoft.identitysample.MSALiOS.
  4. Hit Configure and store the MSAL Configuration for later.

Add MSAL

Get MSAL

CocoaPods

You can use CocoaPods to install MSAL by adding it to your Podfile under target:

use_frameworks!

target '<your-target-here>' do
   pod 'MSAL', '~> 0.4.0'
end

Carthage

You can use Carthage to install MSAL by adding it to your Cartfile:

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

Manually

You can also use Git Submodule or check out the latest release and use as framework in your application.

Add your app registration

Next, add your app registration to your code. Add your Client / Application ID to ViewController.swift:

let kClientID = "Your_Application_Id_Here"

// Additional variables for Auth and Graph API
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?

Configure URL schemes

Register CFBundleURLSchemes to allow a callback so user can be redirected back to the app after sign in.

LSApplicationQueriesSchemes allows using your app to use the Microsoft Authenticator if available.

To do this, open Info.plist as a source code and add the following, replacing the BUNDLE_ID with what you configured in the Azure portal.

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>msauth.[BUNDLE_ID]</string>
        </array>
    </dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>msauth</string>
	<string>msauthv2</string>
</array>

Import MSAL

Import MSAL framework in ViewController.swift and AppDelegate.swift files:

import MSAL

Create your app’s UI

For this tutorial, you need to create:

  • Call Graph API button
  • Sign out button
  • Logging textview

In ViewController.swift, define properties and initUI() as follows:


var loggingText: UITextView!
var signOutButton: UIButton!
var callGraphButton: UIButton!

func initUI() {
        // 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: 50.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
        signOutButton.isEnabled = false
        
        // Add logging textfield
        loggingText = UITextView()
        loggingText.isUserInteractionEnabled = false
        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
    }

Next, add to viewDidLoad() of ViewController.swift:

    override func viewDidLoad() {
        super.viewDidLoad()
        initUI()
        do {
            try self.initMSAL()
        } catch let error {
            self.loggingText.text = "Unable to create Application Context \(error)"
        }
    }

Use MSAL

Initialize MSAL

First, you need to create an MSALPublicClientApplication with an instance of MSALPublicClientConfiguration for your application:

    func initMSAL() throws {
        
        guard let authorityURL = URL(string: kAuthority) else {
            self.loggingText.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)
    }

Handle the Callback

To handle the callback after sign-in, add MSALPublicClientApplication.handleMSALResponse in appDelegate:

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        
        guard let sourceApplication = options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String else {
            return false
        }
        
        return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: sourceApplication)
    }

Acquire Tokens

Now, we can implement the application's UI processing logic and getting 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 any user interaction if an account is present.

acquireTokenInteractively will always show UI when attempting to sign in the user and get tokens; however, it might use session cookies in the browser or an account in the Microsoft authenticator to give an interactive-SSO experience.

    @objc func callGraphAPI(_ sender: UIButton) {
        
        guard let currentAccount = self.currentAccount() else {
            // We check to see if we have a current logged in account.
            // If we don't, then we need to sign someone in.
            acquireTokenInteractively()
            return
        }
        
        acquireTokenSilently(currentAccount)
    }

    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
    }

Get a token interactively

To acquire a token for the first time, you will need to create an MSALInteractiveTokenParameters and call acquireToken.

  1. Create MSALInteractiveTokenParameters with scopes.
  2. Call acquireToken with the parameters created.
  3. Handle error accordingly. For more detail, refer to the iOS error handling guide.
  4. Handle the successful case.
    func acquireTokenInteractively() {
   
        guard let applicationContext = self.applicationContext else { return }
     // #1    
        let parameters = MSALInteractiveTokenParameters(scopes: kScopes)
     // #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.updateSignOutButton(enabled: true)
            self.getContentWithToken()
        }
    }

Get a token silently

To acquire an updated token silently, you will need to create an MSALSilentTokenParameters and call acquireTokenSilent:

    
    func acquireTokenSilently(_ account : MSALAccount!) {
        guard let applicationContext = self.applicationContext else { return }
        let parameters = MSALSilentTokenParameters(scopes: kScopes, account: account)
        
        applicationContext.acquireTokenSilent(with: parameters) { (result, error) in    
            if let error = error {
                let nsError = error as NSError
                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 through self.accessToken, your app can use this value in the HTTP header to make an authorized request to the Microsoft Graph:

header key value
Authorization Bearer

Add the following to ViewController.swift:

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

Learn more about the Microsoft Graph API

Use MSAL for Sign-out

Next up, we'll add support for sign-out to our app.

It's important to note, sign-out with MSAL removes all known information about a user from this application, but the user will still have an active session on their device. If the user attempts to sign in again they may see interaction, but may not need to re-enter their credentials due to the device session being active.

To add sign-out, copy the following method into your ViewController.swift:

    @objc func signOut(_ 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)")
        }
    }

Enable token caching

By default, MSAL caches your app's tokens in the iOS keychain.

To enable token caching, go to your Xcode Project Settings > Capabilities tab > Enable Keychain Sharing > Click Plus > Enter com.microsoft.adalcache.

Add helper methods

Add these helper methods to complete the sample:

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

Multi-account applications

This app is built for a single account scenario. MSAL supports multi-account scenarios as well, but it requires some additional work from apps. You will need to create UI to help user's 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 via the getAllAccounts(...) method.

Test your app

Run locally

If you have followed the code above, try to build and deploy the app to a test device or emulator. You should be able to sign in and get tokens for Azure AD or personal Microsoft accounts! After a user signing in, this app will display the data returned from the Microsoft Graph /me endpoint.

If you have any issues, feel free to open an issue on this doc or in the MSAL library and let us know.

The first time any user signs into your app, they will be prompted by Microsoft identity to consent to the permissions requested. While most users are capable of consenting, some Azure AD tenants have disabled user consent - requiring admins to consent on behalf of all users. To support this scenario, be sure to register your app's scopes in the Azure portal.

Help and Support

Had any trouble with this tutorial or with the Microsoft identity platform? See Help and support