Update app to create and list SharePoint Embedded Containers

Completed

In this exercise, you'll update the existing project to create and retrieve SharePoint Embedded Containers.

Add sign in functionality to the front-end React SPA

Let's start by adding support for user sign in to the React SPA front-end project. This involves adding some configuration code for the Microsoft Authentication Library (MSAL) and configuring the Microsoft Graph Toolkit with the necessary sign in details.

Set up the authentication provider

Locate and open the ./src/index.tsx file.

Add the following imports after the existing imports:

import { Providers } from "@microsoft/mgt-element";
import { Msal2Provider } from "@microsoft/mgt-msal2-provider";
import * as Constants from "./common/constants"
import * as Scopes from "./common/scopes";

Finally, add the following code before the React rendering code to configure a global MSAL provider for the Microsoft Graph Toolkit:

Providers.globalProvider = new Msal2Provider({
  clientId: Constants.CLIENT_ENTRA_APP_CLIENT_ID,
  authority: Constants.CLIENT_ENTRA_APP_AUTHORITY,
  scopes: [
    ...Scopes.GRAPH_OPENID_CONNECT_BASIC,
    Scopes.GRAPH_USER_READ_ALL,
    Scopes.GRAPH_FILES_READ_WRITE_ALL,
    Scopes.GRAPH_SITES_READ_ALL,
    Scopes.SPEMBEDDED_FILESTORAGECONTAINER_SELECTED
  ]
});

Update app homepage to handle sign-in and sign out process

Locate and open the ./src/App.tsx file. Remove the import for the logo.svg file, then add and update the remaining imports to reflect the following code:

import React, {
  useState, useEffect
} from "react";
import {
  Providers,
  ProviderState
} from "@microsoft/mgt-element";
import { Login } from "@microsoft/mgt-react";
import {
  FluentProvider,
  Text,
  webLightTheme
} from "@fluentui/react-components"
import './App.css';
import {
  InteractionRequiredAuthError,
  PublicClientApplication
} from "@azure/msal-browser";
import * as Scopes from "./common/scopes";
import * as Constants from "./common/constants";

After the import statements, add the following custom React hook to get the current user's sign-in status:

function useIsSignedIn() {
  const [isSignedIn, setIsSignedIn] = useState(false);

  useEffect(() => {
    const updateState = async () => {
      const provider = Providers.globalProvider;
      setIsSignedIn(provider && provider.state === ProviderState.SignedIn);
    };

    Providers.onProviderUpdated(updateState);
    updateState();

    return () => {
      Providers.removeProviderUpdatedListener(updateState);
    }
  }, []);

  return isSignedIn;
}

Now, update the App by adding the following code after the declaration:

const isSignedIn = useIsSignedIn();

const promptForContainerConsent = async (event: CustomEvent<undefined>): Promise<void> => {
  const containerScopes = {
    scopes: [Scopes.SPEMBEDDED_FILESTORAGECONTAINER_SELECTED],
    redirectUri: `${window.location.protocol}://${window.location.hostname}${(window.location.port === '80' || window.location.port === '443') ? '' : ':' + window.location.port}`
  };

  const msalInstance = new PublicClientApplication({
    auth: {
      clientId: Constants.CLIENT_ENTRA_APP_CLIENT_ID,
      authority: Constants.CLIENT_ENTRA_APP_AUTHORITY,
    },
    cache: {
      cacheLocation: 'localStorage',
      storeAuthStateInCookie: false,
    },
  });

  msalInstance.acquireTokenSilent(containerScopes)
    .then(response => {
      console.log('tokenResponse', JSON.stringify(response));
    })
    .catch(async (error) => {
      if (error instanceof InteractionRequiredAuthError) {
        return msalInstance.acquireTokenPopup(containerScopes);
      }
    });
}

This code will first get the current sign-in status of the user and then configure and obtain an access token once the user selects a button in the rendering.

Lastly, update the component's rendering by replacing the component's return() statement with the following:

return (
  <FluentProvider theme={webLightTheme}>
    <div className="App">
      <Text size={900} weight='bold'>Sample SPA SharePoint Embedded App</Text>
      <Login loginCompleted={promptForContainerConsent} />
      <div>
      </div>
    </div>
  </FluentProvider>
);

Test the authentication for the React app

Now let's test the client-side React app to ensure the authentication is working.

From the command line in the root folder of the project, run the following command:

npm run start

The script will build the server-side & client-side projects, start them up, and launch a browser to the client-side project:

Screenshot of the React app.

Select the Sign In button and sign in using the Work and School account with admin access to your Microsoft 365 tenant.

After signing in successfully, you'll be redirected back to your React app displaying the name and email of the signed-in user:

Screenshot of the React app with a signed in user.

Stop the server by pressing CTRL + C in the console.

Add the ability to list and select Containers

With the basic project setup and configured to support user authentication, let's now add support to list and select Containers in your tenant's partition.

Container management is a privileged operation that requires an access token that must be obtained server side. Let's start by first creating the server-side API parts to support the React app.

Add utility method to retrieve an OBO token to call Microsoft Graph

We first need a utility file to obtain a token using the OAuth2 On-Behalf-Of flow using our existing credential.

Create a new file ./server/auth.ts and add the following code to it:

import { ConfidentialClientApplication } from "@azure/msal-node";
require('isomorphic-fetch');
import * as MSGraph from '@microsoft/microsoft-graph-client';
import * as Scopes from './common/scopes';

export const getGraphToken = async (confidentialClient: ConfidentialClientApplication, token: string): Promise<[boolean, string | any]> => {
  try {
    const graphTokenRequest = {
      oboAssertion: token,
      scopes: [
        Scopes.GRAPH_SITES_READ_ALL,
        Scopes.SPEMBEDDED_FILESTORAGECONTAINER_SELECTED
      ]
    };
    const oboGraphToken = (await confidentialClient.acquireTokenOnBehalfOf(graphTokenRequest))!.accessToken;
    return [true, oboGraphToken];
  } catch (error: any) {
    const errorResult = {
      status: 500,
      body: JSON.stringify({
        message: `Unable to generate Microsoft Graph OBO token: ${error.message}`,
        providedToken: token
      })
    };
    return [false, errorResult];
  }
}

This will take a configured ConfidentialClientApplication and the user's ID token and use the MSAL library to request a new token we can use to call Microsoft Graph.

Add Container management to the server-side API project

Now, let's create the handler to get a list of the Containers using Microsoft Graph to be returned back to our React app. Create a new file ./server/listContainers.ts and add the following imports to it:

import {
  Request,
  Response
} from "restify";
import * as MSAL from "@azure/msal-node";
require('isomorphic-fetch');
import * as MSGraph from '@microsoft/microsoft-graph-client';
import { getGraphToken } from "./auth";

Next, add the following code to create an instance of an MSAL ConfidentialClientApplication that will be used to obtain the OBO access token:

const msalConfig: MSAL.Configuration = {
  auth: {
    clientId: process.env['API_ENTRA_APP_CLIENT_ID']!,
    authority: process.env['API_ENTRA_APP_AUTHORITY']!,
    clientSecret: process.env['API_ENTRA_APP_CLIENT_SECRET']!
  },
  system: {
    loggerOptions: {
      loggerCallback(loglevel: any, message: any, containsPii: any) {
        console.log(message);
      },
      piiLoggingEnabled: false,
      logLevel: MSAL.LogLevel.Verbose,
    }
  }
};

const confidentialClient = new MSAL.ConfidentialClientApplication(msalConfig);

Create and export a new function that will do the following:

  • Verify the request includes an Authorization header with an access token it.
  • Use that use that token and the ConfidentialClientApplication to obtain an OBO token we can use to call Microsoft Graph.
  • Use the OBO token to create a new AuthenticationProvider client we'll use to call Microsoft Graph.
export const listContainers = async (req: Request, res: Response) => {
  if (!req.headers.authorization) {
    res.send(401, { message: 'No access token provided.' });
    return;
  }

  const [bearer, token] = (req.headers.authorization || '').split(' ');

  const [graphSuccess, oboGraphToken] = await getGraphToken(confidentialClient, token);

  if (!graphSuccess) {
    res.send(200, oboGraphToken);
    return;
  }

  const authProvider = (callback: MSGraph.AuthProviderCallback) => {
    callback(null, oboGraphToken);
  };
}

The last step is to create the Microsoft Graph client and request all Containers that have a specific ContainerTypeId set. Add the following code immediacy before the function's closing bracket:

try {
  const graphClient = MSGraph.Client.init({
    authProvider: authProvider,
    defaultVersion: 'beta'
  });

  const graphResponse = await graphClient.api(`storage/fileStorage/containers?$filter=containerTypeId eq ${process.env["CONTAINER_TYPE_ID"]}`).get();

  res.send(200, graphResponse);
  return;
} catch (error: any) {
  res.send(500, { message: `Unable to list containers: ${error.message}` });
  return;
}

Add this new endpoint to our restify server. Locate and open the ./server/index.ts file, add a single import statement to the end of the existing imports, and add a listener for HTTP GET requests to the /api/listContainers endpoint:

import { listContainers } from "./listContainers";
...

server.get('/api/listContainers', async (req, res, next) => {
  try {
    const response = await listContainers(req, res);
    res.send(200, response)
  } catch (error: any) {
    res.send(500, { message: `Error in API server: ${error.message}` });
  }
  next();
});

Update the React project to display our Containers

With the server-side API setup, we can update our React project to provide users an interface to select an existing Container, or create a new Container.

Start by creating a new interface, ./src/common/IContainer.ts, with the following contents to represent the object we'll send and receive from Microsoft Graph calls:

export interface IContainer {
  id: string;
  displayName: string;
  containerTypeId: string;
  createdDateTime: string;
}

Create a new service that will be used to call our API endpoint or make direct calls from the React app to the Microsoft Graph. Create a new file ./src/services/spembedded.ts and add the following code to it:

import { Providers, ProviderState } from '@microsoft/mgt-element';
import * as Msal from '@azure/msal-browser';
import * as Constants from './../common/constants';
import * as Scopes from './../common/scopes';
import { IContainer } from './../common/IContainer';

export default class SpEmbedded {
}

Next, add a utility function to obtain an access token we can use to call Microsoft Graph. This will create an MSAL PublicClientApplication that we'll use to submit to call our server-side API. Add the following function to the SharePoint Embedded class:

async getApiAccessToken() {
  const msalConfig: Msal.Configuration = {
    auth: {
      clientId: Constants.CLIENT_ENTRA_APP_CLIENT_ID,
      authority: Constants.CLIENT_ENTRA_APP_AUTHORITY,
    },
    cache: {
      cacheLocation: 'localStorage',
      storeAuthStateInCookie: false
    }
  };

  const scopes: Msal.SilentRequest = {
    scopes: [`api://${Constants.CLIENT_ENTRA_APP_CLIENT_ID}/${Scopes.SPEMBEDDED_CONTAINER_MANAGE}`],
    prompt: 'select_account',
    redirectUri: `${window.location.protocol}//${window.location.hostname}${(window.location.port === '80' || window.location.port === '443') ? '' : ':' + window.location.port}`
  };

  const publicClientApplication = new Msal.PublicClientApplication(msalConfig);
  await publicClientApplication.initialize();

  let tokenResponse;
  try {
    tokenResponse = await publicClientApplication.acquireTokenSilent(scopes);
    return tokenResponse.accessToken;
  } catch (error) {
    if (error instanceof Msal.InteractionRequiredAuthError) {
      tokenResponse = await publicClientApplication.acquireTokenPopup(scopes);
      return tokenResponse.accessToken;
    }
    console.log(error)
    return null;
  }
};

Add the following listContainers() method that will call our server-side API to get a list of all our Containers. This will get the access token our utility method returns and include it in calls to the server-side API. Recall that this access token is used to create an MSAL ConfidentialClientApplication to obtain an OBO token to call Microsoft Graph. That OBO token has more permissions granted to it that we can only get from a server-side call:

async listContainers(): Promise<IContainer[] | undefined> {
  const api_endpoint = `${Constants.API_SERVER_URL}/api/listContainers`;

  if (Providers.globalProvider.state === ProviderState.SignedIn) {
    const token = await this.getApiAccessToken();
    const containerRequestHeaders = {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    };
    const containerRequestOptions = {
      method: 'GET',
      headers: containerRequestHeaders
    };
    const response = await fetch(api_endpoint, containerRequestOptions);

    if (response.ok) {
      const containerResponse = await response.json();
      return (containerResponse.value)
        ? (containerResponse.value) as IContainer[]
        : undefined;
    } else {
      console.error(`Unable to list Containers: ${JSON.stringify(response)}`);
      return undefined;
    }
  }
};

Now, create a new React component that will handle all our Container tasks and UI. Create a new file, ./src/components/containers.tsx, and add the following code to it:

import React, { useEffect, useState } from 'react';
import {
  Button,
  Dialog, DialogActions, DialogContent, DialogSurface, DialogBody, DialogTitle, DialogTrigger,
  Dropdown, Option,
  Input, InputProps, InputOnChangeData,
  Label,
  Spinner,
  makeStyles, shorthands, useId
} from '@fluentui/react-components';
import type {
  OptionOnSelectData,
  SelectionEvents
} from '@fluentui/react-combobox'
import { IContainer } from "./../common/IContainer";
import SpEmbedded from '../services/spembedded';

const spe = new SpEmbedded();

const useStyles = makeStyles({
  root: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    ...shorthands.padding('25px'),
  },
  containerSelector: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    rowGap: '10px',
    ...shorthands.padding('25px'),
  },
  containerSelectorControls: {
    width: '400px',
  },
  dialogContent: {
    display: 'flex',
    flexDirection: 'column',
    rowGap: '10px',
    marginBottom: '25px'
  }
});

export const Containers = (props: any) => {
  // BOOKMARK 1 - constants & hooks

  // BOOKMARK 2 - handlers go here

  // BOOKMARK 3 - component rendering
  return (
  );
}

export default Containers;

Note

Notice the // BOOKMARK # comments in the component. These to ensure you're adding code in the correct places.

The first step is to get a list of the Containers. Start by adding the following code before // BOOKMARK 1. This will set a few state values to hold the Containers retrieved from our server-side API:

const [containers, setContainers] = useState<IContainer[]>([]);
const [selectedContainer, setSelectedContainer] = useState<IContainer | undefined>(undefined);
const containerSelector = useId('containerSelector');

Next, add the following React hook after // BOOKMARK 1 to get all Containers when the page loads and set the state object that will keep track of them:

useEffect(() => {
  (async () => {
    const containers = await spe.listContainers();
    if (containers) {
      setContainers(containers);
    }
  })();
}, []);

Create an event handler after the React hook implementation you just added that will fire when a user selects a Container from the dropdown control we'll add to our UX:

const onContainerDropdownChange = (event: SelectionEvents, data: OptionOnSelectData) => {
  const selected = containers.find((container) => container.id === data.optionValue);
  setSelectedContainer(selected);
};

Update the rendering by adding the following to the return() method after the // BOOKMARK 3 code. This will create a DropDown control and a placeholder where we'll add a list of the contents in the selected Container:

<div className={styles.root}>
  <div className={styles.containerSelector}>
    <Dropdown
      id={containerSelector}
      placeholder="Select a Storage Container"
      className={styles.containerSelectorControls}
      onOptionSelect={onContainerDropdownChange}>
      {containers.map((option) => (
        <Option key={option.id} value={option.id}>{option.displayName}</Option>
      ))}
    </Dropdown>
  </div>
  {selectedContainer && (`[[TOOD]] container "${selectedContainer.displayName}" contents go here`)}
</div>

This code uses a styling object we added to the component when we initially created it. To use the styles, add the following code immediately before the return() statement and // BOOKMARK 3 comment:

const styles = useStyles();

The last step is to add our new Containerscomponent to the app. Locate and open the ./src/App.tsx file and add the following import after the existing imports:

import Containers from "./components/containers";

Locate the <div></div> markup in the component's return(). Replace that markup with the following to add our Containers component only if the user is signed in:

<div>
  {isSignedIn && (<Containers />)}
</div>

Test listing and selecting Containers

Now let's test the client-side React app to see the effects of our changes to list and display Containers in the React app.

From the command line in the root folder of the project, run the following command:

npm run start

When the browser loads, sign-in using the same Work and School account you've been using.

After signing-in, the page will reload and should display a list of Containers if you've previously created any.

Screenshot of the React app listing all our Containers.

When you select a Container, notice the conditional logic will show the placeholder with the name of the Container selected:

Screenshot of the React app after selecting a Container.

Stop the server by pressing CTRL + C in the console.

Add the ability to create new Containers

In this section, you'll update the server-side API and React app to create new contains from our web application.

Let's start by first creating the server-side API parts to support the React app.

Add support to create Containers to the server-side API project

Now let's create the handler to create a Container using Microsoft Graph to be returned back to our React app. Create a new file ./server/createContainer.ts and add the following imports to it:

import {
  Request,
  Response
} from "restify";
import * as MSAL from "@azure/msal-node";
require('isomorphic-fetch');
import * as MSGraph from '@microsoft/microsoft-graph-client';
import { getGraphToken } from "./auth";

Next, add the following code to create an instance of an MSAL ConfidentialClientApplication that will be used to obtain the OBO access token:

const msalConfig: MSAL.Configuration = {
  auth: {
    clientId: process.env['API_ENTRA_APP_CLIENT_ID']!,
    authority: process.env['API_ENTRA_APP_AUTHORITY']!,
    clientSecret: process.env['API_ENTRA_APP_CLIENT_SECRET']!
  },
  system: {
    loggerOptions: {
      loggerCallback(loglevel: any, message: any, containsPii: any) {
        console.log(message);
      },
      piiLoggingEnabled: false,
      logLevel: MSAL.LogLevel.Verbose,
    }
  }
};

const confidentialClient = new MSAL.ConfidentialClientApplication(msalConfig);

Create and export a new function that will do the following:

  • Verify the request includes an Authorization header with an access token it.
  • Use that use that token and the ConfidentialClientApplication to obtain an OBO token we can use to call Microsoft Graph.
  • Use the OBO token to create a new AuthenticationProvider client we'll use to call Microsoft Graph.
export const createContainer = async (req: Request, res: Response) => {
  if (!req.headers.authorization) {
    res.send(401, { message: 'No access token provided.' });
    return;
  }

  const [bearer, token] = (req.headers.authorization || '').split(' ');

  const [graphSuccess, graphTokenRequest] = await getGraphToken(confidentialClient, token);

  if (!graphSuccess) {
    res.send(200, graphTokenRequest);
    return;
  }

  const authProvider = (callback: MSGraph.AuthProviderCallback) => {
    callback(null, graphTokenRequest);
  };
}

The last step is to create the Microsoft Graph client and submit the request to create a new Container set to a specific ContainerTypeId. Add the following code immediacy before the function's closing bracket:

try {
  const graphClient = MSGraph.Client.init({
    authProvider: authProvider,
    defaultVersion: 'beta'
  });

  const containerRequestData = {
    displayName: req.body!.displayName,
    description: (req.body?.description) ? req.body.description : '',
    containerTypeId: process.env["CONTAINER_TYPE_ID"]
  };

  const graphResponse = await graphClient.api(`storage/fileStorage/containers`).post(containerRequestData);

  res.send(200, graphResponse);
  return;
} catch (error: any) {
  res.send(500, { message: `Failed to create container: ${error.message}` });
  return;
}

Add this new endpoint to our restify server. Locate and open the ./server/index.ts file, add a single import statement to the end of the existing imports, and add a listener for HTTP POST requests to the /api/createContainers endpoint:

import { createContainer } from "./createContainer";

...

server.post('/api/createContainer', async (req, res, next) => {
  try {
    const response = await createContainer(req, res);
    res.send(200, response)
  } catch (error: any) {
    res.send(500, { message: `Error in API server: ${error.message}` });
  }
  next();
});

Update the React project to create a new Container

Locate and open the file ./src/services/spembedded.ts and add the following code the class:

async createContainer(containerName: string, containerDescription: string = ''): Promise<IContainer | undefined> {
  const api_endpoint = `${Constants.API_SERVER_URL}/api/createContainer`;

  if (Providers.globalProvider.state === ProviderState.SignedIn) {
    const token = await this.getApiAccessToken();
    const containerRequestHeaders = {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    };

    const containerRequestData = {
      displayName: containerName,
      description: containerDescription
    };
    const containerRequestOptions = {
      method: 'POST',
      headers: containerRequestHeaders,
      body: JSON.stringify(containerRequestData)
    };

    const response = await fetch(api_endpoint, containerRequestOptions);

    if (response.ok) {
      const containerResponse = await response.json();
      return containerResponse as IContainer;
    } else {
      console.error(`Unable to create container: ${JSON.stringify(response)}`);
      return undefined;
    }
  }
};

This new method is similar to the existing listContainers() method, except it creates a new object and submits it as a POST to our server-side API.

The last step is to update our Containers component to update the UI to support creating a Container. Locate and open the ./src/components/containers.tsx file.

For this step, we'll use a Fluent UI React Dialog component. Start by adding the following state objects and UI component ID objects immediately before the // BOOKMARK 1 comment:

const [dialogOpen, setDialogOpen] = useState(false);
const containerName = useId('containerName');
const [name, setName] = useState('');
const containerDescription = useId('containerDescription');
const [description, setDescription] = useState('');
const [creatingContainer, setCreatingContainer] = useState(false);

Next, add the following code immediately before the // BOOKMARK 2 comment. These handlers are used to update the name and description properties for the new Container from the Input components that will be in the Dialog. They're also used to handle with the user selects a button to create the Container:

const handleNameChange: InputProps["onChange"] = (event: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData): void => {
  setName(data?.value);
};

const handleDescriptionChange: InputProps["onChange"] = (event: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData): void => {
  setDescription(data?.value);
};

const onContainerCreateClick = async (event: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
  setCreatingContainer(true);
  const newContainer = await spe.createContainer(name, description);

  if (newContainer) {
    setName('');
    setDescription('');
    setContainers(current => [...current, newContainer]);
    setSelectedContainer(newContainer);
    setDialogOpen(false);
  } else {
    setName('');
    setDescription('');
  }
  setCreatingContainer(false);
}

Finally, add the following React code immediately after the closing </Dropdown> element. This will create a Fluent UI React Dialog component that's triggered from a button:

<Dialog open={dialogOpen} onOpenChange={(event, data) => setDialogOpen(data.open)}>

  <DialogTrigger disableButtonEnhancement>
    <Button className={styles.containerSelectorControls} appearance='primary'>Create a new storage Container</Button>
  </DialogTrigger>

  <DialogSurface>
    <DialogBody>
      <DialogTitle>Create a new storage Container</DialogTitle>

      <DialogContent className={styles.dialogContent}>
        <Label htmlFor={containerName}>Container name:</Label>
        <Input id={containerName} className={styles.containerSelectorControls} autoFocus required
          value={name} onChange={handleNameChange}></Input>
        <Label htmlFor={containerDescription}>Container description:</Label>
        <Input id={containerDescription} className={styles.containerSelectorControls} autoFocus required
          value={description} onChange={handleDescriptionChange}></Input>
        {creatingContainer &&
          <Spinner size='medium' label='Creating storage Container...' labelPosition='after' />
        }
      </DialogContent>

      <DialogActions>
        <DialogTrigger disableButtonEnhancement>
          <Button appearance="secondary" disabled={creatingContainer}>Cancel</Button>
        </DialogTrigger>
        <Button appearance="primary"
          value={name}
          onClick={onContainerCreateClick}
          disabled={creatingContainer || (name === '')}>Create storage Container</Button>
      </DialogActions>
    </DialogBody>
  </DialogSurface>

</Dialog>

Test creating new Containers

Now let's test the client-side React app to see the effects of our changes to create Containers in the React app.

From the command line in the root folder of the project, run the following command:

npm run start

When the browser loads, sign-in using the same Work and School account you've been using.

After signing-in, the page will reload and should now include a button to launch the dialog:

Screenshot of the React app with a button to launch the dialog.

Select the Create a new storage Container button to open the dialog. Notice how the button is disabled until you enter a name:

Screenshot of the dialog to create a new Container.

Enter a name and description for the Container and select the Create storage Container. While creating the Container, the buttons are disabled and a Spinner control shows the user it's working:

Screenshot of the dialog creating the Container.

Once the dialog disappears, you'll see the selector new displays our new Container!

Screenshot showing the updated selector with our new Container.

Stop the server by pressing CTRL + C in the console.

Summary

In this exercise, you updated the existing project to create and retrieve SharePoint Embedded Containers.

Check your knowledge

1.

What is the purpose of the ConfidentialClientApplication in the server-side API?

2.

Which operation can't be performed directly by the React application and requires the server-side API?