Securing Blazor Web assembly using Cookies and Auth0

The article shows how an ASP.NET Core Blazor web assembly 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 Backends for Frontends (BFF) Pattern.

Code: https://github.com/damienbod/SeparatingApisPerSecurityLevel

Blogs in this series

The application was built as described in the previous blog in this series. Please refer to that blog for implementation details about the WASM application, user session and anti-forgery tokens. Setting up the Auth0 authentication and the differences are described in this blog.

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 WASM 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.

The Microsoft.AspNetCore.Authentication.OpenIdConnect Nuget package is used to add the authentication to the ASP.NET Core application. User secrets are used for configuration which uses the Auth0 sensitive data

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <WebProject_DirectoryAccessLevelKey>1</WebProject_DirectoryAccessLevelKey>
    <UserSecretsId>de0b7f31-65d4-46d6-8382-30c94073cf4a</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Client\BlazorAuth0Bff.Client.csproj" />
    <ProjectReference Include="..\Shared\BlazorAuth0Bff.Shared.csproj" />
  </ItemGroup>
  
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.5" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.5" NoWarn="NU1605" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.5" NoWarn="NU1605" />
    <PackageReference Include="IdentityModel" Version="5.1.0" />
    <PackageReference Include="IdentityModel.AspNetCore" Version="3.0.0" />
  </ItemGroup>

</Project>

The ConfigureServices method in the Startup class of the ASP.NET Core Blazor server application is used to add the authentication. The Open ID Connect code flow with PKCE and a client secret is used for the default challenge and a cookie is used to persist the tokens if authenticated. The Blazor client WASM uses the cookie to access the API.

The Open ID Connect is configured to match the Auth0 settings for the client. A client secret is required and used to authenticate the application. The PKCE option is set explicitly to use PKCE with the client configuration. The required scopes are set so that the profile is returned and an email. These are OIDC standard scopes. The user profile API is used to return the profile data and so keep the id_token small. The tokens are persisted. If successful, the data is persisted to an identity cookie. The logout client is configured as documented by Auth0 in its example.

services.AddAuthentication(options => {
	options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
	options.Cookie.Name = "__Host-BlazorServer";
	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.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;
		}
	};
});

The Configure method is implement to require authentication. The UseAuthentication extension method is required. Our endpoints are added like in the previous blog.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	// IdentityModelEventSource.ShowPII = true;
	JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

	app.UseHttpsRedirection();
	app.UseBlazorFrameworkFiles();
	app.UseStaticFiles();

	app.UseRouting();
	app.UseAuthentication();
	app.UseAuthorization();

	app.UseEndpoints(endpoints =>
	{
		endpoints.MapRazorPages();
		endpoints.MapControllers();
		endpoints.MapFallbackToPage("/_Host");
	});
}

The Auth0 configuration can be downloaded in the sample application, or you can configure this direct in the Auth0 UI and copy this. Three properties are required. I added these to the user secrets in my application development. If I deployed this to Azure, I would add these to an Azure Key Vault and can then use managed identities to access the secrets.

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

Now everything will run and you can now use ASP.NET Core Blazor BFF with Auth0. We don’t need any access tokens in the browser. This was really simple to configure and only ASP.NET Core standard Nuget packages are used. Security best practices are supported by Auth0 and it is really easy to setup. In production I would force MFA and FIDO2 if possible.

Links

Securing Blazor Web assembly using Cookies and Azure AD

https://auth0.com/

https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery

https://docs.microsoft.com/en-us/aspnet/core/blazor/security

https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios

4 comments

  1. […] Securing Blazor Web assembly using Cookies and Auth0 (Damien Bowden) […]

  2. […] Securing Blazor Web assembly using Cookies and Auth0 – Damien Bowden […]

  3. […] Securing Blazor Web assembly using Cookies and Auth0 […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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

%d bloggers like this: