Sign in users and call the Microsoft Graph API from a JavaScript single-page application (SPA)

This guide demonstrates how a JavaScript single-page application (SPA) can:

  • Sign in personal accounts, as well as work and school accounts
  • Acquire an access token
  • Call the Microsoft Graph API or other APIs that require access tokens from the Microsoft identity platform endpoint

Note

If you are new to the Microsoft identity platform, we recommend you start with the Sign in users and get an access token in a JavaScript SPA quickstart.

How the sample app generated by this guide works

Shows how the sample app generated by this tutorial works

More information

The sample application created by this guide enables a JavaScript SPA to query the Microsoft Graph API or a web API that accepts tokens from the Microsoft identity platform endpoint. In this scenario, after a user signs in, an access token is requested and added to HTTP requests through the authorization header. This token will be used to acquire the user's profile and mails via MS Graph API. Token acquisition and renewal are handled by the Microsoft Authentication Library (MSAL) for JavaScript.

Libraries

This guide uses the following library:

Library Description
msal.js Microsoft Authentication Library for JavaScript

Set up your web server or project

Prefer to download this sample's project instead? Download the project files.

To configure the code sample before you execute it, skip to the configuration step.

Prerequisites

  • To run this tutorial, you need a local web server, such as Node.js, .NET Core, or IIS Express integration with Visual Studio 2017.

  • Instructions in this guide are based on a web server built in Node.js. We recommend using Visual Studio Code as your integrated development environment (IDE).

Create your project

Make sure you have Node.js installed, and then create a folder to host your application. There, we will implement a simple Express web server to serve your index.html file.

  1. First, using Visual Studio Code integrated terminal, locate your project folder, and then install Express using NPM.

  2. Next, create a .js file named server.js, and then add the following code:

    const express = require('express');
    const morgan = require('morgan');
    const path = require('path');
    
    //initialize express.
    const app = express();
    
    // Initialize variables.
    const port = 3000; // process.env.PORT || 3000;
    
    // Configure morgan module to log all requests.
    app.use(morgan('dev'));
    
    // Set the front-end folder to serve public assets.
    app.use(express.static('JavaScriptSPA'))
    
    // Set up a route for index.html.
    app.get('*', function (req, res) {
        res.sendFile(path.join(__dirname + '/index.html'));
    });
    
    // Start the server.
    app.listen(port);
    console.log('Listening on port ' + port + '...');
    

You now have a simple server to serve your SPA. The intended folder structure at the end of this tutorial is as follows:

a text depiction of the intended SPA folder structure

Create the SPA UI

  1. Create an index.html file for your JavaScript SPA. This file implements a UI built with Bootstrap 4 Framework and imports script files for configuration, authentication and API call.

    In the index.html file, add the following code:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <title>Quickstart | MSAL.JS Vanilla JavaScript SPA</title>
    
        <!-- msal.js with a fallback to backup CDN -->
        <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.2.1/js/msal.js" integrity="sha384-9TV1245fz+BaI+VvCjMYL0YDMElLBwNS84v3mY57pXNOt6xcUYch2QLImaTahcOP" crossorigin="anonymous"></script>
        <script type="text/javascript">
          if(typeof Msal === 'undefined')document.write(unescape("%3Cscript src='https://alcdn.msftauth.net/lib/1.2.1/js/msal.js' type='text/javascript' integrity='sha384-m/3NDUcz4krpIIiHgpeO0O8uxSghb+lfBTngquAo2Zuy2fEF+YgFeP08PWFo5FiJ' crossorigin='anonymous'%3E%3C/script%3E"));
        </script>
    
        <!-- adding Bootstrap 4 for UI components  -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
      </head>
      <body>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
          <a class="navbar-brand" href="/">MS Identity Platform</a>
          <div class="btn-group ml-auto dropleft">
            <button type="button" id="signIn" class="btn btn-secondary" onclick="signIn()">Sign In</button>
            <button type="button" id="signOut" class="btn btn-success d-none" onclick="signOut()">Sign Out</button>
        </div>
        </nav>
        <br>
        <h5 class="card-header text-center">Vanilla JavaScript SPA calling MS Graph API with MSAL.JS</h5>
        <br>
        <div class="row" style="margin:auto" >
        <div id="card-div" class="col-md-3 d-none">
        <div class="card text-center">
          <div class="card-body">
            <h5 class="card-title" id="welcomeMessage">Please sign-in to see your profile and read your mails</h5>
            <div id="profile-div"></div>
            <br>
            <br>
            <button class="btn btn-primary" id="seeProfile" onclick="seeProfile()">See Profile</button>
            <br>
            <br>
            <button class="btn btn-primary d-none" id="readMail" onclick="readMail()">Read Mails</button>
          </div>
        </div>
        </div>
        <br>
        <br>
          <div class="col-md-4">
            <div class="list-group" id="list-tab" role="tablist">
            </div>
          </div>
          <div class="col-md-5">
            <div class="tab-content" id="nav-tabContent">
            </div>
          </div>
        </div>
        <br>
        <br>
    
        <!-- importing bootstrap.js and supporting js libraries -->
        <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    
        <!-- importing app scripts (load order is important) -->
        <script type="text/javascript" src="./authConfig.js"></script>
        <script type="text/javascript" src="./graphConfig.js"></script>
        <script type="text/javascript" src="./ui.js"></script>
    
        <!-- replace next line with authRedirect.js if you would like to use the redirect flow -->
        <!-- <script type="text/javascript" src="./authRedirect.js"></script>   -->
        <script type="text/javascript" src="./authPopup.js"></script>
        <script type="text/javascript" src="./graph.js"></script>
      </body>
    </html>
    

    Tip

    You can replace the version of MSAL.js in the preceding script with the latest released version under MSAL.js releases.

  2. Now, create a .js file named ui.js, which will access and update DOM elements, and add the following code:

    // Select DOM elements to work with
    const welcomeDiv = document.getElementById("welcomeMessage");
    const signInButton = document.getElementById("signIn");
    const signOutButton = document.getElementById('signOut');
    const cardDiv = document.getElementById("card-div");
    const mailButton = document.getElementById("readMail");
    const profileButton = document.getElementById("seeProfile");
    const profileDiv = document.getElementById("profile-div");
    
    function showWelcomeMessage(account) {
      // Reconfiguring DOM elements
      cardDiv.classList.remove('d-none');
      welcomeDiv.innerHTML = `Welcome ${account.name}`;
      signInButton.classList.add('d-none');
      signOutButton.classList.remove('d-none');
    }
    
    function updateUI(data, endpoint) {
      console.log('Graph API responded at: ' + new Date().toString());
    
      if (endpoint === graphConfig.graphMeEndpoint) {
        const title = document.createElement('p');
        title.innerHTML = "<strong>Title: </strong>" + data.jobTitle;
        const email = document.createElement('p');
        email.innerHTML = "<strong>Mail: </strong>" + data.mail;
        const phone = document.createElement('p');
        phone.innerHTML = "<strong>Phone: </strong>" + data.businessPhones[0];
        const address = document.createElement('p');
        address.innerHTML = "<strong>Location: </strong>" + data.officeLocation;
        profileDiv.appendChild(title);
        profileDiv.appendChild(email);
        profileDiv.appendChild(phone);
        profileDiv.appendChild(address);
    
      } else if (endpoint === graphConfig.graphMailEndpoint) {
          if (data.value.length < 1) {
            alert("Your mailbox is empty!")
          } else {
            const tabList = document.getElementById("list-tab");
            tabList.innerHTML = ''; // clear tabList at each readMail call
            const tabContent = document.getElementById("nav-tabContent");
    
            data.value.map((d, i) => {
              // Keeping it simple
              if (i < 10) {
                const listItem = document.createElement("a");
                listItem.setAttribute("class", "list-group-item list-group-item-action")
                listItem.setAttribute("id", "list" + i + "list")
                listItem.setAttribute("data-toggle", "list")
                listItem.setAttribute("href", "#list" + i)
                listItem.setAttribute("role", "tab")
                listItem.setAttribute("aria-controls", i)
                listItem.innerHTML = d.subject;
                tabList.appendChild(listItem)
    
                const contentItem = document.createElement("div");
                contentItem.setAttribute("class", "tab-pane fade")
                contentItem.setAttribute("id", "list" + i)
                contentItem.setAttribute("role", "tabpanel")
                contentItem.setAttribute("aria-labelledby", "list" + i + "list")
                contentItem.innerHTML = "<strong> from: " + d.from.emailAddress.address + "</strong><br><br>" + d.bodyPreview + "...";
                tabContent.appendChild(contentItem);
              }
            });
          }
      }
    }
    

Register your application

Before proceeding further with authentication, register your application on Azure Active Directory.

  1. Sign in to the Azure portal.
  2. If your account gives you access to more than one tenant, select the account at the upper right, and then set your portal session to the Azure AD tenant that you want to use.
  3. Go to the Microsoft identity platform for developers App registrations page.
  4. When the Register an application page appears, enter a name for your application.
  5. Under Supported account types, select Accounts in any organizational directory and personal Microsoft accounts.
  6. In the Redirect URI section, select the Web platform from the drop-down list, and then set the value to the application URL that's based on your web server.
  7. Select Register.
  8. On the app Overview page, note the Application (client) ID value for later use.
  9. This quickstart requires the Implicit grant flow to be enabled. In the left pane of the registered application, select Authentication.
  10. In Advanced settings, under Implicit grant, select the ID tokens and Access tokens check boxes. ID tokens and access tokens are required because this app must sign in users and call an API.
  11. Select Save.

Set a redirect URL for Node.js

For Node.js, you can set the web server port in the server.js file. This tutorial uses port 3000, but you can use any other available port.

To set up a redirect URL in the application registration information, switch back to the Application Registration pane, and do either of the following:

  • Set http://localhost:3000/ as the Redirect URL.
  • If you're using a custom TCP port, use http://localhost:<port>/ (where <port> is the custom TCP port number).
    1. Copy the URL value.
    2. Switch back to the Application Registration pane, and paste the copied value as the Redirect URL.

Configure your JavaScript SPA

Create a new .js file named authConfig.js, which will contain your configuration parameters for authentication, and add the following code:

  const msalConfig = {
    auth: {
      clientId: "Enter_the_Application_Id_Here",
      authority: "Enter_the_Cloud_Instance_Id_HereEnter_the_Tenant_Info_Here",
      redirectUri: "Enter_the_Redirect_Uri_Here",
    },
    cache: {
      cacheLocation: "sessionStorage", // This configures where your cache will be stored
      storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
    }
  };

  // Add here scopes for id token to be used at MS Identity Platform endpoints.
  const loginRequest = {
    scopes: ["openid", "profile", "User.Read"]
  };

  // Add here scopes for access token to be used at MS Graph API endpoints.
  const tokenRequest = {
    scopes: ["Mail.Read"]
  };

Where:

  • <Enter_the_Application_Id_Here> is the Application (client) ID for the application you registered.
  • <Enter_the_Cloud_Instance_Id_Here> is the instance of the Azure cloud. For the main or global Azure cloud, simply enter https://login.microsoftonline.com. For national clouds (for example, China), see National clouds.
  • <Enter_the_Tenant_info_here> is set to one of the following options:
    • If your application supports accounts in this organizational directory, replace this value with the Tenant ID or Tenant name (for example, contoso.microsoft.com).
    • If your application supports accounts in any organizational directory, replace this value with organizations.
    • If your application supports accounts in any organizational directory and personal Microsoft accounts, replace this value with common. To restrict support to personal Microsoft accounts only, replace this value with consumers.

Use the Microsoft Authentication Library (MSAL) to sign in the user

Create a new .js file named authPopup.js, which will contain your authentication and token acquisition logic, and add the following code:

const myMSALObj = new Msal.UserAgentApplication(msalConfig);

function signIn() {
  myMSALObj.loginPopup(loginRequest)
    .then(loginResponse => {
      console.log('id_token acquired at: ' + new Date().toString());
      console.log(loginResponse);

      if (myMSALObj.getAccount()) {
        showWelcomeMessage(myMSALObj.getAccount());
      }
    }).catch(error => {
      console.log(error);
    });
}

function signOut() {
  myMSALObj.logout();
}

function callMSGraph(theUrl, accessToken, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200) {
           callback(JSON.parse(this.responseText));
        }
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xmlHttp.send();
}

function getTokenPopup(request) {
  return myMSALObj.acquireTokenSilent(request)
    .catch(error => {
      console.log(error);
      console.log("silent token acquisition fails. acquiring token using popup");

      // fallback to interaction when silent call fails
        return myMSALObj.acquireTokenPopup(request)
          .then(tokenResponse => {
            return tokenResponse;
          }).catch(error => {
            console.log(error);
          });
    });
}

function seeProfile() {
  if (myMSALObj.getAccount()) {
    getTokenPopup(loginRequest)
      .then(response => {
        callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI);
        profileButton.classList.add('d-none');
        mailButton.classList.remove('d-none');
      }).catch(error => {
        console.log(error);
      });
  }
}

function readMail() {
  if (myMSALObj.getAccount()) {
    getTokenPopup(tokenRequest)
      .then(response => {
        callMSGraph(graphConfig.graphMailEndpoint, response.accessToken, updateUI);
      }).catch(error => {
        console.log(error);
      });
  }
}

More information

After a user selects the Sign In button for the first time, the signIn method calls loginPopup to sign in the user. This method opens a pop-up window with the Microsoft identity platform endpoint to prompt and validate the user's credentials. After a successful sign-in, the user is redirected back to the original index.html page. A token is received, processed by msal.js, and the information contained in the token is cached. This token is known as the ID token and contains basic information about the user, such as the user display name. If you plan to use any data provided by this token for any purposes, you need to make sure this token is validated by your backend server to guarantee that the token was issued to a valid user for your application.

The SPA generated by this guide calls acquireTokenSilent and/or acquireTokenPopup to acquire an access token used to query the Microsoft Graph API for user profile info. If you need a sample that validates the ID token, take a look at this sample application in GitHub. The sample uses an ASP.NET web API for token validation.

Get a user token interactively

After the initial sign-in, you do not want to ask users to reauthenticate every time they need to request a token to access a resource. So acquireTokenSilent should be used most of the time to acquire tokens. There are situations, however, where you need to force users to interact with Microsoft identity platform endpoint. Examples include:

  • Users need to reenter their credentials because the password has expired.
  • Your application is requesting access to a resource, and you need the user's consent.
  • Two-factor authentication is required.

Calling acquireTokenPopup opens a pop-up window (or acquireTokenRedirect redirects users to the Microsoft identity platform endpoint). In that window, users need to interact by confirming their credentials, giving consent to the required resource, or completing the two-factor authentication.

Get a user token silently

The acquireTokenSilent method handles token acquisition and renewal without any user interaction. After loginPopup (or loginRedirect) is executed for the first time, acquireTokenSilent is the method commonly used to obtain tokens used to access protected resources for subsequent calls. (Calls to request or renew tokens are made silently.) acquireTokenSilent may fail in some cases. For example, the user's password may have expired. Your application can handle this exception in two ways:

  1. Make a call to acquireTokenPopup immediately, which triggers a user sign-in prompt. This pattern is commonly used in online applications where there is no unauthenticated content in the application available to the user. The sample generated by this guided setup uses this pattern.

  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 commonly used when the user can use other functionality of the application without being disrupted. For example, there might be unauthenticated content available in the application. In this situation, the user can decide when they want to sign in to access the protected resource, or to refresh the outdated information.

Note

This quickstart uses the loginPopup and acquireTokenPopup methods by default. If you are using Internet Explorer as your browser, it is recommended to use loginRedirect and acquireTokenRedirect methods, due to a known issue related to the way Internet Explorer handles pop-up windows. If you would like to see how to achieve the same result using Redirect methods, please see.

Call the Microsoft Graph API by using the token you just acquired

  1. First, create a .js file named graphConfig.js, which will store your REST endpoints. Add the following code:

       const graphConfig = {
         graphMeEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me",
         graphMailEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me/messages"
       };
    

    Where:

    • <Enter_the_Graph_Endpoint_Here> is the instance of MS Graph API. For the global MS Graph API endpoint, simply replace this string with https://graph.microsoft.com. For national cloud deployments, please refer to Graph API Documentation.
  2. Next, create a .js file named graph.js, which will make a REST call to Microsoft Graph API, and add the following code:

    function callMSGraph(endpoint, token, callback) {
      const headers = new Headers();
      const bearer = `Bearer ${token}`;
    
      headers.append("Authorization", bearer);
    
      const options = {
          method: "GET",
          headers: headers
      };
    
      console.log('request made to Graph API at: ' + new Date().toString());
    
      fetch(endpoint, options)
        .then(response => response.json())
        .then(response => callback(response, endpoint))
        .catch(error => console.log(error))
    }
    

More information about making a REST call against a protected API

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

Test your code

  1. Configure the server to listen to a TCP port that's based on the location of your index.html file. For Node.js, start the web server to listen to the port by running the following commands at a command-line prompt from the application folder:

    npm install
    npm start
    
  2. In your browser, enter http://localhost:3000 or http://localhost:{port}, where port is the port that your web server is listening to. You should see the contents of your index.html file and the Sign In button.

Test your application

After the browser loads your index.html file, select Sign In. You're prompted to sign in with the Microsoft identity platform endpoint:

The JavaScript SPA account sign-in window

The first time that you sign in to your application, you're prompted to grant it access to your profile and sign you in:

The "Permissions requested" window

View application results

After you sign in, your user profile information is returned in the Microsoft Graph API response that's displayed:

Results from the Microsoft Graph API call

More information about scopes and delegated permissions

The Microsoft Graph API requires the user.read scope to read a user's profile. By default, this scope is automatically added 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. For example, the Microsoft Graph API requires the Mail.Read scope in order to list the user’s mails.

Note

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

If a back-end API doesn't require a scope (not recommended), you can use clientId as the scope in the calls to acquire tokens.

Help and support

If you need help, want to report an issue, or would like to learn about your support options, see Help and support for developers.