Edit

Consume the Microsoft Graph in the SharePoint Framework

Note

This walkthrough contains older generated React and property-pane code. MSGraphClientV3 requires SPFx 1.15 or later in SharePoint Online; the earlier 1.4.1 API introduction isn't the minimum version for these snippets. For SPFx 1.24, use React 18.3.1 and preserve the generated root and disposal method as described in the React 18 upgrade guide. The legacy render call below isn't a current React 18 example.

Consuming REST APIs secured with Azure Active Directory (Azure AD) and Open Authorization (OAuth 2.0) from within a SharePoint Framework client-side web part or extension is a common enterprise-level business scenario.

Introduced in v1.4.1, you can use the SharePoint Framework to consume Microsoft Graph REST APIs, or any other REST API that's registered in Azure AD.

In this article, you'll learn how to create a SharePoint Framework solution that uses the Microsoft Graph API with a custom set of permissions. For a conceptual overview of this technology, see Connect to Azure AD-secured APIs in SharePoint Framework solutions.

Important

Using the Microsoft Graph API with SharePoint Framework directly using Microsoft Authentication Library for JavaScript is not supported with SPFx version 1.4.1 and beyond. Please use the SharePoint Framework provided native MSGraphClientV3 for the Microsoft Graph API operations.

Solution overview

The steps in this article show you how to build a client-side web part that enables searching for users in the current tenant, as shown in the following screenshot. The search is based on Microsoft Graph and requires at least the User.ReadBasic.All permission.

A client-side web part that has a text box and search button for searching for users within a tenant

The client-side web part enables searching for users based on their name, and provides all the matching users through a DetailsList Office UI Fabric component. The web part has an option in the property pane to select how to access Microsoft Graph. In versions of the SharePoint Framework starting with v1.4.1, you can access Microsoft Graph by using either the native graph client (MSGraphClient), or the low-level type used to access any Azure AD-secured REST API (AadHttpClient).

Note

To get the historical source code for this solution, see the api-scopes GitHub repo. Its version history targets SPFx 1.10, so compare its package versions before reusing the sample.

If you're already familiar with how to create SharePoint Framework solutions, you can continue to Configure the API permissions requests.

Create the initial solution

For a new SharePoint Online project, use the generator version specified in Set up your development environment. The command below installs the latest published generator; it doesn't upgrade an existing solution.

npm install -g @microsoft/generator-sharepoint

Next, create a new SharePoint Framework solution:

  1. Create a folder in your file system. You'll store the solution source code and move the current path into this folder.

  2. Run the Yeoman generator to scaffold a new solution.

    yo @microsoft/sharepoint
    
  3. When prompted, enter the following values (select the default option for all prompts omitted below):

    • What is your solution name? spfx-api-scopes-tutorial
    • Which type of client-side component to create? Web Part
    • What is your Web part name? GraphConsumer
    • Which template would you like to use? React
  4. Start Visual Studio Code (or your favorite code editor) within the context of the current folder.

    code .
    

Configure the base web part elements

Next, configure the initial elements of the client-side web part.

Configure the custom properties

  1. Create a new source code file under the ./src/webparts/graphConsumer/components folder of the solution

    Call the new file ClientMode.ts and use it to declare a TypeScript enum with the available options for the ClientMode property of the web part.

    export enum ClientMode {
      aad,
      graph,
    }
    
  2. Open the GraphConsumerWebPart.ts file in the ./src/webparts/graphConsumer folder of the solution.

    Change the definition of the IGraphConsumerWebPartProps interface to accept a value of type ClientMode.

    export interface IGraphConsumerWebPartProps {
      clientMode: ClientMode;
    }
    
  3. Update the getPropertyPaneConfiguration() method of the client-side web part to support the choice selection in the property pane. The following example shows the new implementation of the method.

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
      return {
        pages: [
          {
            header: {
              description: strings.PropertyPaneDescription
            },
            groups: [
              {
                groupName: strings.BasicGroupName,
                groupFields: [
                  PropertyPaneChoiceGroup('clientMode', {
                    label: strings.ClientModeLabel,
                    options: [
                      { key: ClientMode.aad, text: "AadHttpClient"},
                      { key: ClientMode.graph, text: "MSGraphClient"},
                    ]
                  }),
                ]
              }
            ]
          }
        ]
      };
    }
    
  4. You need to update the render() method of the client-side web part to create a properly configured instance of the React component for rendering. The following code shows the updated method definition.

    public render(): void {
      const element: React.ReactElement<IGraphConsumerProps > = React.createElement(
        GraphConsumer,
        {
          description: this.properties.description,
          isDarkTheme: this._isDarkTheme,
          environmentMessage: this._environmentMessage,
          hasTeamsContext: !!this.context.sdks.microsoftTeams,
          userDisplayName: this.context.pageContext.user.displayName,
          clientMode: this.properties.clientMode,
          context: this.context,
        }
      );
    
      ReactDom.render(element, this.domElement);
    }
    
  5. For this code to work, you need to add some import statements at the beginning of the GraphConsumerWebPart.ts file, as shown in the following example. Note the import for the PropertyPaneChoiceGroup control, and the import of the ClientMode enum.

    import * as React from "react";
    import * as ReactDom from "react-dom";
    import { Version } from "@microsoft/sp-core-library";
    import {
      BaseClientSideWebPart,
      IPropertyPaneConfiguration,
      PropertyPaneChoiceGroup,
    } from "@microsoft/sp-webpart-base";
    
    import * as strings from "GraphConsumerWebPartStrings";
    import GraphConsumer from "./components/GraphConsumer";
    import { IGraphConsumerProps } from "./components/IGraphConsumerProps";
    import { ClientMode } from "./components/ClientMode";
    

Update the resource strings

To compile the solution, you need to update the mystrings.d.ts file under the ./src/webparts/graphConsumer/loc folder of the solution.

  1. Rewrite the interface that defines the resource strings with the following code.

    declare interface IGraphConsumerWebPartStrings {
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ClientModeLabel: string;
      SearchFor: string;
      SearchForValidationErrorMessage: string;
    }
    
  2. Configure proper values for the newly created resource strings by updating the en-us.js file within the same folder.

    define([], function () {
      return {
        PropertyPaneDescription: "Description",
        BasicGroupName: "Group Name",
        ClientModeLabel: "Client Mode",
        SearchFor: "Search for",
        SearchForValidationErrorMessage: "Invalid value for 'Search for' field",
      };
    });
    

Update the style for the client-side web part

You also need to update the SCSS style file.

Open the GraphConsumer.module.scss under the ./src/webparts/graphConsumer/components folder of the solution. Add the following style classes, right after the .title class:

.form {
  @include ms-font-l;
  @include ms-fontColor-white;
}

label {
  @include ms-fontColor-white;
}
.container {
  max-width: 700px;
  margin: 0px auto;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}

.row {
  @include ms-Grid-row;
  @include ms-fontColor-white;
  
  & {
    background-color: $ms-color-themeDark;
    padding: 20px;
  }
}

.column {
  @include ms-Grid-col;
  @include ms-lg10;
  @include ms-xl8;
  @include ms-xlPush2;
  @include ms-lgPush1;
}

.title {
  @include ms-font-xl;
  @include ms-fontColor-white;
}

Update the React component rendering the web part

Now you can update the GraphConsumer React component under the ./src/webparts/graphConsumer/components folder of the solution.

  1. Update the IGraphConsumerProps.ts file to accept the custom properties required by the web part implementation. The following example shows the updated content of the IGraphConsumerProps.ts file. Notice the import of the ClientMode enum definition, and the import of the WebPartContext type. You'll use that later.

    import { WebPartContext } from "@microsoft/sp-webpart-base";
    import { ClientMode } from "./ClientMode";
    
    export interface IGraphConsumerProps {
       description: string;
       isDarkTheme: boolean;
       environmentMessage: string;
       hasTeamsContext: boolean;
       userDisplayName: string;
       clientMode: ClientMode;
       context: WebPartContext;
    }
    
  2. Create a new interface to hold the React component state. Create a new file in the ./src/webparts/graphConsumer/components folder, and call it IGraphConsumerState.ts. The following is the interface definition.

    import { IUserItem } from "./IUserItem";
    
    export interface IGraphConsumerState {
      users: Array<IUserItem>;
      searchFor: string;
    }
    
  3. Define the IUserItem interface (within a file called IUserItem.ts stored in the ./src/webparts/graphConsumer/components folder). That interface is imported in the state file. The interface is used to define the outline of the users retrieved from the current tenant and bound to the DetailsList in the UI.

    export interface IUserItem {
      displayName: string;
      mail: string;
      userPrincipalName: string;
    }
    
  4. Update the GraphConsumer.tsx file. First, add some import statements to import the types you defined earlier. Notice the import for IGraphConsumerProps, IGraphConsumerState, ClientMode, and IUserItem. There are also some imports for the Office UI Fabric components used to render the UI of the React component.

    import * as strings from "GraphConsumerWebPartStrings";
    import { BaseButton, Button, PrimaryButton } from '@fluentui/react/lib/Button';
    import { TextField } from '@fluentui/react/lib/TextField';
    import { DetailsList, DetailsListLayoutMode, CheckboxVisibility, SelectionMode } from '@fluentui/react/lib/DetailsList';
    import * as React from "react";
    
    import { AadHttpClient, HttpClientResponse, MSGraphClientV3  } from "@microsoft/sp-http";
    import { escape } from "@microsoft/sp-lodash-subset";
    
    import { ClientMode } from "./ClientMode";
    import styles from "./GraphConsumer.module.scss";
    import { IGraphConsumerProps } from "./IGraphConsumerProps";
    import { IGraphConsumerState } from "./IGraphConsumerState";
    import { IUserItem } from "./IUserItem";
    
  5. After the imports, define the outline of the columns for the DetailsList component of Office UI Fabric.

    // Configure the columns for the DetailsList component
    let _usersListColumns = [
      {
        key: "displayName",
        name: "Display name",
        fieldName: "displayName",
        minWidth: 50,
        maxWidth: 100,
        isResizable: true,
      },
      {
        key: "mail",
        name: "Mail",
        fieldName: "mail",
        minWidth: 50,
        maxWidth: 100,
        isResizable: true,
      },
      {
        key: "userPrincipalName",
        name: "User Principal Name",
        fieldName: "userPrincipalName",
        minWidth: 100,
        maxWidth: 200,
        isResizable: true,
      },
    ];
    

    This array is used in the settings of the DetailsList component, as you can see in the render() method of the React component.

  6. Replace this component with the following code.

    public render(): React.ReactElement<IGraphConsumerProps> {
      return (
        <div className={ styles.graphConsumer }>
          <div className={ styles.container }>
            <div className={ styles.row }>
              <div className={ styles.column }>
                <span className={ styles.title }>Search for a user!</span>
                <p className={ styles.form }>
                  <TextField
                      label={ strings.SearchFor }
                      required={ true }
                      onChange={ this._onSearchForChanged }
                      onGetErrorMessage={ this._getSearchForErrorMessage }
                      value={ this.state.searchFor }
                    />
                </p>
                <p className={ styles.form }>
                  <PrimaryButton
                      text='Search'
                      title='Search'
                      onClick={ this._search }
                    />
                </p>
                {
                  (this.state.users != null && this.state.users.length > 0) ?
                    <p className={ styles.form }>
                    <DetailsList
                        items={ this.state.users }
                        columns={ _usersListColumns }
                        setKey='set'
                        checkboxVisibility={ CheckboxVisibility.hidden }
                        selectionMode={ SelectionMode.none }
                        layoutMode={ DetailsListLayoutMode.fixedColumns }
                        compact={ true }
                    />
                  </p>
                  : null
                }
              </div>
            </div>
          </div>
        </div>
      );
    }
    
  7. Update the React component type declaration and add a constructor, as shown in the following example:

    export default class GraphConsumer extends React.Component<IGraphConsumerProps, IGraphConsumerState> {
    
      constructor(props: IGraphConsumerProps, state: IGraphConsumerState) {
        super(props);
    
        // Initialize the state of the component
        this.state = {
          users: [],
          searchFor: ""
        };
      }
    

    There are some validation rules and handling events for the TextField component to collect the search criteria. The following are the method implementations.

    Add these two methods to the end of the GraphConsumer class:

    private _onSearchForChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => {
    
      // Update the component state accordingly to the current user's input
      this.setState({
        searchFor: newValue,
      });
    }
    
    private _getSearchForErrorMessage = (value: string): string => {
      // The search for text cannot contain spaces
      return (value == null || value.length == 0 || value.indexOf(" ") < 0)
        ? ''
        : `${strings.SearchForValidationErrorMessage}`;
    }
    

    The PrimaryButton fires a \_search() function, which determines what client technology to use to consume Microsoft Graph. Add this method to the end of the GraphConsumer class:

    private _search = (event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button, MouseEvent>) : void => {
      console.log(this.props.clientMode);
    
      // Based on the clientMode value search users
      switch (this.props.clientMode)
      {
        case ClientMode.aad:
          this._searchWithAad();
          break;
        case ClientMode.graph:
        this._searchWithGraph();
        break;
      }
    }
    

The DetailsList component instance is rendered in the render() method, in case there are items in the users property of the component's state.

Configure the API permissions requests

To consume Microsoft Graph or any other third-party REST API, you need to explicitly declare the permission requirements from an OAuth perspective in the manifest of your solution.

In the SharePoint Framework v1.4.1 or later, you can do that by configuring the webApiPermissionRequests property in the package-solution.json under the config folder of the solution. The following example shows an excerpt of that file for the current solution.

Copy the declaration of the webApiPermissionRequests property.

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
  "solution": {
    "name": "spfx-api-scopes-tutorial-client-side-solution",
    "id": "841cd609-d821-468d-a6e4-2d207b966cd8",
    "version": "1.0.0.0",
    "includeClientSideAssets": true,
    "skipFeatureDeployment": true,
    "webApiPermissionRequests": [
      {
        "resource": "Microsoft Graph",
        "scope": "User.ReadBasic.All"
      }
    ]
  },
  "paths": {
    "zippedPackage": "solution/spfx-api-scopes-tutorial.sppkg"
  }
}

Notice the webApiPermissionRequests, which is an array of webApiPermissionRequest items. Each item defines the resource and the scope of the permission request.

The resource can be the name or the ObjectId (in Azure AD) of the resource for which you want to configure the permission request. For Microsoft Graph, the name is Microsoft Graph. The ObjectId isn't unique and varies on a per tenant basis.

The scope can be the name of the permission, or the unique ID of that permission. You can get the permission name from the API documentation. You can get the permission ID from the API manifest file.

Note

For a list of the permissions that are available in Microsoft Graph, see Microsoft Graph permissions reference.

Request and approve the least-privileged permission required by the operation; don't depend on historical default grants. This example requests User.ReadBasic.All. For the supported approval process, see Connect to Microsoft Entra ID-secured APIs in SharePoint Framework solutions.

The User.ReadBasic.All permission is sufficient for searching for users and getting their displayName, mail, and userPrincipalName.

When you package and deploy your solution, you (or an admin) will have to grant the requested permissions to your solution. For details, see Deploy the solution and grant permissions.

Consume Microsoft Graph

You can now implement the methods to consume the Microsoft Graph. You have two options:

  • Use the AadHttpClient client object
  • Use the MSGraphClientV3 client object

The AadHttpClient client object is useful for consuming any REST API. You can use it to consume Microsoft Graph or any other third-party (or first-party) REST API.

The MSGraphClientV3 client object can consume the Microsoft Graph only. Internally it uses the AadHttpClient client object and supports the fluent syntax of the Microsoft Graph SDK.

Using AadHttpClient

To consume any REST API using the AadHttpClient client object, create a new instance of the AadHttpClient type by calling the context.aadHttpClientFactory.getClient() method and providing the URI of the target service.

The object created provides methods to make the following requests:

  • get(): makes an HTTP GET request
  • post(): makes an HTTP POST request
  • fetch(): makes any other kind of HTTP request, based on the HttpClientConfiguration and IHttpClientOptions arguments provided.

Because all these methods support the asynchronous development model of JavaScript/TypeScript, you can handle their result with promises.

The following example shows the \_searchWithAad() method of the sample solution.

private _searchWithAad = (): void => {
  // Log the current operation
  console.log("Using _searchWithAad() method");

  // Using Graph here, but any 1st or 3rd party REST API that requires Azure AD auth can be used here.
  this.props.context.aadHttpClientFactory
    .getClient("https://graph.microsoft.com")
    .then((client: AadHttpClient) => {
      // Search for the users with givenName, surname, or displayName equal to the searchFor value
      return client
        .get(
          `https://graph.microsoft.com/v1.0/users?$select=displayName,mail,userPrincipalName&$filter=(givenName%20eq%20'${escape(this.state.searchFor)}')%20or%20(surname%20eq%20'${escape(this.state.searchFor)}')%20or%20(displayName%20eq%20'${escape(this.state.searchFor)}')`,
          AadHttpClient.configurations.v1
        );
    })
    .then((response: HttpClientResponse) => {
        return response.json();
      })
      .then((json: { value: Array<{ displayName: string; mail: string; userPrincipalName: string; }> }) => {

      // Prepare the output array
      var users: Array<IUserItem> = new Array<IUserItem>();

      // Log the result in the console for testing purposes
      console.log(json);

      // Map the JSON response to the output array
      json.value.map((item: any) => {
        users.push( {
          displayName: item.displayName,
          mail: item.mail,
          userPrincipalName: item.userPrincipalName,
        });
      });

      // Update the component state accordingly to the result
      this.setState(
        {
          users: users,
        }
      );
    })
    .catch(error => {
      console.error(error);
    });
}

The get() method gets the URL of the OData request as the input argument. A successful request returns a JSON object with the response.

Using MSGraphClientV3

If you're targeting Microsoft Graph, you can use the MSGraphClientV3 client object, which provides a more fluent syntax.

The following example shows the implementation of the _searchWithGraph() method of the sample solution.

private _searchWithGraph = () : void => {

  // Log the current operation
  console.log("Using _searchWithGraph() method");

  this.props.context.msGraphClientFactory
    .getClient('3')
    .then((client: MSGraphClientV3) => {
      // From https://github.com/microsoftgraph/msgraph-sdk-javascript sample
      client
        .api("users")
        .version("v1.0")
        .select("displayName,mail,userPrincipalName")
        .filter(`(givenName eq '${escape(this.state.searchFor)}') or (surname eq '${escape(this.state.searchFor)}') or (displayName eq '${escape(this.state.searchFor)}')`)
        .get((err, res) => {

          if (err) {
            console.error(err);
            return;
          }

          // Prepare the output array
          var users: Array<IUserItem> = new Array<IUserItem>();

          // Map the JSON response to the output array
          res.value.map((item: any) => {
            users.push( {
              displayName: item.displayName,
              mail: item.mail,
              userPrincipalName: item.userPrincipalName,
            });
          });

          // Update the component state accordingly to the result
          this.setState(
            {
              users: users,
            }
          );
        });
    });
}

You get an instance of the MSGraphClientV3 type by calling the context.msGraphClientFactory.getClient('3') method.

You then use the fluent API of the Microsoft Graph SDK to define the OData query that runs against the target Microsoft Graph endpoint.

The result is a JSON response that you have to decode and map to the typed result.

Note

You can use a fully typed approach by using the Microsoft Graph TypeScript types.

Deploy the solution and grant permissions

You're now ready to build, bundle, package, and deploy the solution.

  1. Run the Heft commands to verify that the solution builds correctly.

    heft build
    
  2. Use the following command to package the solution.

    heft package-solution
    
  3. Browse to the app catalog of your target tenant and upload the solution package. You can find the solution package under the sharepoint/solution folder of your solution. It's the .sppkg file. After you upload the solution package, the app catalog prompts you with a dialog box, similar to the one shown in the following screenshot.

    Screenshot of the app catalog UI when uploading the package solution

    A message in the lower part of the screen tells you that the solution package requires permissions approval. This is because of the webApiPermissionRequests property in the package-solution.json file.

  4. In the modern SharePoint Online Admin Center, in the left quick launch menu, under Advanced select the API access menu item. You'll see a page similar to the following.

    Screenshot of the WebApiPermission management page

    An administrator with the required permissions can approve or deny requests on this page. Review the current API permission-management guidance for roles and tenant-wide scope; don't assume every administrative role can approve every API request.

    Note

    For more information about how the tenant-level permission scopes work internally, see the articles in the See also section.

  5. Choose the permission that you requested in the package-solution.json file of your solution, select Approve or reject access, and then select Approve. The following screenshot shows the panel in the Admin UI.

    Screenshot of the WebApiPermission management page during the approval process

Warning

If permission approval fails, retain the intended Microsoft Graph resource and investigate the error and correlation ID using the current API permission guidance. Don't substitute a different service principal name as a generic workaround.

Test the solution

  1. Run your solution by using the following Heft command.

    heft start --nobrowser
    
  2. Open a test SharePoint page and load local manifests using the SPFx developer toolbar. For example:

    https://contoso.sharepoint.com/sites/dev/SitePages/GraphConsumer.aspx
    
  3. Edit the page, add the GraphConsumer client-side web part, and configure Client Mode. Save the page and leave edit mode to search for users.

    SharePoint Framework manages token acquisition through its native clients. Don't depend on the historical ADAL or implicit-flow implementation, or use the appearance of a pop-up as a success check. Verify the API response and handle authentication or consent errors as described in AadHttpClient guidance.

    Screenshot of the UI of the sample application

And that's it! Now you can build enterprise-level solutions that use Azure AD-secured REST APIs.

See also