Authentication is very important to any application. While rolling out your own authentication, services like Azure AD (Active Directory) make getting your application up and running much faster. In this article, we will walk through the necessary steps in detail to setup Azure AD authentication with .NET Core and React.

To follow along, you will need the following:

  1. Dotnet 3.1 (or latest LTS)
  2. Nodejs
  3. Access to an Azure account

Setup Azure AD Instances

To begin, we will go into Azure and create our Azure AD resources. Once you are logged in, simply search for ‘Azure Active Directory’. Once there, you will need to create two new app registrations, one for our backend application and one for our frontend SPA.

To continue, go to ‘App Registrations’ and create two apps. For this example, we’ll have AzureAdExampleBackend and AzureAdExampleFrontend.

For the frontend app, make sure to set a redirect URL since we will be using Dotnet Core. The default redirect is https://localhost:5001 as seen below.

Next, we will go to our backend AD instance and setup access for our frontend instance. Go to ‘Expose an API’ and setup the scope for our backend API.

The scope is the permission that the API will expose to our frontend application. Users will need to consent to these scopes when authenticating with the application. The display names and descriptions are what users and admins will see when they first login to the app and their permissions are initially requested.

A screenshot of a cell phone

Description automatically generated

The next step will be to add the API permissions to the frontend. Start with going to your frontend instance and going to API permissions under the ‘Manage’ section of the sidebar and hitting ‘Add a permission’. You can find your backend Azure AD instance by going to ‘My APIs’ and grant it the Read permission we setup in the previous step.

Once complete, return to the App Registrations and make note of your ClientIds and TenantId. We will be using these in the next section that goes over the actual application build.

The Code

To begin, assuming you have the latest Dotnet Core and Nodejs installed, we will use the react generator with the following command: dotnet new react. This is going to create a new .Net Core application with a React client application.

Before we go any further into the code, we will be adding our Azure AD configuration settings to the appsettings.json, making sure to add the appropriate Ids where needed. For this file make sure that you are using the backend instance ClientId.

"AzureAd": {
  "Instance": "https://login.microsoftonline.com/",
  "Domain": "https://login.microsoftonline.com/common",
  "TenantId": "<fill with your id>",
  "ClientId": "<fill with your id>"
},

We will begin here with adding the [Microsoft.AspNetCore.Authentication.AzureAD.UI]() package, by running `dotnet add package Microsoft.AspNetCore.Authentication.AzureAD.UI` in the command line. Then we’ll go to the `Startup.cs` file and add the following:

public void ConfigureServices (IServiceCollection services) {
    services.Configure<CookiePolicyOptions> (options => {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddAuthentication (AzureADDefaults.AuthenticationScheme)
        .AddAzureAD (options => Configuration.Bind ("AzureAd", options));

    services.Configure<OpenIdConnectOptions> (AzureADDefaults.OpenIdScheme, options => {
        options.Authority = options.Authority + "/v2.0/";
        options.TokenValidationParameters.ValidateIssuer = false;
    })
    ...
    ...
}

public void Configure (IApplicationBuilder app, IWebHostEnvironment env) {
  ...
  ...
  app.UseRouting ();
  app.UseAuthentication();
  app.UseAuthorization();
  ...
  ...
}

Next, we want to test our authentication by actually authenticating an endpoint in our application, luckily the generator creates a controller for us, so go to the WeathersController.cs and add [Authorize] above the controller definition.

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase {
  ...
  ...
}

For the next step, go into the ClientApp folder and run yarn add redux react-redux react-aad-msal msal. This installs the redux and the react-aad library, which makes it easier to authenticate with Azure AD in a React app. Now, create a new folder src/auth and within that folder we will create a new file authProvider.js and add in the following and replace ClientId with your frontend ClientId that was previously copied.

import { MsalAuthProvider, LoginType } from "react-aad-msal";
import { Logger, LogLevel } from "msal";

import { MsalAuthProvider, LoginType } from 'react-aad-msal';

export const authProvider = new MsalAuthProvider(
  {
    auth: {
      authority: "https://login.microsoftonline.com/common",
      clientId: "<your frontend clientId here>",
      postLogoutRedirectUri: window.location.origin,
      redirectUri: window.location.origin,
      validateAuthority: true,
      navigateToLoginRequestUrl: true,
    },
    cache: {
      cacheLocation: 'sessionStorage',
      storeAuthStateInCookie: true,
    },
  },
  {
    scopes: ['api://<your backend clientId here>/Read']
  },
  LoginType.Redirect,
);

To see the full list of options available, take a look at the documentation for the [react-aad] library.

Next, we want to setup a redux store to hold our authenticated tokens. For this, we will create a new folder src/state, create a file index.js, and add the following:

import { createStore } from 'redux';
import { AuthenticationActions, AuthenticationState } from 'react-aad-msal';

const initialState = {
  initializing: false,
  initialized: false,
  idToken: null,
  accessToken: null,
  state: AuthenticationState.Unauthenticated,
};

const rootReducer = (state = initialState, action) => {
  switch (action.type) {
    case AuthenticationActions.Initializing:
      return {
        ...state,
        initializing: true,
        initialized: false,
      };
    case AuthenticationActions.Initialized:
      return {
        ...state,
        initializing: false,
        initialized: true,
      };
    case AuthenticationActions.AcquiredIdTokenSuccess:
      return {
        ...state,
        idToken: action.payload,
      };
    case AuthenticationActions.AcquiredAccessTokenSuccess:
      return {
        ...state,
        accessToken: action.payload,
      };
    case AuthenticationActions.AcquiredAccessTokenError:
      return {
        ...state,
        accessToken: null,
      };
    case AuthenticationActions.LoginSuccess:
      return {
        ...state,
        account: action.payload.account,
      };
    case AuthenticationActions.LoginError:
    case AuthenticationActions.AcquiredIdTokenError:
    case AuthenticationActions.LogoutSuccess:
      return { ...state, idToken: null, accessToken: null, account: null };
    case AuthenticationActions.AuthenticatedStateChanged:
      return {
        ...state,
        state: action.payload,
      };
    default:
      return state;
  }
};

export const store = createStore(rootReducer);

Now that we have our configuration setup, the next step is to start making changes to the component files. First, go to the `index.js` file in the root `src` directory. We want to add the following:

ReactDOM.render(
  <Provider store={store}>
    <AzureAD
      provider={authProvider}
      reduxStore={store}
      forceLogin={true}>
      <BrowserRouter basename={baseUrl}>
        <App />
      </BrowserRouter>
    </AzureAD>
  </Provider>,
  rootElement);

This will pass the our Store to both the Provider and our AzureAd component. We pass in the necessary parameters and set forceLogin to true, so it auto redirects to our login page. Users will need to login before accessing the application. We will leverage the AzureAd component once again in our NavMenu, since we will want a log out button.

<AzureAD
  provider={authProvider}
  reduxStore={store}>
  {({ login, logout, authenticationState }) => {
    if (authenticationState === AuthenticationState.Authenticated) {
      return (<button onClick={logout} color="inherit" variant="outlined">Log Out</button>);
    }
  }}
</AzureAD>

Adding this to the end of the Nav list will display a logout button. Since we are forced to login when we first visit the application, we will not need to make use of a login button. Similar to the backend controller, since we used the generator to create the base application for us, we have a few frontend components setup already. Once that calls our endpoint it requires authorization. Find the FetchData.js file (the changes are very minor) and componentDidMount() function. We simply need to get our accessToken before we load our weather data.

async componentDidMount() {
  await authProvider.getAccessToken().then(res => this.setState({ token: res.accessToken }));
  this.populateWeatherData();
}

  After this, simply add the token to the header of the request.

const response = await fetch('https://localhost:5001/weatherforecast', {
  headers: !this.state.token ? {} : { 'Authorization': `Bearer ${this.state.token}` }
});

After making all of these changes, simply run dotnet watch run in your command line and visit https://localhost:5001. You will be redirected to your Microsoft branded login page and then brought back to your application where you’ll be able to access the fetch data page and display the weather data from our controller. Since we added the [Authorize] attribute, the only way to access this data is to be authenticated against the application.

To continue working with Azure AD take a look at these resources: