Getting Started - Single Frontend
Duende.BFF (Backend for Frontend) is a library that helps you build secure, modern web applications by acting as a security gateway between your frontend and backend APIs. This guide will walk you through setting up a simple BFF application with a single frontend.
What You’ll Build
Section titled “What You’ll Build”By the end of this guide you will have an ASP.NET Core host that:
- Authenticates users via OpenID Connect and stores the session server-side
- Exposes secure local API endpoints with CSRF protection
- Optionally proxies remote API calls with automatic token attachment
Setting Up A BFF project
Section titled “Setting Up A BFF project”-
Create A New ASP.NET Core Project
Create a new ASP.NET Core Web Application:
Terminal window dotnet new web -n MyBffAppcd MyBffApp -
Add The Duende.BFF NuGet Package
Install the Duende.BFF package:
Terminal window dotnet add package Duende.BFF -
Configure BFF In
Program.csAdd the following to your
Program.cs: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");// Add this scope if you want to receive refresh tokensoptions.Scope.Add("offline_access");}).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;});builder.Services.AddAuthorization();var app = builder.Build();app.UseAuthentication();app.UseRouting();// adds antiforgery protection for local APIsapp.UseBff();// adds authorization for local and remote API endpointsapp.UseAuthorization();app.Run();builder.Services.AddBff();// 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");// Add this scope if you want to receive refresh tokensoptions.Scope.Add("offline_access");});builder.Services.AddAuthorization();var app = builder.Build();app.UseAuthentication();app.UseRouting();// adds antiforgery protection for local APIsapp.UseBff();// adds authorization for local and remote API endpointsapp.UseAuthorization();// login, logout, user, backchannel logout...app.MapBffManagementEndpoints();app.Run();Make sure to replace the Authority, ClientID and ClientSecret with values from your identity provider. Also consider if the scopes are correct.
-
Adding Local APIs
If your browser-based application uses local APIs, you can add those directly to your BFF app. The BFF supports both controllers and minimal APIs to create local API endpoints.
It’s important to mark up the APIs with .AsBffApiEndpoint(), because this adds CSRF protection.
Program.cs // Adds authorization for local and remote API endpointsapp.UseAuthorization();// Place your custom routes after the 'UseAuthorization()'app.MapGet("/hello-world", () => "hello-world").AsBffApiEndpoint(); // Adds CSRF protection to the controller endpointsProgram.cs builder.Services.AddControllers();// ...app.UseAuthorization();// When mapping the api controllers, place this after // UseAuthorization()app.MapControllers().RequireAuthorization().AsBffApiEndpoint(); // This statement adds CSRF protection to the controller endpointsLocalApiController.cs [Route("hello")]public class LocalApiController : ControllerBase{[Route("world")][HttpGet]public IActionResult SelfContained(){return Ok("hello world");}} -
Adding Remote APIs
If you also want to call remote api’s from your browser based application, then you should proxy the calls through the BFF.
The BFF extends the capabilities of Yarp in order to achieve this.
Terminal dotnet add package Duende.BFF.YarpProgram.cs builder.Services.AddBff().AddRemoteApis(); // Adds the capabilities needed to perform proxying to remote APIs.// ...// Map any call (including child routes) from /api/remote to https://remote-api-addressapp.MapRemoteBffApiEndpoint("/api/remote", new Uri("https://remote-api-address")).WithAccessToken(RequiredTokenType.Client);Program.cs builder.Services.AddBff().AddRemoteApis() // This adds the capabilities needed to perform proxying to remote api's..AddYarpConfig(new RouteConfig() // This statement configures yarp.{RouteId = "route_id",ClusterId = "cluster_id",Match = new RouteMatch{Path = $"api/remote/{{**catch-all}}"}}, new ClusterConfig(){ClusterId = "cluster_id",Destinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase){{ "destination_1", new DestinationConfig { Address = "https://remote-api-address" } }}});// ...app.UseAuthorization();// Add the Yarp middleware that will proxy the requests.app.MapReverseProxy(proxyApp => {proxyApp.UseAntiforgeryCheck();});You can also use an
IConfigurationinstead of programmatically configuring the proxy. -
Adding Server-Side Sessions
By default, Duende.BFF uses an in-memory session store. This is suitable for development and testing, but not recommended for production as sessions will be lost when the application restarts.
Program.cs builder.Services.AddBff().AddServerSideSessions(); // Uses in-memory session store by default// ...existing code for authentication, authorization, etc.For production scenarios, you can use Entity Framework to persist sessions in a database. First, add the NuGet package:
Terminal dotnet add package Duende.BFF.EntityFrameworkThen configure the session store in your
Program.cs:Program.cs builder.Services.AddBff().AddServerSideSessions().AddEntityFrameworkServerSideSessions(options =>{options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));});// ...existing code for authentication, authorization, etc.You will also need to run the Entity Framework migrations to create the necessary tables.
Frontend Integration
Section titled “Frontend Integration”With the BFF host running, your frontend (JavaScript SPA, React, Angular, etc.) needs to call a few BFF endpoints for authentication and to make API calls. Below is a minimal vanilla JavaScript pattern you can adapt.
Check the current user session
Section titled “Check the current user session”On load, call /bff/user to check whether the user is logged in. This endpoint returns the user’s claims as JSON when authenticated, or a 401/empty response when anonymous.
// Fetch the current user from the BFF sessionasync function getUser() { const response = await fetch('/bff/user', { headers: { 'X-CSRF': '1' } });
if (response.ok) { return await response.json(); // Array of { type, value } claim objects }
return null; // Not authenticated}Login and logout links
Section titled “Login and logout links”Use plain anchor tags pointing to the BFF management endpoints. Do not use fetch for these — they must trigger a full browser redirect.
<!-- Login: redirects to the identity provider --><a href="/bff/login">Log in</a>
<!-- Logout: the href must include the session ID from the logout_url claim --><!-- Read this from the /bff/user response, not hardcoded --><a id="logout-link">Log out</a>// Wire up logout link with the session-bound URL from /bff/userconst user = await getUser();if (user) { const logoutUrlClaim = user.find(c => c.type === 'bff:logout_url'); document.getElementById('logout-link').href = logoutUrlClaim?.value ?? '/bff/logout';}Calling BFF API endpoints
Section titled “Calling BFF API endpoints”Every request to a BFF API endpoint must include the X-CSRF: 1 header. Without it, the BFF will reject the request with 401 Unauthorized.
// Centralized fetch wrapper — always add the X-CSRF headerasync function bffFetch(url, options = {}) { const response = await fetch(url, { ...options, headers: { 'X-CSRF': '1', ...options.headers, }, });
// Redirect to login if the session has expired if (response.status === 401) { window.location.href = `/bff/login?returnUrl=${encodeURIComponent(window.location.pathname)}`; return; }
return response;}
// Example usageconst data = await bffFetch('/api/weather');const json = await data.json();Proactive session polling (optional)
Section titled “Proactive session polling (optional)”To detect server-initiated session termination (e.g., back-channel logout), poll /bff/user periodically:
// Poll every 60 seconds; redirect to login if session endssetInterval(async () => { const user = await getUser(); if (!user) { window.location.href = '/bff/login'; }}, 60_000);See Also
Section titled “See Also”- Multiple Frontends — Serve several SPAs from the same BFF host
- Blazor Applications — BFF setup for Blazor Server and WASM
- Local APIs — Full reference for embedded API endpoints and CSRF protection
- Remote APIs — Direct forwarding to upstream services
- Token Management — How BFF handles access token refresh automatically
- Server-Side Sessions — Persistent session configuration
- Access Token Management — The underlying token lifecycle library used by BFF