Secure an Angular application using Microsoft Entra External ID and ASP.NET Core with BFF

This article looks at implementing an ASP.NET Core application hosting an Angular nx application which authenticates using Microsoft Entra External ID for customers (CIAM). The ASP.NET Core authentication is implemented using the Microsoft.Identity.Web Nuget package. The client implements the OpenID Connect code flow with PKCE and is a confidential client.

Code: https://github.com/damienbod/bff-MicrosoftEntraExternalID-aspnetcore-angular

Microsoft Entra External ID for customers (CIAM) is a new Microsoft product for customer (B2C) identity solutions. This has many changes to the existing Azure AD B2C solution and adopts many of the features from Microsoft Entra ID (Azure AD). At present, the product is in public preview.

App registration setup

As with any Microsoft Entra ID, Azure AD B2C, Microsoft Entra External ID CIAM application, an Azure App registration is created and used to define the authentication client. The ASP.NET core application is a confidential client and must use a secret or a certificate to authenticate the application as well as the user.

The client authenticates using an OpenID Connect (OIDC) confidential code flow with PKCE. The implicit flow does not need to be activated.

User flow setup

In Microsoft Entra External ID for customers (CIAM), the application must be connected to the user flow. In external identities, a new user flow can be created and the application (The Azure app registration) can be added to the user flow. The user flow can be used to define the specific customer authentication requirements.

Architecture Setup

The application is setup to authenticate as one and remove the sensitive data from the client browser. The single security context has UI logic implemented in Angular and server logic, including the security flows, implemented in ASP.NET Core. The server part of the application handles all requests from the client application and the client application should only use the APIs from the same ASP.NET Core implemented host. Cookies are used to send the secure API requests. The UI implementation is greatly simplified and the backend application can add additional security features as it is a confidential client, or trusted client.

ASP.NET Core Setup

The ASP.NET Core application is implemented using the Microsoft.Identity.Web Nuget package. The recommended flow for trusted applications is the OpenID Connect confidential code flow with PKCE. This is setup using the AddMicrosoftIdentityWebApp method and also the EnableTokenAcquisitionToCallDownstreamApi method. The CIAM client configuration is read using the json EntraExternalID section.

services.AddScoped<MsGraphService>();
services.AddScoped<CaeClaimsChallengeService>();

services.AddAntiforgery(options =>
{
    options.HeaderName = "X-XSRF-TOKEN";
    options.Cookie.Name = "__Host-X-XSRF-TOKEN";
    options.Cookie.SameSite = SameSiteMode.Strict;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

services.AddHttpClient();
services.AddOptions();

var scopes = configuration.GetValue<string>("DownstreamApi:Scopes");
string[] initialScopes = scopes!.Split(' ');

services.AddMicrosoftIdentityWebAppAuthentication(configuration, "MicrosoftEntraExternalID")
    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
    .AddMicrosoftGraph("https://graph.microsoft.com/v1.0", initialScopes)
    .AddInMemoryTokenCaches();

In the appsettings.json, user secrets or the production setup, the client specific configurations are defined. The settings must match the Azure App registration. The SignUpSignInPolicyId is no longer used compared to Azure AD B2C. This is linked in the user flow.

  "MicrosoftEntraExternalID": {
    "Authority": "https://damienbodciam.ciamlogin.com/",
    "ClientId": "0990af2f-c338-484d-b23d-dfef6c65f522",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath ": "/signout-callback-oidc"
    // "ClientSecret": "--in-user-secrets--"
  },

Angular Setup

The Angular solution for development and production is setup like described in this blog:

The UI part of the application implements no OpenID connect flows and is always part of the server application. The UI can only access APIs from the single hosting application.

Notes

I always try to implement user flows for B2C solutions and avoid custom setups as these setups are hard to maintain, expensive to keep updated and hard to migrate when the product is end of life.

Setting up a CIAM client in ASP.NET Core works without problems. CIAM offers many more features but is still missing some essential ones. This product is starting to look really good and will be a great improvement on Azure AD B2C when it is feature complete.

Strong authentication is missing from Microsoft Entra External ID for customers (CIAM) and this makes it hard to test using my Azure AD users. Hopefully FIDO2 and passkeys will get supported soon. See the following link for the supported authentication methods:

https://learn.microsoft.com/en-us/azure/active-directory/external-identities/customers/concept-supported-features-customers

I also require a standard OpenID Connect identity provider (Code flow confidential client with PKCE support) in most of my customer solution rollouts. This is not is supported at present.

With CIAM, new possibilities are also possible for creating single solutions to support both B2B and B2C use cases. Support for Azure security groups and Azure roles in Microsoft Entra External ID for customers (CIAM) is one of the features which makes this possible.

Links

https://learn.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core

https://nx.dev/getting-started/intro

https://github.com/AzureAD/microsoft-identity-web

https://github.com/isolutionsag/aspnet-react-bff-proxy-example

https://learn.microsoft.com/en-us/azure/active-directory/external-identities/

https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-external-id

https://developer.microsoft.com/en-us/identity/customers

https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/microsoft-entra-external-id-public-preview-developer-centric/ba-p/3823766

https://github.com/Azure-Samples/ms-identity-ciam-dotnet-tutorial

https://github.com/damienbod/EntraExternalIdCiam

https://github.com/damienbod/bff-aspnetcore-angular

https://github.com/damienbod/bff-auth0-aspnetcore-angular

https://github.com/damienbod/bff-openiddict-aspnetcore-angular

https://github.com/damienbod/bff-azureadb2c-aspnetcore-angular

https://github.com/damienbod/bff-aspnetcore-vuejs

6 comments

  1. […] Secure an Angular application using Microsoft Entra External ID and ASP.NET Core with BFF (Damien Bowden) […]

  2. […] Secure an Angular application using Microsoft Entra External ID and ASP.NET Core with BFF – Damien Bowden […]

  3. Tony Mould · · Reply

    Thanks Damien, a couple of questions:
    many of your projects use
    app.MapNotFound(“/api/{**segment}”);
    and
    app.UseNoUnauthorizedRedirect(“/api”);

    Do we still need these? – I get 404 /api responses without the mapping.
    what is the attack we are mitigating with UseNoUnauthorizedRedirect() – the sample the same without this.

    In addition even though we ConfigureKestrel to not return the ‘server’ header. I still get this header in the responses??

    1. Thanks, I will update this, Don’t know how to fix the server bit

      Greetings Damien

  4. Thanks, this looks interesting, how would I do this if the frontend is Nuxt 3 app, do you have any pointers for that ?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.