Ray
Ray

Reputation: 396

Azure AD B2C React Client .NET 5 API

I am having a problem configuring a React client accessing a .NET 5 web API using Azure AD B2C. I reviewed the following documents;

I ended up using the examples shown in the last document and using the configuration settings for the node.js API server in my .NET 5 Web API;

React Configuration

import { LogLevel } from "@azure/msal-browser";

export const b2cPolicies = {
    names: {
        signIn: "b2c_1_signin", 
        signInStaff: "b2c_1_signupin_staff" 
    },

    authorities: {
        signIn: {
            authority: "https://<b2cTenantName>.b2clogin.com/<b2cTenantName>.onmicrosoft.com/b2c_1_signin", 
        },
        signInStaff: {
            authority: "https://<b2cTenantName>.b2clogin.com/<b2cTenantName>.onmicrosoft.com/b2c_1_signupin_staff"
        }
    },

    authorityDomain: "<b2cTenantName>.b2clogin.com" 
}

export const msalConfig = {
    auth: {
        clientId: "ec0441a4-89ac-1111-1111-111111111111",
        authority: b2cPolicies.authorities.signIn.authority,
        knownAuthorities: [b2cPolicies.authorityDomain],
        redirectUri: "http://localhost:3000",
        navigateToLoginRequestUrl: false,
    },
    cache: {
        cacheLocation: "sessionStorage",
        storeAuthStateInCookie: false,
    },
    system: {
        loggerOptions: {
            loggerCallback: (level, message, containsPii) => {
                if (containsPii) return;
                level = LogLevel.Verbose;

                switch (level) {
                    case LogLevel.Error:
                        console.error(message);
                        return;
                    case LogLevel.Info:
                        console.info(message);
                        return;
                    case LogLevel.Verbose:
                        console.debug(message);
                        return;
                    case LogLevel.Warning:
                        console.warn(message);
                        return;
                    default:
                        console.log(message);
                }
            }
        }
    }
};

export const loginRequest = {
    scopes: ["openid", "offline_access"]
};

export const loginRequestStaff = {
    scopes: ["openid", "profile"]
};

 export const protectedResources = {
    portalApi: {
        scopes: ["https://<b2cTenantName>.onmicrosoft.com/PortalClient/access_as_user"],
        redirectUri: "http://localhost:3000/Dashboard",
    },
    portalApiStaff: {
        scopes: ["https://<b2cTenantName>.onmicrosoft.com/PortalClient/access_as_staff"],
        redirectUri: "http://localhost:3000/Dashboard",
    }
}

.NET 5 AppSettings.json

{
    ...
    
    "AzureAd": {
        "Instance": "https://<b2cTenantName>.b2clogin.com",
        "Domain": "<b2cTenantName>.onmicrosoft.com",
        "ClientId": "ec0441a4-89ac-1111-1111-111111111111",
        "SignUpSignInPolicyId": "B2C_1_SignIn"
    },

    ...
}

Portal API Registration

Property Value
Application (Client) ID d1a138a9-2379-1111-1111-111111111111
Directory (Tenant) ID 6c977334-b859-2222-2222-222222222222
Redirect URIs http://localhost:3000
Certificates & Secrets None
API Permissions (all have Admin consent) Microsoft Graph - offline_access, openid
Application ID URI https://.onmicrosoft.com/PortalApi
Exposed APIs (scopes) access_as_staff, access_as_user

Portal Client Registration

Property Value
Application (Client) ID ec0441a4-89ac-1111-1111-111111111111
Directory (Tenant) ID 6c977334-b859-2222-2222-222222222222
Redirect URIs http://localhost:3000/Dashboard
http://localhost:3000
Certificates & Secrets None
API Permissions (all have Admin consent) Microsoft Graph: - offline_access, openid
Portal API - access_as_staff, access_as_user

MSAL Login & Access Token Code

import { PublicClientApplication } from "@azure/msal-browser";
import { msalConfig, b2cPolicies, protectedResources, loginRequest, loginRequestStaff } from "./authConfig";

let msalInstance = new PublicClientApplication(msalConfig);
export const getMsalInstance = () => msalInstance;

export const login = () => {
    msalInstance.config.auth.authority = b2cPolicies.authorities.signIn.authority;
    msalInstance.loginRedirect(loginRequest);
}

export const loginAsStaff = () => {
    msalInstance.config.auth.authority = b2cPolicies.authorities.signInStaff.authority;
    msalInstance.loginRedirect(loginRequestStaff);
}

export const getAccessToken = async (accessToken, isStaffUser) => {
    const accounts = msalInstance.getAllAccounts();
    if (accounts.length === 0) return null;

    let _accessToken = {...accessToken};

    if (_accessToken.token == null || _accessToken.expires <= Date()) {
        const response = await msalInstance.acquireTokenSilent({
            account: accounts[0],
            scopes: isStaffUser ? protectedResources.portalApiStaff.scopes : protectedResources.portalApi.scopes
        });
        const expiryDt = new Date(0).setUTCSeconds((response.idTokenClaims.exp));
        _accessToken = { token: response.idToken, expires: expiryDt };
    }
    return _accessToken;
}

.NET 5 Web API Startup.cs

    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApi(options =>
                {
                    Configuration.Bind("AzureAd", options);
                    options.TokenValidationParameters.NameClaimType = "name";
                }, options => { Configuration.Bind("AzureAd", options); });

            services.AddAuthorization();
            ...
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseStaticFiles();
            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();
            ...
        }
    }

When acquiring the access token in the MSAL code I get the following response in response.idToken;

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1669793111,
  "nbf": 1669789511,
  "ver": "1.0",
  "iss": "https://<b2cTenantName>.b2clogin.com/6c977334-b859-2222-2222-222222222222/v2.0/",
  "sub": "3a35cfce-de55-4217-9c33-217f14639578",
  "aud": "ec0441a4-89ac-1111-1111-111111111111",
  "nonce": "ce9b5c4f-010d-43cc-9dce-861e6a087489",
  "iat": 1669789511,
  "auth_time": 1669789496,
  "idp_access_token": "eyJraWQiOiItVWRTSVB...7bBGxGDzNQWqw",
  "idp": "https://<idp>/oauth2/auskoi3basJBCcDOy1t7",
  "given_name": "John",
  "family_name": "Doe",
  "name": "John Doe",
  "oid": "3a35cfce-de55-4217-9c33-217f14639578",
  "emails": [
    "[email protected]"
  ],
  "tfp": "B2C_1_SignUpIn_Staff"
}.[Signature]

and if I then decode the idp-access-token I get;

{
  "kid": "-UdSIPeUmX7e4pqpXRBb7IXu3bCLsQo0hU67PYDhdfM",
  "alg": "RS256"
}.{
  "ver": 1,
  "jti": "AT.r_KbqlRqqur1gxf9u5S8VReP2awA58YjtIfKiBdLokQ",
  "iss": "https://<my-company>/oauth2/auskoi3basJBCcDOy1t7",
  "aud": "Company-CustomerPortal",
  "iat": 1669789481,
  "exp": 1669793081,
  "cid": "0oakoicxjrL5IMsmd1t7",
  "uid": "00ua37oxlxRdmbFgb1t7",
  "scp": [
    "groups",
    "profile",
    "openid"
  ],
  "auth_time": 1669789477,
  "sub": "[email protected]"
}.[Signature]

Neither of which have the access_as_staff scope.

If I configure the client and API code to use the Portal Client clientId (ec0441a4-89ac-1111-1111-111111111111) and submit the access token to an [Authorize] route on the web API I get the following error;

IDW10201: Neither scope or roles claim was found in the bearer token.

If however I configure the API to use the Portal API clientId (d1a138a9-2379-1111-1111-111111111111) and the client to use the Portal Client clientId (ec0441a4-89ac-1111-1111-111111111111) as shown in the example, I get the following error;

IDX10214: Audience validation failed. Audiences: 'ec0441a4-89ac-1111-1111-111111111111'. Did not match: validationParameters.ValidAudience: 'd1a138a9-2379-1111-1111-111111111111' or validationParameters.ValidAudiences: 'null'.

Upvotes: 0

Views: 524

Answers (1)

kavya Saraboju
kavya Saraboju

Reputation: 10859

I tried to reproduce the same in my environment:

I tried to change the scope parameter but still I received . In my case I am calling graph api: So my scope must be https://graph.microsoft.com/.default

I got the same error when I tried to call an API from my web app.

enter image description here

Error:

SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: 'xxx'. Did not match: validationParameters.ValidAudience: xxxx or validationParameters.ValidAudiences: xxxx

In my case I have given wrong clientId in my clientApp registration in

appsettings.json

  {
    "AzureAd": {
      "Instance": "....",
      "Domain": "xxx",
      "ClientId": "xxx",
      "TenantId": "xxxf3a0cxxb0",
      "ClientSecret": "xxxxxxxxx",
      "ClientCertificates": [
      ],
      ….
    },
    

Make sure to expose the scopes for your API in your API configuration . Give that (exposed)API permissions to the APP that is clientAPP .

Also check the issuer endpoint,

"iss": "https://<b2cTenantName>.b2clogin.com/xxxx/v2.0/",

If it has v2 endpoint make sure , the value is 2 in> "accessTokenAcceptedVersion": 2,

enter image description here

With all changes , I could call my Api successfully:

enter image description here Also check this reference : Azure AD B2C: Call an ASP.NET Web API from an ASP.NET Web App - Code Samples | Microsoft Learn

Upvotes: 0

Related Questions