Blazor Applications
This quickstart walks you through how to create a BFF Blazor application. The source code for this quickstart is available on GitHub.
What You’ll Build
Section titled “What You’ll Build”By the end of this guide you will have a Blazor application (Server + WASM) that authenticates users via OpenID Connect, stores session state server-side through the BFF, and calls a weather API using a BFF-managed HTTP client — with no access tokens exposed to the browser.
Creating the project structure
Section titled “Creating the project structure”-
Create a Blazor App
Terminal window mkdir BlazorBffAppcd BlazorBffAppdotnet new blazor --interactivity auto --all-interactiveThis creates a Blazor application with a Server project and a client project.
-
Configure the BffApp Server Project
To configure the server, the first step is to add the BFF Blazor package.
Terminal window cd BlazorBffAppdotnet add package Duende.BFF.BlazorThen configure the application to use BFF. Add this to your services:
// BFF setup for Blazorbuilder.Services.AddBff().ConfigureOpenIdConnect(options =>{options.Authority = "https://demo.duendesoftware.com";options.ClientId = "interactive.confidential";options.ClientSecret = "secret";options.ResponseType = "code";options.ResponseMode = "query";options.GetClaimsFromUserInfoEndpoint = true;options.SaveTokens = true;options.MapInboundClaims = false;options.Scope.Clear();options.Scope.Add("openid");options.Scope.Add("profile");options.Scope.Add("api");options.Scope.Add("offline_access");options.TokenValidationParameters.NameClaimType = "name";options.TokenValidationParameters.RoleClaimType = "role";}).ConfigureCookies(options =>{// Because we use an identity server that's configured on a different site// (duendesoftware.com vs localhost), we need to configure the SameSite property to Lax.// Setting it to Strict would cause the authentication cookie not to be sent after logging in.// The user would have to refresh the page to get the cookie.// Recommendation: Set it to 'strict' if your IDP is on the same site as your BFF.options.Cookie.SameSite = SameSiteMode.Lax;}).AddServerSideSessions() // Add in-memory implementation of server-side sessions.AddBlazorServer();// Make sure authentication state is available to all components.builder.Services.AddCascadingAuthenticationState();builder.Services.AddAuthorization();// BFF setup for Blazor (v3)builder.Services.AddBff().AddServerSideSessions() // Add in-memory implementation of server-side sessions.AddBlazorServer();// Configure the authenticationbuilder.Services.AddAuthentication(options =>{options.DefaultScheme = "cookie";options.DefaultChallengeScheme = "oidc";options.DefaultSignOutScheme = "oidc";}).AddCookie("cookie", options =>{// Configure the cookie with __Host prefix for maximum securityoptions.Cookie.Name = "__Host-blazor";// Because we use an identity server that's configured on a different site// (duendesoftware.com vs localhost), we need to configure the SameSite property to Lax.// Setting it to Strict would cause the authentication cookie not to be sent after logging in.// The user would have to refresh the page to get the cookie.// Recommendation: Set it to 'strict' if your IDP is on the same site as your BFF.options.Cookie.SameSite = SameSiteMode.Lax;}).AddOpenIdConnect("oidc", options =>{options.Authority = "https://demo.duendesoftware.com";options.ClientId = "interactive.confidential";options.ClientSecret = "secret";options.ResponseType = "code";options.ResponseMode = "query";options.GetClaimsFromUserInfoEndpoint = true;options.SaveTokens = true;options.MapInboundClaims = false;options.Scope.Clear();options.Scope.Add("openid");options.Scope.Add("profile");options.Scope.Add("api");options.Scope.Add("offline_access");options.TokenValidationParameters.NameClaimType = "name";options.TokenValidationParameters.RoleClaimType = "role";});// Make sure authentication state is available to all components.builder.Services.AddCascadingAuthenticationState();builder.Services.AddAuthorization();To configure the web app pipeline, add the following after
builder.Build():app.UseRouting();app.UseAuthentication();// Add the BFF middleware which performs anti-forgery protectionapp.UseBff();app.UseAuthorization();app.UseAntiforgery();// In v4, management endpoints (/bff/login, /bff/logout, etc.) are// registered automatically — no call to MapBffManagementEndpoints() needed.app.UseRouting();app.UseAuthentication();// Add the BFF middleware which performs anti-forgery protectionapp.UseBff();app.UseAuthorization();app.UseAntiforgery();// In v3, management endpoints must be registered explicitlyapp.MapBffManagementEndpoints();
Configuring the BffApp.Client project
Section titled “Configuring the BffApp.Client project”-
Configure the Client Project
To add the BFF to the client project, add the following:
Terminal window cd ..cd BlazorBffApp.Clientdotnet add package Duende.BFF.Blazor.ClientThen add the following to your
Program.cs:builder.Services.AddBffBlazorClient(); // Provides auth state provider that polls the /bff/user endpointbuilder.Services.AddCascadingAuthenticationState();Your application is ready to use BFF now.
Configuring your application to use BFF’s features
Section titled “Configuring your application to use BFF’s features”Add the following components to your BlazorBffApp.Client/Components folder:
-
LoginDisplay.razor
The following code shows a login / logout button depending on your authentication state. Note: use the logout link from the
LogoutUrlclaim, because it contains both the correct route and the session id.BlazorBffApp.Client/Components/LoginDisplay.razor @using Duende.Bff.Blazor.Client@using Microsoft.AspNetCore.Components.Authorization@using Microsoft.Extensions.Options@rendermode InteractiveAuto@inject IOptions<BffBlazorClientOptions> Options<AuthorizeView><Authorized><strong>Hello, @context.User.Identity?.Name</strong><a class="nav-link btn btn-link" href="@BffLogoutUrl(context)">Log Out</a></Authorized><Authorizing><a class="nav-link btn btn-link disabled">Log in</a></Authorizing><NotAuthorized><a class="nav-link btn btn-link" href="bff/login">Log in</a></NotAuthorized></AuthorizeView>@code {string BffLogoutUrl(AuthenticationState context){var logoutUrl = context.User.FindFirst(Constants.ClaimTypes.LogoutUrl);return $"{Options.Value.StateProviderBaseAddress}{logoutUrl?.Value}";}} -
RedirectToLogin.razor
The following code will redirect users to the identity provider for authentication. Once authentication is complete, users will be redirected back to where they came from.
BlazorBffApp.Client/Components/RedirectToLogin.razor @inject NavigationManager Navigation@rendermode InteractiveAuto@code {protected override void OnInitialized(){var returnUrl = Uri.EscapeDataString("/" + Navigation.ToBaseRelativePath(Navigation.Uri));Navigation.NavigateTo($"bff/login?returnUrl={returnUrl}", forceLoad: true);}} -
Modifications to Routes.razor
Replace the contents of
Routes.razorso it matches below:BlazorBffApp.Client/Routes.razor @using Microsoft.AspNetCore.Components.Authorization@using BlazorBffApp.Client.Components<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }"><Found Context="routeData"><AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"><NotAuthorized>@if (context.User.Identity?.IsAuthenticated != true){<RedirectToLogin />}else{<p role="alert">You (@context.User.Identity?.Name) are not authorized to access this resource.</p>}</NotAuthorized></AuthorizeRouteView><FocusOnNavigate RouteData="routeData" Selector="h1" /></Found></Router>This ensures that accessing a page that requires authorization automatically redirects the user to the identity provider for authentication.
-
Modifications to MainLayout.razor
Modify your
MainLayout.razorto include theLoginDisplay:BlazorBffApp.Client/Layout/MainLayout.razor @inherits LayoutComponentBase@using BlazorBffApp.Client.Components<div class="page"><div class="sidebar"><NavMenu /></div><main><div class="top-row px-4"><LoginDisplay /></div><article class="content px-4">@Body</article></main></div><div id="blazor-error-ui" data-nosnippet>An unhandled error has occurred.<a href="." class="reload">Reload</a><span class="dismiss">🗙</span></div>Now your application supports logging in and out.
Exposing APIs
Section titled “Exposing APIs”Now we’re going to expose an embedded API for weather forecasts to Blazor WebAssembly (WASM) and call it via an HttpClient.
-
Configuring the Client app
Add a class called
WeatherHttpClientto theBlazorBffApp.Clientproject:BlazorBffApp.Client/WeatherHttpClient.cs public class WeatherHttpClient(HttpClient client) : IWeatherClient{public async Task<WeatherForecast[]> GetWeatherForecasts() =>await client.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast")?? throw new JsonException("Failed to deserialize");}public class WeatherForecast{public DateOnly Date { get; set; }public int TemperatureC { get; set; }public string? Summary { get; set; }public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);}// The IWeatherClient interface abstracts between server and client implementations.public interface IWeatherClient{Task<WeatherForecast[]> GetWeatherForecasts();}Then register this in
Program.cs:BlazorBffApp.Client/Program.cs builder.Services.AddBffBlazorClient() // Provides auth state provider that polls the /bff/user endpoint// Register an HTTP client configured to fetch data from the BFF host..AddLocalApiHttpClient<WeatherHttpClient>();// Register the concrete implementation with the abstractionbuilder.Services.AddSingleton<IWeatherClient, WeatherHttpClient>(); -
Configuring the server
Add a class called
ServerWeatherClientto yourBlazorBffAppserver project:BlazorBffApp/ServerWeatherClient.cs public class ServerWeatherClient : IWeatherClient{public Task<WeatherForecast[]> GetWeatherForecasts(){var startDate = DateOnly.FromDateTime(DateTime.Now);string[] summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast{Date = startDate.AddDays(index),TemperatureC = Random.Shared.Next(-20, 55),Summary = summaries[Random.Shared.Next(summaries.Length)]}).ToArray());}}Then add an endpoint to your HTTP pipeline and register the server implementation:
BlazorBffApp/Program.cs // Register the server-side implementationbuilder.Services.AddSingleton<IWeatherClient, ServerWeatherClient>();// ...app.MapGet("/WeatherForecast", (IWeatherClient weatherClient) => weatherClient.GetWeatherForecasts()).RequireAuthorization().AsBffApiEndpoint(); -
Displaying Weather Information From The API
By default, the Blazor template ships with a weather page. Change the content of
Weather.razorto this:BlazorBffApp.Client/Pages/Weather.razor @page "/weather"@using BlazorBffApp.Client.Components@using Microsoft.AspNetCore.Authorization@rendermode InteractiveWebAssembly@attribute [Authorize]<PageTitle>Weather</PageTitle><WeatherComponent @rendermode="new InteractiveWebAssemblyRenderMode()" />Now add a
WeatherComponent.razor:BlazorBffApp.Client/Components/WeatherComponent.razor @inject IWeatherClient WeatherClient<h1>Weather</h1><p>This component demonstrates showing data.</p>@if (forecasts == null){<p><em>Loading...</em></p>}else{<table class="table"><thead><tr><th>Date</th><th aria-label="Temperature in Celsius">Temp. (C)</th><th aria-label="Temperature in Fahrenheit">Temp. (F)</th><th>Summary</th></tr></thead><tbody>@foreach (var forecast in forecasts){<tr><td>@forecast.Date.ToShortDateString()</td><td>@forecast.TemperatureC</td><td>@forecast.TemperatureF</td><td>@forecast.Summary</td></tr>}</tbody></table>}@code {private WeatherForecast[]? forecasts;protected override async Task OnInitializedAsync(){forecasts = await WeatherClient.GetWeatherForecasts();}}
See Also
Section titled “See Also”- Single Frontend Getting Started — Simpler setup for a single SPA
- Blazor Fundamentals — Rendering modes, data access patterns, and auth state
- Local APIs — Embedding API endpoints in the BFF host
- Token Management — How BFF handles access token refresh automatically
- Server-Side Sessions — Persisting sessions with Entity Framework
- Access Token Management — The underlying token lifecycle library
- Troubleshooting — Common Blazor BFF issues and fixes