Secure a Blazor WASM ASP.NET Core hosted APP using BFF and OpenIddict

This article shows how to implement authentication and secure a Blazor WASM application hosted in ASP.NET Core using the backend for frontend (BFF) security architecture to authenticate. All security is implemented in the backend and the Blazor WASM is a view of the ASP.NET Core application, no security is implemented in the public client. The application is a trusted client and a secret is used to authenticate the application as well as the identity. The Blazor WASM UI can only use the hosted APIs on the same domain.



The Blazor WASM and the ASP.NET Core host application is implemented as a single application and deployed as one. The server part implements the authentication using OpenID Connect. OpenIddict is used to implement the OpenID Connect server application. The code flow with PKCE and a user secret is used for authentication.

Open ID Connect Server setup

The OpenID Connect server is implemented using OpenIddict. The is standard implementation as like the documentation. The worker class implements the IHostService interface and is used to add the code flow client used by the Blazor ASP.NET Core application. PKCE is added as well as a client secret.

static async Task RegisterApplicationsAsync(IServiceProvider provider)
	var manager = provider.GetRequiredService<IOpenIddictApplicationManager>();

	// Blazor Hosted
	if (await manager.FindByClientIdAsync("blazorcodeflowpkceclient") is null)
		await manager.CreateAsync(new OpenIddictApplicationDescriptor
			ClientId = "blazorcodeflowpkceclient",
			ConsentType = ConsentTypes.Explicit,
			DisplayName = "Blazor code PKCE",
			DisplayNames =
				[CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MVC"
			PostLogoutRedirectUris =
				new Uri("https://localhost:44348/signout-callback-oidc"),
				new Uri("https://localhost:5001/signout-callback-oidc")
			RedirectUris =
				new Uri("https://localhost:44348/signin-oidc"),
				new Uri("https://localhost:5001/signin-oidc")
			ClientSecret = "codeflow_pkce_client_secret",
			Permissions =
				Permissions.Prefixes.Scope + "dataEventRecords"
			Requirements =

Blazor client Application

The client application was created using the Blazor.BFF.OpenIDConnect.Template Nuget template package. The configuration is read from the app settings using the OpenIDConnectSettings section. You could add more configurations if required. This is otherwise a standard OpenID Connect client and will work with any OIDC compatible server. PKCE is required and also a secret to validate the application. The AddAntiforgery method is used so that API calls can be forced to validate anti-forgery token to protect against CSRF as well as the same site cookie protection.

public void ConfigureServices(IServiceCollection services)
	services.AddAntiforgery(options =>
		options.HeaderName = "X-XSRF-TOKEN";
		options.Cookie.Name = "__Host-X-XSRF-TOKEN";
		options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
		options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;

	var openIDConnectSettings = Configuration.GetSection("OpenIDConnectSettings");

	services.AddAuthentication(options =>
		options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
		options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
   .AddOpenIdConnect(options =>
	   options.SignInScheme = "Cookies";
	   options.Authority = openIDConnectSettings["Authority"];
	   options.ClientId = openIDConnectSettings["ClientId"];
	   options.ClientSecret = openIDConnectSettings["ClientSecret"];
	   options.RequireHttpsMetadata = true;
	   options.ResponseType = "code";
	   options.UsePkce = true;
	   options.SaveTokens = true;
	   options.GetClaimsFromUserInfoEndpoint = true;
	   //options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username");

   services.AddControllersWithViews(options =>
		options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));

	services.AddRazorPages().AddMvcOptions(options =>
		//var policy = new AuthorizationPolicyBuilder()
		//    .RequireAuthenticatedUser()
		//    .Build();
		//options.Filters.Add(new AuthorizeFilter(policy));

The OIDC configuration settings are read from the OpenIDConnectSettings section. This can be extended if further specific settings are required.

"OpenIDConnectSettings": {
 "Authority": "https://localhost:44395",
 "ClientId": "blazorcodeflowpkceclient",
 "ClientSecret": "codeflow_pkce_client_secret"

The NetEscapades.AspNetCore.SecurityHeaders Nuget package is used to add security headers to the application to protect the session. The configuration is setup for Blazor.

public static HeaderPolicyCollection GetHeaderPolicyCollection(bool isDev, string idpHost)
	var policy = new HeaderPolicyCollection()
		.AddCrossOriginOpenerPolicy(builder =>
		.AddCrossOriginResourcePolicy(builder =>
		.AddCrossOriginEmbedderPolicy(builder => // remove for dev if using hot reload
		.AddContentSecurityPolicy(builder =>

			// due to Blazor

			// disable script and style CSP protection if using Blazor hot reload
			// if using hot reload, DO NOT deploy with an insecure CSP
		.AddPermissionsPolicy(builder =>

	if (!isDev)
		// maxage = one year in seconds
		policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365);

	return policy;

The APIs used by the Blazor UI are protected by the ValidateAntiForgeryToken and the Authorize attribute. You could add authorization as well if required. Cookies are used for this API with same site protection.

[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public class DirectApiController : ControllerBase
	public IEnumerable<string> Get()
		return new List<string> { "some data", "more data", "loads of data" };

When the application is started, the user can sign-in and authenticate using OpenIddict.

The setup keeps all the security implementation in the trusted backend. This setup can work against any OpenID Connect conform server. By having a trusted application, it is now possible to implement access to downstream APIs in a number of ways and possible to add further protections as required. The downstream API does not need to be public either. You should only use a downstream API if required. If a software architecture forces you to use APIs from separate domains, then a YARP reverse proxy can be used to access to API, or a service to service API call, ie trusted client with a trusted server, or an on behalf flow (OBO) flow can be used.



  1. […] Secure a Blazor WASM ASP.NET Core hosted APP using BFF and OpenIddict [#.NET #.NET Core #ASP.NET Core #OAuth2 #Security #SQLite #Uncategorized #BFF #Blazor #cookie #dotnet #OAuth #OIDC #openIDConnect #OpenIddict #WASM] […]

  2. […] Secure a Blazor WASM ASP.NET Core hosted APP using BFF and OpenIddict (Damien Bowden) […]

  3. […] Secure a Blazor WASM ASP.NET Core hosted APP using BFF and OpenIddict – Damien Bowden […]

  4. Hi Damien, first of all, thanks for trying to simplify this, as it turns out, very complex technology in .NET. I am struggling with understanding the concept of how it should work.
    In a LOB application, like accounting, retail, etc, users do not have anything to share with the app . So, asking the user to explicitly confirm that they allow access to some resource is unnecessary. Also, confirmation while logging out can also be unnecessary.

    Are these two things configurable, so they can be skipped in flow? I know that this is OAuth functionality, but in my case I just need to authenticate users.

    And another thing: ASP.NET Core Identity provides a lot of functionality out of the box, like using 2FA. We can override UI and define our custom UI for these screens in OpenIddictServer project.

    What I do not understand is, how is this suppose to be integrated in our Blazor WASM app? How do we redirect all these requests?


Leave a Reply

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

You are commenting using your 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: