In this exercise you will extend the application from the previous exercise to support authentication with Azure AD. This is required to obtain the necessary OAuth access token to call the Microsoft Graph. In this step you will integrate the Microsoft Authentication Library library into the application.

  1. Create a new file in the root directory named config.js and add the following code.

    const msalConfig = {
      auth: {
        clientId: 'YOUR_APP_ID_HERE',
        redirectUri: 'http://localhost:8080'
      }
    };
    
    const msalRequest = {
      scopes: [
        'user.read',
        'mailboxsettings.read',
        'calendars.readwrite'
      ]
    }
    

    Replace YOUR_APP_ID_HERE with the application ID from the Application Registration Portal.

    重要

    If you're using source control such as git, now would be a good time to exclude the config.js file from source control to avoid inadvertently leaking your app ID.

  2. Open auth.js and add the following code to the beginning of the file.

    // Create the main MSAL instance
    // configuration parameters are located in config.js
    const msalClient = new msal.PublicClientApplication(msalConfig);
    

Implement sign-in

In this section you'll implement the signIn and signOut functions.

  1. Replace the existing signIn function with the following.

    async function signIn() {
      // Login
      try {
        // Use MSAL to login
        const authResult = await msalClient.loginPopup(msalRequest);
        console.log('id_token acquired at: ' + new Date().toString());
        // Save the account username, needed for token acquisition
        sessionStorage.setItem('msalAccount', authResult.account.username);
        // TEMPORARY
        updatePage(Views.error, {
          message: 'Login successful',
          debug: `Token: ${authResult.accessToken}`
        });
      } catch (error) {
        console.log(error);
        updatePage(Views.error, {
          message: 'Error logging in',
          debug: error
        });
      }
    }
    

    This temporary code will display the access token after a successful login.

    async function signIn() {
      // Login
      try {
        // Use MSAL to login
        const authResult = await msalClient.loginPopup(msalRequest);
        console.log('id_token acquired at: ' + new Date().toString());
        // Save the account username, needed for token acquisition
        sessionStorage.setItem('msalAccount', authResult.account.username);
    
        // Get the user's profile from Graph
        user = await getUser();
        // Save the profile in session
        sessionStorage.setItem('graphUser', JSON.stringify(user));
        updatePage(Views.home);
      } catch (error) {
        console.log(error);
        updatePage(Views.error, {
          message: 'Error logging in',
          debug: error
        });
      }
    }
    
  2. Replace the existing signOut function with the following.

    function signOut() {
      account = null;
      sessionStorage.removeItem('graphUser');
      msalClient.logout();
    }
    
  3. Save your changes and refresh the page. After you sign in, you should see an error box that shows the access token.

Get the user's profile

In this section you'll improve the signIn function to use the access token to get the user's profile from Microsoft Graph.

  1. Add the following function in auth.js to retrieve the user's access token.

    async function getToken() {
      let account = sessionStorage.getItem('msalAccount');
      if (!account){
        throw new Error(
          'User account missing from session. Please sign out and sign in again.');
      }
    
      try {
        // First, attempt to get the token silently
        const silentRequest = {
          scopes: msalRequest.scopes,
          account: msalClient.getAccountByUsername(account)
        };
    
        const silentResult = await msalClient.acquireTokenSilent(silentRequest);
        return silentResult.accessToken;
      } catch (silentError) {
        // If silent requests fails with InteractionRequiredAuthError,
        // attempt to get the token interactively
        if (silentError instanceof msal.InteractionRequiredAuthError) {
          const interactiveResult = await msalClient.acquireTokenPopup(msalRequest);
          return interactiveResult.accessToken;
        } else {
          throw silentError;
        }
      }
    }
    
  2. Create a new file in the root of the project named graph.js and add the following code.

    // Create an authentication provider
    const authProvider = {
      getAccessToken: async () => {
        // Call getToken in auth.js
        return await getToken();
      }
    };
    
    // Initialize the Graph client
    const graphClient = MicrosoftGraph.Client.initWithMiddleware({authProvider});
    

    This code creates an authorization provider that wraps the getToken method in auth.js, and initializes the Graph client with this provider.

  3. Add the following function in graph.js to get the user's profile.

    async function getUser() {
      return await graphClient
        .api('/me')
        // Only get the fields used by the app
        .select('id,displayName,mail,userPrincipalName,mailboxSettings')
        .get();
    }
    
  4. Replace the existing signIn function with the following.

    async function signIn() {
      // Login
      try {
        // Use MSAL to login
        const authResult = await msalClient.loginPopup(msalRequest);
        console.log('id_token acquired at: ' + new Date().toString());
        // Save the account username, needed for token acquisition
        sessionStorage.setItem('msalAccount', authResult.account.username);
    
        // Get the user's profile from Graph
        user = await getUser();
        // Save the profile in session
        sessionStorage.setItem('graphUser', JSON.stringify(user));
        updatePage(Views.home);
      } catch (error) {
        console.log(error);
        updatePage(Views.error, {
          message: 'Error logging in',
          debug: error
        });
      }
    }
    
  5. Save your changes and refresh the page. After you sign in, you should end up back on the home page, but the UI should change to indicate that you are signed-in.

    A screenshot of the home page after signing in

  6. Click the user avatar in the top right corner to access the Sign out link. Clicking Sign out resets the session and returns you to the home page.

    A screenshot of the dropdown menu with the Sign out link

Storing and refreshing tokens

At this point your application has an access token, which is sent in the Authorization header of API calls. This is the token that allows the app to access the Microsoft Graph on the user's behalf.

However, this token is short-lived. The token expires an hour after it is issued. This is where the refresh token becomes useful. The refresh token allows the app to request a new access token without requiring the user to sign in again.

Because the app is using the MSAL library, you do not have to implement any token storage or refresh logic. MSAL caches the token in the browser session. The acquireTokenSilent method first checks the cached token, and if it is not expired, it returns it. If it is expired, it uses the cached refresh token to obtain a new one. The Graph client object calls the getToken method in auth.js on every request, ensuring that it has an up-to-date token.