Troubleshooting
This page covers the most common problems encountered when building and operating a Duende BFF application. Each scenario is described in symptom → cause → solution format.
Symptom: Anti-Forgery Token Validation Failures — 401 Unauthorized with missing X-CSRF header
Section titled “Symptom: Anti-Forgery Token Validation Failures — 401 Unauthorized with missing X-CSRF header”Cause: The BFF enforces the presence of a custom X-CSRF: 1 header on all API endpoints decorated with .AsBffApiEndpoint(). Requests that do not include this header are rejected.
Solution:
Add the X-CSRF: 1 header to every fetch() call targeting a BFF API endpoint. The easiest approach is a centralized wrapper:
function bffFetch(url, options = {}) { return fetch(url, { ...options, headers: { 'X-CSRF': '1', ...options.headers, }, });}Also verify that:
app.UseBff()appears afterapp.UseRouting()andapp.UseAuthentication(), and beforeapp.UseAuthorization()in your middleware pipeline.- The endpoint is decorated with
.AsBffApiEndpoint()(Minimal API) or[BffApi]/.AsBffApiEndpoint()at mapping time (MVC).
See Middleware Pipeline for the canonical order and a table of common mistakes.
Symptom: CORS Errors With BFF Endpoints — failed OPTIONS preflight on /bff/user or API endpoints
Section titled “Symptom: CORS Errors With BFF Endpoints — failed OPTIONS preflight on /bff/user or API endpoints”Cause: The BFF and the SPA are on different origins. CORS errors here are usually a sign that the BFF and frontend are not being served from the same origin, which defeats part of the BFF pattern’s security model.
Solution:
The BFF is designed to serve the frontend from the same origin. If you must host them on different origins, configure a CORS policy that explicitly allows the SPA origin and allows credentials:
builder.Services.AddCors(options =>{ options.AddPolicy("SpaPolicy", policy => { policy.WithOrigins("https://app.example.com") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); // Required for cookie-based auth });});
// Must come before UseAuthentication and UseBffapp.UseCors("SpaPolicy");Symptom: Session Expiration Causing Silent Failures — SPA stops receiving data, 401 with no user-facing error
Section titled “Symptom: Session Expiration Causing Silent Failures — SPA stops receiving data, 401 with no user-facing error”Cause: The BFF session (stored in the authentication cookie) has expired. BFF API endpoints return 401 instead of a redirect when the session expires, so the SPA must handle this explicitly.
Solution:
Detect 401 responses in your fetch wrapper and redirect to the BFF login endpoint:
async function bffFetch(url, options = {}) { const response = await fetch(url, { ...options, headers: { 'X-CSRF': '1', ...options.headers }, });
if (response.status === 401) { window.location.href = `/bff/login?returnUrl=${encodeURIComponent(window.location.pathname)}`; return; }
return response;}Also consider:
- Polling
/bff/userperiodically to detect session expiry proactively. - Configuring absolute and sliding session lifetimes on the cookie handler to match your requirements.
- Using server-side sessions to enable server-initiated session termination.
Symptom: YARP Proxy Misconfiguration — proxied requests return 404, missing token, or bypass anti-forgery
Section titled “Symptom: YARP Proxy Misconfiguration — proxied requests return 404, missing token, or bypass anti-forgery”Cause: Common YARP configuration mistakes include:
- Missing
UseAntiforgeryCheck()in theMapReverseProxypipeline. - Typos in metadata keys when using
appsettings.jsonconfiguration. - Route patterns that don’t include
{**catch-all}to capture sub-paths.
Solution:
Ensure UseAntiforgeryCheck() is explicitly included:
app.MapReverseProxy(proxyApp =>{ proxyApp.UseAntiforgeryCheck(); // Required — not automatic for YARP});When configuring via appsettings.json, metadata keys are case-sensitive:
"Metadata": { "Duende.Bff.Yarp.TokenType": "User", "Duende.Bff.Yarp.AntiforgeryCheck": "true"}For route patterns, ensure sub-paths are captured:
"Match": { "Path": "/api/{**catch-all}" }Symptom: Blazor WASM — Token Not Available in Components, exception or null when calling GetUserAccessTokenAsync
Section titled “Symptom: Blazor WASM — Token Not Available in Components, exception or null when calling GetUserAccessTokenAsync”Cause: In Blazor WASM, HttpContext is not available. Access tokens are managed server-side by the BFF host and must never be exposed to client-side components.
Solution:
Use AddLocalApiHttpClient<T>() to register a typed HTTP client that routes through the BFF host. The BFF host attaches the token server-side before forwarding:
// Client-side Program.csbuilder.Services .AddBffBlazorClient() .AddLocalApiHttpClient<WeatherHttpClient>();The WeatherHttpClient then calls the BFF host’s local API endpoint (which does have access to HttpContext and can call GetUserAccessTokenAsync()), rather than calling the remote API directly.
Symptom: Silent Login Failures — prompt=none fails in Safari/Firefox, users unexpectedly logged out
Section titled “Symptom: Silent Login Failures — prompt=none fails in Safari/Firefox, users unexpectedly logged out”Cause: Modern browsers block third-party cookies. The prompt=none / silent renew flow in traditional SPAs relies on an iframe that sends a cookie to the identity provider — this breaks when third-party cookies are blocked.
Solution:
The BFF pattern is specifically designed to avoid this problem. Token renewal is handled server-side using refresh tokens, which do not rely on third-party cookies. Ensure:
offline_accessscope is requested so a refresh token is issued.SaveTokens = trueis set on the OIDC handler.- The BFF’s
Duende.AccessTokenManagementintegration is active (it is by default).
options.Scope.Add("offline_access"); // Required for refresh tokensoptions.SaveTokens = true; // Required to store tokens in the sessionSee Third-Party Cookies for a deeper discussion of how browser cookie restrictions affect authentication flows.
Symptom: 302 Redirect Instead of 401 on API Endpoints — SPA receives HTML instead of JSON
Section titled “Symptom: 302 Redirect Instead of 401 on API Endpoints — SPA receives HTML instead of JSON”Cause: The API endpoint is not marked as a BFF API endpoint, so ASP.NET Core’s default challenge behavior (302 redirect) applies instead of BFF’s 401 response.
Solution:
Add .AsBffApiEndpoint() to the endpoint:
// Minimal APIapp.MapGet("/api/data", () => Results.Ok("data")) .RequireAuthorization() .AsBffApiEndpoint(); // Converts 302 challenge to 401
// MVC controllersapp.MapControllers() .RequireAuthorization() .AsBffApiEndpoint();This instructs the BFF middleware to return 401 for unauthenticated requests rather than issuing a redirect challenge. Your SPA can then detect the 401 and navigate to /bff/login.
Symptom: Cookie Size Exceeding Browser Limits — users with many roles cannot log in, cookie silently dropped
Section titled “Symptom: Cookie Size Exceeding Browser Limits — users with many roles cannot log in, cookie silently dropped”Cause: All claims are stored in the authentication cookie by default. Large numbers of claims (e.g., from many roles or large identity tokens) can cause the cookie to exceed the 4KB browser limit. ASP.NET Core chunks cookies, but excessively large sessions still cause issues.
Solution:
Switch to server-side sessions. The browser cookie then only holds a session ID (a small opaque value), and all claims are stored in the server-side session store:
builder.Services.AddBff() .AddEntityFrameworkServerSideSessions(options => { options.UseSqlServer(connectionString); });Additionally, filter unnecessary claims from the session using an IClaimsTransformation or by configuring the OIDC handler to not request unnecessary scopes:
// Only request claims you actually needoptions.Scope.Clear();options.Scope.Add("openid");options.Scope.Add("profile");// Don't add scopes whose claims you don't useSee Also
Section titled “See Also”- Getting Started: Single Frontend — Correct initial setup
- Getting Started: Blazor — Blazor-specific configuration
- Local APIs — CSRF protection for embedded API endpoints
- YARP Integration — Advanced proxy configuration
- Server-Side Sessions — Production session persistence
- Token Management — Access token refresh and revocation
- Third-Party Cookies — Browser cookie restrictions and BFF
- Access Token Management — The underlying token lifecycle library