Duende BFF (Backend for Frontend)
When to Use This Skill
- Building or securing a SPA (React, Angular, Vue, Blazor WASM) that calls APIs requiring authentication
- Implementing the Backend-for-Frontend security pattern to keep access tokens out of the browser
- Configuring BFF session management, login/logout endpoints, and server-side sessions
- Proxying requests from a frontend to remote APIs while automatically attaching access tokens
- Adding CSRF/anti-forgery protection to APIs consumed by browser-based applications
- Integrating
Duende.BFFwithDuende.AccessTokenManagementfor automatic token refresh - Deploying a BFF behind a reverse proxy or configuring same-site cookie behavior
Core Principles
- Tokens Never Touch the Browser — The BFF holds all OAuth tokens server-side; the browser only ever sees an HTTP-only, Secure, SameSite cookie
- CSRF Protection Is Mandatory — Every BFF API endpoint must require the
X-CSRF: 1header; use.AsBffApiEndpoint()orMapRemoteBffApiEndpoint— never skip it without an explicit alternative - Cookie Configuration Determines Security Posture —
SameSite=Strictis preferred when the IDP is on the same site;Laxis acceptable when cross-site redirects are required after login - Server-Side Sessions for Production — The default in-memory cookie session is unsuitable for production; persist sessions with
Duende.BFF.EntityFramework - Token Management Is Automatic — BFF integrates with
Duende.AccessTokenManagement; never manually refresh tokens or pass raw access tokens to the frontend
Docs: https://docs.duendesoftware.com/bff/
Pattern 1: Setup and Registration (BFF v4)
BFF v4 uses a streamlined registration API that auto-configures OpenID Connect and cookie authentication with recommended defaults.
BFF v3 Registration
For projects still on v3, explicit scheme setup is required and MapBffManagementEndpoints() must be called manually:
Key Differences: V4 vs V3
Pattern 2: Login and Logout Endpoints
In BFF v4, management endpoints (/bff/login, /bff/logout, /bff/user, /bff/backchannel-logout) are registered automatically by AddBff() with the implicit default frontend. In v3, they require an explicit call to MapBffManagementEndpoints().
Login — A browser navigation to /bff/login initiates an OIDC Authorization Code flow. After the IDP redirects back, the BFF sets an HTTP-only authentication cookie.
Logout — A browser navigation to /bff/logout signs the user out locally and initiates an OIDC end_session flow. It also revokes the refresh token automatically.
Pattern 3: CSRF / Anti-Forgery Protection
The BFF enforces a custom X-CSRF header on every protected endpoint. This triggers a CORS preflight for cross-origin requests, effectively preventing CSRF attacks. The header value is irrelevant — its presence is sufficient.
Local (Embedded) API Endpoints
Middleware Order
UseBff() must appear after UseRouting() but before UseAuthorization(). Incorrect order silently disables anti-forgery enforcement.
Skipping Anti-Forgery
For specific endpoints that cannot send the anti-forgery header (e.g., webhook receivers), use .SkipAntiforgery():
Skipping Response Handling (V4)
By default, BFF converts 401/403 responses from local API endpoints into JSON-friendly responses (no redirect). Use .SkipResponseHandling() to bypass this and trigger normal ASP.NET Core authentication redirects:
Conditional Anti-Forgery (V4)
In v4, DisableAntiForgeryCheck is a delegate that allows conditionally skipping anti-forgery per-request:
Pattern 4: Remote API Proxying
The BFF can act as a reverse proxy to APIs deployed on separate hosts. Requests carry only the session cookie; the BFF exchanges it for an access token before forwarding.
Install the YARP integration package:
Token Type Options
Custom Access Token Retriever (V4)
Implement IAccessTokenRetriever to customize per-route token retrieval. In v4, DefaultAccessTokenRetriever is internal — implement the interface directly:
ForwarderRequestConfig (V4)
Configure per-endpoint activity timeout and response buffering for remote API proxying:
Pattern 5: Session Management
Server-Side Sessions
Default cookie-based sessions embed claims and tokens in the cookie. For production, move session data server-side: the cookie only carries a session ID, keeping cookie size small and enabling server-initiated revocation.
Tokens never touch the cookie with server-side sessions. All tokens — including refresh tokens — live in the server-side session store; the cookie holds only the session id. This is why the store choice is a security/availability decision, not just a size optimization.
The in-memory store is not durable and not shared: sessions are lost on process restart, and in a load-balanced deployment a request routed to a different instance won't find the session (the user appears logged out). For any multi-node BFF, use a persistent, shared store — the EF store from
Duende.BFF.EntityFramework(AddEntityFrameworkServerSideSessions).
EF Migrations for Session Store
Pattern 6: Token Management Integration
BFF integrates with Duende.AccessTokenManagement (ATM) automatically when SaveTokens = true is set on the OIDC handler. Tokens are stored in the server-side session and refreshed transparently.
Refresh Token Revocation
BFF revokes refresh tokens automatically at logout. Configure rotation behavior on IdentityServer — BFF clients are confidential clients and do not need rotating (one-time-use) refresh tokens.
Pattern 7: SPA Integration
Session Check Endpoint (/bff/user)
The /bff/user endpoint returns the current user's claims or 401. Use it on SPA startup to determine authentication state.
Fetch Wrapper for CSRF Header
Every fetch() call to a BFF API endpoint must include X-CSRF: 1. Wrap fetch globally rather than adding it to every call site.
Handling 401 and Session Expiry
BFF API endpoints return 401 (not a redirect) when the session has expired. The SPA must detect this and redirect to /bff/login.
Login and Logout Links
Login and logout are browser navigations, not fetch calls. Do not use fetch or XMLHttpRequest for these flows.
Pattern 8: Deployment Considerations
SameSite Cookie Configuration
Reverse Proxy / Path Base
When the BFF is hosted behind a reverse proxy (e.g., nginx, Azure Application Gateway), configure forwarded headers and path base so authentication callbacks resolve correctly.
CORS Policy
The BFF serves the SPA from the same origin, so CORS is typically not needed between the SPA and BFF. CORS should be configured only for cross-origin scenarios.
Data Protection in Clustered Deployments
When running multiple BFF instances, cookies and anti-forgery tokens must be decryptable by all nodes. Configure a shared Data Protection key store. See ASP.NET Core Data Protection for comprehensive configuration guidance — BFF depends on Data Protection equally to IdentityServer.
Pattern 9: YARP Reverse Proxy Integration
For complex proxying scenarios, BFF integrates with YARP (Yet Another Reverse Proxy) via the Duende.BFF.Yarp package, which provides full BFF token management and anti-forgery enforcement inside the YARP pipeline.
Setup with In-Code Configuration
YARP Configuration via appsettings.json
When using JSON configuration instead of LoadFromMemory, set BFF behavior via route metadata:
Warning: Metadata keys (
Duende.Bff.Yarp.TokenType,Duende.Bff.Yarp.AntiforgeryCheck) are case-sensitive strings. Typos fail silently — no token is attached and no anti-forgery check is performed.
YARP Code Configuration Extensions
Note: YARP routes use TokenType (not RequiredTokenType which is used by MapRemoteBffApiEndpoint).
Pattern 10: Multi-Frontend (V4)
BFF v4 supports serving multiple frontends from a single BFF host. Each frontend gets its own OIDC, cookie, and API configuration. The default single-frontend behavior is an implicit multi-frontend setup with one frontend.
AutomaticallyRegisterBffMiddleware
By default, BFF middleware is auto-registered. In multi-frontend scenarios, disable this for manual control:
Frontend Configuration (Code)
IIndexHtmlTransformer
Implement IIndexHtmlTransformer to inject frontend-specific configuration into the index.html before serving:
IndexHtmlDefaultCacheDuration
Control CDN index.html cache duration (default 5 minutes):
Pattern 11: Blazor Integration
Blazor Server
AddBlazorServer() integrates BFF session management with Blazor Server's circuit model. Long-lived circuits may encounter expired sessions — configure appropriate polling intervals via BffBlazorServerOptions.
Blazor WASM (Client)
BffBlazorServerOptions
BffBlazorClientOptions
BffOptions Reference
V4 Breaking Change:
EnableSessionCleanuphas been removed. Use.AddSessionCleanupBackgroundProcess()on the BFF builder instead.
Extensibility: Logout Endpoint (V4)
Customize the logout endpoint by implementing ILogoutEndpoint:
Validate return URLs with IReturnUrlValidator to prevent open redirector attacks.
Extensibility: Session Store (V4)
V4 uses UserSessionKey and PartitionKey types instead of raw strings. The IUserSessionStore interface:
Register a custom store: .AddServerSideSessions<YourCustomStore>()
Session cleanup is a separate concern — implement IUserSessionStoreCleanup and register with .AddSessionCleanupBackgroundProcess().
Common Pitfalls
-
Calling
/bff/loginor/bff/logoutviafetch()— These endpoints trigger OIDC redirects and must be browser navigations (window.location.href), not AJAX calls. -
Omitting
offline_accessscope — Without a refresh token, BFF cannot automatically renew expired access tokens. The user will receive 401 errors from remote APIs when their access token expires. -
Using in-memory sessions in production —
AddServerSideSessions()without EF means sessions vanish on restart and cannot be shared across instances. Always useAddEntityFrameworkServerSideSessions()in production. -
Forgetting
SaveTokens = true— Without this, OIDC tokens are not stored in the session, andGetUserAccessTokenAsync()returns nothing. Token management silently fails. -
Missing
X-CSRF: 1header in SPA fetch calls — BFF returns 401 for API requests without the header. Centralize header injection in a fetch wrapper rather than adding it to each call site. -
Incorrect middleware order —
UseBff()must come afterUseRouting()and beforeUseAuthorization(). Any deviation silently breaks anti-forgery enforcement without a clear error. -
Exposing access tokens to the frontend — Returning token values from a local API endpoint to JavaScript completely defeats the BFF pattern and its token-theft protections.
-
Using
SameSite=Strictwith a cross-site IDP — After the OIDC redirect back from the IDP, the browser won't send the post-login session cookie on the first request because it was a cross-site navigation. UseLaxwhen the IDP is on a different site. -
Forgetting to revoke the refresh token on logout — BFF does this automatically, but if
RevokeRefreshTokenOnLogout = falseis set, abandoned sessions retain valid refresh tokens indefinitely. -
Not configuring Data Protection in multi-instance deployments — Cookie decryption failures manifest as users being perpetually logged out in load-balanced environments.
-
YARP metadata key typos — When using appsettings.json configuration for YARP, the metadata keys (
Duende.Bff.Yarp.TokenType,Duende.Bff.Yarp.AntiforgeryCheck) are case-sensitive strings. A typo causes silent failure: no token is attached and no anti-forgery check is performed. -
Forgetting
UseAntiforgeryCheck()in the YARP pipeline — UnlikeMapRemoteBffApiEndpoint, YARP's anti-forgery enforcement is not automatic.proxyApp.UseAntiforgeryCheck()must be explicitly added insideMapReverseProxy; omitting it leaves YARP routes unprotected.
Resources
- Duende BFF Overview
- Getting Started: Single Frontend
- Embedded (Local) APIs
- Proxying Remote APIs
- Multi-Frontend
- Server-Side Sessions
- Token Management
- Extensibility: Tokens
- Extensibility: HTTP Forwarder
- Session Management Endpoints
- BFF Options Reference
- ASP.NET Core Data Protection
- BFF v3 → v4 Upgrade Guide
- NuGet: Duende.BFF
- NuGet: Duende.BFF.Yarp
- NuGet: Duende.BFF.EntityFramework
- Related skills:
aspnetcore-authentication,token-management,identityserver-configuration

