Skip to content

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.

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
  1. Create A New ASP.NET Core Project

    Create a new ASP.NET Core Web Application:

    Terminal window
    dotnet new web -n MyBffApp
    cd MyBffApp
  2. Add The Duende.BFF NuGet Package

    Install the Duende.BFF package:

    Terminal window
    dotnet add package Duende.BFF
  3. Configure BFF In Program.cs

    Add 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 tokens
    options.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 APIs
    app.UseBff();
    // adds authorization for local and remote API endpoints
    app.UseAuthorization();
    app.Run();

    Make sure to replace the Authority, ClientID and ClientSecret with values from your identity provider. Also consider if the scopes are correct.

  4. 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 endpoints
    app.UseAuthorization();
    // Place your custom routes after the 'UseAuthorization()'
    app.MapGet("/hello-world", () => "hello-world")
    .AsBffApiEndpoint(); // Adds CSRF protection to the controller endpoints
  5. 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.Yarp
    Program.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-address
    app.MapRemoteBffApiEndpoint("/api/remote", new Uri("https://remote-api-address"))
    .WithAccessToken(RequiredTokenType.Client);
  6. 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.

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.

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 session
async 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
}

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/user
const user = await getUser();
if (user) {
const logoutUrlClaim = user.find(c => c.type === 'bff:logout_url');
document.getElementById('logout-link').href = logoutUrlClaim?.value ?? '/bff/logout';
}

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 header
async 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 usage
const data = await bffFetch('/api/weather');
const json = await data.json();

To detect server-initiated session termination (e.g., back-channel logout), poll /bff/user periodically:

// Poll every 60 seconds; redirect to login if session ends
setInterval(async () => {
const user = await getUser();
if (!user) {
window.location.href = '/bff/login';
}
}, 60_000);