azure-ad-b2cazure-ad-msal

AzureAd .NET - React running on same domain


I have a react application and aspcore backend running on the same domain. (everything is redirected to /index.html except /api/

My users need to login Azure AD.

const msalConfig = {
  auth: {
    clientId: "{clientId}",
    authority: "https://login.microsoftonline.com/{tenantId}",
    redirectUri: `${window.location.origin}`,
  },
  cache: {
    cacheLocation: "sessionStorage",
    storeAuthStateInCookie: false
  }
};

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
const msalInstance = new PublicClientApplication(msalConfig);
root.render(
  <StrictMode><MsalProvider instance={msalInstance}>
      <App />
    </MsalProvider>
  </StrictMode>
);

Everytime I call the backend I get the token by:

    // Try to acquire token silently
    const tokenResult = await this.msalInstance.acquireTokenSilent({
      account: this.account,
      scopes: [{clientId}],
    });

    return tokenResult.accessToken;

Now I need to verify the token in my aspcore backend.

I tried serveral ways of adding Authentication. I had hoped this would be suffient:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

appsettings:

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "{tenantId}",
    "ClientId": "{clientId}",
    "Issuer": "https://login.microsoftonline.com/{tenantId}/v2.0/",
    "Audience": "{clientId}"
  },

I still get a 401 status code. I've registered one app in the Microsoft Entra Admin Center. Didn't think I had to create two seperate registration because my site runs on the same domain. But I might be wrong. Can someone help me with the correct configuration?


Solution

  • I created a sample React app frontend and an ASP. NET Core backend with Azure AD integration.

    401 status code.

    I got above error because of using wrong "Audience":"{ClientID}",so I changed the "Audience": "api://{ClientId}".

    appsettings.json:

     "AzureAd": {
       "Instance": "https://login.microsoftonline.com/",
       "TenantId": "{TenantId}",
       "ClientId": "{ClientId}",
       "Audience": "api://{ClientId}",
       "Issuer": "https://login.microsoftonline.com/{TenantId}/v2.0"
     }
    

    Below is my complete program.cs class.

    Program.cs:

    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.Identity.Web;
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
    builder.Services.AddControllers();
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("AllowReactApp", policy =>
        {
            policy.WithOrigins("http://localhost:3000")
                  .AllowAnyHeader()
                  .AllowAnyMethod()
                  .AllowCredentials();
        });
    });
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    var app = builder.Build();
    app.UseCors("AllowReactApp");
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    app.UseHttpsRedirection();
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
    

    DataController.cs:

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    namespace serverapi.Controllers
    {
        [Authorize]
        [ApiController]
        [Route("api/[controller]")]
        public class DataController : ControllerBase
        {
            [HttpGet]
            public IActionResult Get()
            {
                return Ok(new { message = "Hello from API!" });
            }
        }
    }
    

    Below is my complete React Application, I used scope as scopes: ['api://{ClientId}/access_as_user'] in react app.

    App.js:

    import React, { useEffect, useState } from 'react';
    import { useMsal } from '@azure/msal-react';
    function App() {
      const { instance, accounts } = useMsal();
      const [apiResponse, setApiResponse] = useState('');
      const login = () => {
        instance.loginPopup({
          scopes: ['api://{ClientId}/access_as_user'], 
        });
      };
      const callApi = async () => {
        try {
          const tokenResponse = await instance.acquireTokenSilent({
            account: accounts[0],
            scopes: ['api://{ClientId}/access_as_user'], 
          });
          const accessToken = tokenResponse.accessToken;
          const response = await fetch('https://localhost:7165/api/Data', {
            headers: {
              Authorization: `Bearer ${accessToken}`,
            },
          });
          const data = await response.json();
          setApiResponse(data);
        } catch (error) {
          console.error('API call error: ', error);
        }
      };
      return (
        <div>
          <h1>React + Azure AD</h1>
          {!accounts.length && <button onClick={login}>Login</button>}
          {accounts.length && <button onClick={callApi}>Call API</button>}
          <pre>{apiResponse && JSON.stringify(apiResponse, null, 2)}</pre>
        </div>
      );
    }
    export default App;
    

    index.js:

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { PublicClientApplication } from '@azure/msal-browser';
    import { MsalProvider } from '@azure/msal-react';
    import App from './App';
    const msalConfig = {
      auth: {
        clientId: '{Client ID}',
        authority: 'https://login.microsoftonline.com/{TenantId}', 
        redirectUri: `${window.location.origin}`,
      },
      cache: {
        cacheLocation: 'sessionStorage',
        storeAuthStateInCookie: false,
      },
    };
    const msalInstance = new PublicClientApplication(msalConfig);
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <MsalProvider instance={msalInstance}>
        <App />
      </MsalProvider>
    );
    

    I added the Scope to my app registration as shown below.

    enter image description here

    Asp. net Output:

    enter image description here

    React Output:

    enter image description here

    enter image description here