Skip to content

Blazor Applications

This quickstart walks you through how to create a BFF Blazor application. The source code for this quickstart is available on GitHub.

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.

  1. Create a Blazor App

    Terminal window
    mkdir BlazorBffApp
    cd BlazorBffApp
    dotnet new blazor --interactivity auto --all-interactive

    This creates a Blazor application with a Server project and a client project.

  2. Configure the BffApp Server Project

    To configure the server, the first step is to add the BFF Blazor package.

    Terminal window
    cd BlazorBffApp
    dotnet add package Duende.BFF.Blazor

    Then configure the application to use BFF. Add this to your services:

    // BFF setup for Blazor
    builder.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();

    To configure the web app pipeline, add the following after builder.Build():

    app.UseRouting();
    app.UseAuthentication();
    // Add the BFF middleware which performs anti-forgery protection
    app.UseBff();
    app.UseAuthorization();
    app.UseAntiforgery();
    // In v4, management endpoints (/bff/login, /bff/logout, etc.) are
    // registered automatically — no call to MapBffManagementEndpoints() needed.
  1. Configure the Client Project

    To add the BFF to the client project, add the following:

    Terminal window
    cd ..
    cd BlazorBffApp.Client
    dotnet add package Duende.BFF.Blazor.Client

    Then add the following to your Program.cs:

    builder.Services
    .AddBffBlazorClient(); // Provides auth state provider that polls the /bff/user endpoint
    builder.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:

  1. LoginDisplay.razor

    The following code shows a login / logout button depending on your authentication state. Note: use the logout link from the LogoutUrl claim, 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}";
    }
    }
  2. 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);
    }
    }
  3. Modifications to Routes.razor

    Replace the contents of Routes.razor so 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.

  4. Modifications to MainLayout.razor

    Modify your MainLayout.razor to include the LoginDisplay:

    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.

Now we’re going to expose an embedded API for weather forecasts to Blazor WebAssembly (WASM) and call it via an HttpClient.

  1. Configuring the Client app

    Add a class called WeatherHttpClient to the BlazorBffApp.Client project:

    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 abstraction
    builder.Services.AddSingleton<IWeatherClient, WeatherHttpClient>();
  2. Configuring the server

    Add a class called ServerWeatherClient to your BlazorBffApp server 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 implementation
    builder.Services.AddSingleton<IWeatherClient, ServerWeatherClient>();
    // ...
    app.MapGet("/WeatherForecast", (IWeatherClient weatherClient) => weatherClient.GetWeatherForecasts())
    .RequireAuthorization()
    .AsBffApiEndpoint();
  3. Displaying Weather Information From The API

    By default, the Blazor template ships with a weather page. Change the content of Weather.razor to 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();
    }
    }