Secure Angular application using Auth0 and ASP.NET Core with BFF

The article shows how an Angular nx Standalone UI hosted in an ASP.NET Core application can be secured using cookies. Auth0 is used as the identity provider. The trusted application is protected using the Open ID Connect code flow with a secret and using PKCE. The API calls are protected using the secure cookie and anti-forgery tokens to protect against CSRF. This architecture is also known as the Backend for Frontend (BFF) Pattern.

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

Auth0 Setup

An Auth0 account is required and a Regular Web Application was setup for this. This is not an SPA application and must always be deployed with a backend which can keep a secret. The Angular client can only use the APIs on the same domain and uses cookies. All application authentication is implemented in the trusted backend and the secure data is encrypted in the cookie.

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 request 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 setup to authenticate using OpenID Connect and to store this session in a secure cookie. All OpenID Connect providers require small specific flavors of OpenID Connect. The different OpenID Connect clients can all be implemented using the standard ASP.NET Core AddOpenIdConnect method. If you want, most identity providers provide product specific clients which just wrap this client and change the names of the methods, and pre-configure the provider server specifics. When using the client specific clients, you need to re-learn the APIs for the different OpenID Connect servers. The following code implements the OpenID Connect client for Auth0 and also acquires a delegated access token for the required scope. This is not required, just added as documentation.

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
    options.Cookie.Name = "__Host-auth0";
    options.Cookie.SameSite = SameSiteMode.Lax;
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.Authority = $"https://{configuration["Auth0:Domain"]}";
    options.ClientId = configuration["Auth0:ClientId"];
    options.ClientSecret = configuration["Auth0:ClientSecret"];
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("email");
    options.Scope.Add("auth0-user-api-one");
    // options.CallbackPath = new PathString("/signin-oidc");
    options.ClaimsIssuer = "Auth0";
    options.SaveTokens = true;
    options.UsePkce = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters.NameClaimType = "name";

    options.Events = new OpenIdConnectEvents
    {
        // handle the logout redirection 
        OnRedirectToIdentityProviderForSignOut = (context) =>
        {
            var logoutUri = $"https://{configuration["Auth0:Domain"]}/v2/logout?client_id={configuration["Auth0:ClientId"]}";

            var postLogoutUri = context.Properties.RedirectUri;
            if (!string.IsNullOrEmpty(postLogoutUri))
            {
                if (postLogoutUri.StartsWith("/"))
                {
                    // transform to absolute
                    var request = context.Request;
                    postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
                }
                logoutUri += $"&returnTo={Uri.EscapeDataString(postLogoutUri)}";
            }

            context.Response.Redirect(logoutUri);
            context.HandleResponse();

            return Task.CompletedTask;
        },
        OnRedirectToIdentityProvider = context =>
        {
            // The context's ProtocolMessage can be used to pass along additional query parameters
            // to Auth0's /authorize endpoint.
            // 
            // Set the audience query parameter to the API identifier to ensure the returned Access Tokens can be used
            // to call protected endpoints on the corresponding API.
            context.ProtocolMessage.SetParameter("audience", "https://auth0-api1");

            return Task.FromResult(0);
        }
    };
});

The OpenID Connect client for Auth0 using the configuration from the appsettings or

  "Auth0": {
    "Domain": "your-domain-in-auth0",
    "ClientId": "--in-secrets--",
    "ClientSecret": "--in-secrets--"
  }

The API controller uses the secure cookie and the CSRF protection.

[ValidateAntiForgeryToken]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[ApiController]
[Route("api/[controller]")]
public class DirectApiController : ControllerBase
{
    [HttpGet]
    public async Task<IEnumerable<string>> GetAsync()
    {
        // if you need a delegated access token for downstream APIs
        var accessToken = await HttpContext.GetTokenAsync("access_token");

        return new List<string> { "some data", "more data", "loads of data" };
    }
}

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.

Links

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

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

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

https://auth0.com/docs

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

3 comments

  1. […] Secure Angular application using Auth0 and ASP.NET Core with BFF (Damien Bowden) […]

  2. […] Secure Angular application using Auth0 and ASP.NET Core with BFF – Damien Bowden […]

  3. […] sample written by Doğan Erişen earlier this year. [On September 18th, 2023, Damien Bowd published this post describing the use of the BFF architecture to secure an Angular SPA with Auth0 using ASP.NET […]

Leave a comment

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