SharePoint Framework WebPart & Asp.Net Core WebApi with Azure AD Auth
Tags: dotnet, sharepoint
Published: 2022-03-04 15:38:59

When developing new Webparts for SharePoint or Addins for Office 365 you might want to make use of your own APIs. This will raise the challenge of how to protect you backend and authenticate your users. Since I haven't found any good and complete guidelines on how to setup an SPFX  Webpart together with a C# ASP.Net Core WebApi backend, I wanted to share my approach. 

Setup:
* VS Studio 2019
* VS Code (for SPFX development)
* .Net Core 5
* Azure AD Global Admin or at least `Privileged Role Administrator`
* [SPFX prerequisites (Node v14, Yeoman, generator-sharepoint)](https://docs.microsoft.com/en/sharepoint/dev/spfx/set-up-your-development-environment)

## Create the Backend
Create a new Project using VS 2019. If you are asked for the project template choose `ASP.NET Core Web API`. Click on next when asked for the Target Framework and Authentication Type choose `Microsoft identity platform`:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mw6hai8odmjryjkcziki.png)
After creation of the project you will be prompted for an additional required package called `dotnet msidentity tool`. Just click `finish` to complete the project creation.

### Configure the Microsoft identity platform
To configure the microsoft identity platform to connect zu your Azure AD click on configure at the connected services screen:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2r4ws2drh7tu8qqirdl.png)
A Browser Window will open and you are asked to login to your 365 tenant. If you already have an App registered in Azure you can select an existing one, otherwise just let Visual Studio create one for you:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k7suoxe6mew18drus90j.png)
In the opening dialog just enter the application Name which will be created on the Azure AD side.  
After creation, select the newly created APP. When asked for Additional Configuration you do not need to select anything. Additional Graph or Permissions to another API can be revisited when going to the App Registrations in the Azure Admin Portal. After finishing the dialog and installing required nuget packages you will see that the `Microsoft identity platform` is now configured. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/si27g4gl932jjacq0jm2.png)

By configuring the Microsoft identity platform a few things happend with your project. If you check the `appsettings.json` you will see that there is a configuration part created for `AzureAd` this includes your azure domain, tenant Id and client id. These configuration settings will match the App that has been created on the Azure side. When opening the `Startup.cs` you will find that the authentication was added to the service and app configuration:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/txe2dx4e54go7giuxj2b.png)
For allowing access from other origins we need to configure CORS. To allow all orgins simply add 
```
app.UseCors(x => x
               .AllowAnyMethod()
               .AllowAnyHeader()
               .AllowCredentials()
               .SetIsOriginAllowed(hostName => true));
``` 
to the `Configure(IApplicationBuilder app, IWebHostEnvironment env)` method. Make sure to add the `.UseCors` before the `.UseEndoints` for example:
```
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
            }
            
            app.UseHttpsRedirection();
            app.UseRouting();
            
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseCors(x => x
               .AllowAnyMethod()
               .AllowAnyHeader()
               .AllowCredentials()
               .SetIsOriginAllowed(hostName => true));

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            }); 
        }
```

When checking the `WeatherForecastController.cs` in the `Controllers` directory, ensure that the controller is guarded with an `Authorize` annotation:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7x2mf5fqwtq3g3f23ac.png)

This annotation will ensure that the endpoints of this controller can only be accessed by Users which are authenticated by your Azure AD. You can run the debugger and try to access your endpoint by accessing `https://localhost:44340/WeatherForecast` in your browser (depending on your debug configurations a different port can be used). You should receive an Http 401 Error, this means you are not authorized since your Browser is not passing the Azure AD token to your backend. This is expected, however when setting up the SharePoint Framework Webpart we will configure it, so that the token will be passed from SharePoint Online to your backend without any additional authentication. 
 
## Create the SharePoint Framework WebPart
In this guide I will make use of the Yeoman generator to generate the boilerplate code to get started. Please not that I will be using the react template and name my application `HelloWorld` for this guide. If you are unsure on how to initialize the SPFX Webpart [follow the steps documented by Microsoft](https://docs.microsoft.com/en/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part). After creation of the boilerplate open the folder in vscode.

In order to pass the Azure Token from your signed in user we to the backend you need to make use of `AadHttpClientFactory`. The boilerplate code will make it available in the default WebPart class. Since I gave my App the default `HelloWorld` name, the root file to bootstrap our application can be found at `src\webparts\helloWorld\HelloWorldWebPart.ts`. Open that file and pass the instance of `this.context.aadHttpClientFactory` to the generated component:
```
public render(): void {
    const element: React.ReactElement<IHelloWorldProps> = React.createElement(
      HelloWorld,
      {
        aadHttpClientFactory: this.context.aadHttpClientFactory,
        description: this.properties.description,
        isDarkTheme: this._isDarkTheme,
        environmentMessage: this._environmentMessage,
        hasTeamsContext: !!this.context.sdks.microsoftTeams,
        userDisplayName: this.context.pageContext.user.displayName
      }
    );

    ReactDom.render(element, this.domElement);
  }
```

Since now the type validation will complain, we need to adjust the interface for the props that will be passed to the component. Open the file `src\webparts\helloWorld\components\IHelloWorldProps.ts` and add `aadHttpClientFactory: AadHttpClientFactory;` to the interface:
```
import { AadHttpClientFactory } from '@microsoft/sp-http';

export interface IHelloWorldProps {
  description: string;
  isDarkTheme: boolean;
  environmentMessage: string;
  hasTeamsContext: boolean;
  userDisplayName: string;
  aadHttpClientFactory: AadHttpClientFactory;
}
```

Now that we pass the Factory for creating clients to access our Endpoints we need to make use of it. Open the file `src\webparts\helloWorld\components\HelloWorld.tsx`. I replaced the render code to just render a Button which when being clicked will call our backend endpoint. Please note that you need to update the Id used in the `getClient()` method. The Id which should be used here is your clientId which you will find in the `appsettings.json` from you backend project. In my case this is `acc8e92b-a093-4514-a9f8-de4e92301aed`:

```
import * as React from 'react';
import styles from './HelloWorld.module.scss';
import { IHelloWorldProps } from './IHelloWorldProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { DefaultButton } from 'office-ui-fabric-react';
import { AadHttpClient, HttpClientResponse } from '@microsoft/sp-http';

export default class HelloWorld extends React.Component<IHelloWorldProps, {}> {
  private async callBackend (): Promise<void> {
    let client = await this.props.aadHttpClientFactory.getClient('acc8e92b-a093-4514-a9f8-de4e92301aed');
    let response: HttpClientResponse = await client.get('https://localhost:44340/WeatherForecast', AadHttpClient.configurations.v1);
    let jsonData = await response.json();
    console.log(jsonData);
  }

  public render(): React.ReactElement<IHelloWorldProps> {
    const {
      description,
      isDarkTheme,
      environmentMessage,
      hasTeamsContext,
      userDisplayName
    } = this.props;

    return (
      <section className={`${styles.helloWorld} ${hasTeamsContext ? styles.teams : ''}`}>
        <DefaultButton onClick={this.callBackend.bind(this)}>Click me</DefaultButton>
      </section>
    );
  }
}
```

Now that the code is finished we need to add the required permission which has to be approved by the Admin when adding our WebPart to our SharePoint App Catalog. Therefore you have to open the `package-solution.json` and add (make sure to replace the resource id with your client id from above).
```
"webApiPermissionRequests": [
      {
        "resource": "acc8e92b-a093-4514-a9f8-de4e92301aed",
        "scope": "user_impersonation"
      }
    ],
```
to the solution so it will look like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lipwvmot6eiqn4sb5it0.png)
 
If you now want to run your SPFX Webpart using `gulp serve` in your SharePoint Online Workbench we need to grant the Workbench App permissions, this however is not required when packaging and deploying the App without using the workbench. To grant permission to the workbench open your [azure portal](https://portal.azure.com/). Navigate to `App registrations`:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wxyaptj7qdn409zyq5fr.png). Select `All applications` and search for `SharePoint Online Client Extensibility Web Application Principal`. Choose the first one:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/majb6q6x7lp0hhrr6lnw.png). Now we need to add the permission to allow passing the user to our backend. Click on `API Permissions` and then `Add a permission` and then `APIs my organization uses`. Search for your clientId or App Name which you used during the VS Wizard for setting up the microsoft identity platform:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tzy6hlgso0mpv9x0wtbq.png). Select the App and add `access_as_user` permission:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mn349r2n993ma6rw20t.png)
After adding the permission grant the admin consent to put the added permission effective:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fxiimf5sq840pgkfzmpp.png).

If you now run `gulp server` in your SPFX folder and open your workbench, you should see a button. When clicking it you will have to login (unless you already logged in to your tenant). After authentication you will see the backend response in your console:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwmw1p4nzvrabf3d5yh5.png)

## Deployment
When deploying the WebPart you need to adjust the appregistration since your URLs will change. In order to do that access your Azure Portal and open the app registration site. Choose again `All applications` and search for your application name which you gave during the VS Wizard for the setup of the microsoft identity platform.  You can also filter by clicking on the `Application (client) ID starts with` field and paste your clientId which we used previously. You should now find your app:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bg0ivw4wa4womp7wjg3r.png)
Click on it and open the Authentication Panel:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n4r9wz1mr25dlnvrfye4.png)

All you have to do now is updating the redirect URLs to the final URLs.