| title | Getting Started - Single Frontend | ||||
|---|---|---|---|---|---|
| description | A guide on how to create a BFF application with a single frontend. | ||||
| sidebar |
|
import { Code } from "@astrojs/starlight/components"; import { Tabs, TabItem } from "@astrojs/starlight/components";
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.
:::note Duende.BFF V4 introduced a new way of configuring the BFF, which automatically configures the BFF using recommended practices. If you're upgrading from V3, please refer to the upgrade guide.
When in single frontend mode, an implicit default frontend is automatically registered. This ensures all the management routes and OpenID Connect-handling routes are available for your frontend.
When you call .AddFrontend() to add a new frontend, the system switches to multi-frontend mode. If you wish to have a default frontend in multi-frontend mode, you'll need to explicitly add it. See multi-frontend support for more information on this topic.
:::
- .NET 8.0 or later
- A frontend application (e.g., React, Angular, Vue, or plain JavaScript)
Create a new ASP.NET Core Web Application:
dotnet new web -n MyBffApp
cd MyBffAppInstall the Duende.BFF package:
dotnet add package Duende.BFFAdd the following to your Program.cs:
{/* prettier-ignore /} {/ prettier-ignore */} ```csharp 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();
```
// Configure the authentication
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options =>
{
// Configure the cookie with __Host prefix for maximum security
options.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 tokens
options.Scope.Add("offline_access");
});
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();
// 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.
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.
{/* prettier-ignore /} {/ prettier-ignore */}
<Code
lang="csharp"
title="Program.cs"
code={`// 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`}/>
<Code
lang="csharp"
title="Program.cs"
code={`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 endpoints`}/>
<Code
lang="csharp"
title="LocalApiController.cs"
code={`[Route("hello")]
public class LocalApiController : ControllerBase { [Route("world")] [HttpGet] public IActionResult SelfContained() { return Ok("hello world"); } }`}/>
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.
dotnet add package Duende.BFF.Yarp{/* prettier-ignore /} {/ prettier-ignore */}
<Code
lang="csharp"
title="Program.cs"
code={`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);`}/>
{/* prettier-ignore */} <Code
lang="csharp"
title="Program.cs"
code={`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 `IConfiguration` instead of programmatically configuring the proxy.
{/* prettier-ignore /} {/ prettier-ignore */}
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.
<Code
lang="csharp"
title="Program.cs"
code={`builder.Services.AddBff()
.AddServerSideSessions(); // Uses in-memory session store by default
// ...existing code for authentication, authorization, etc.`}/>
{/* prettier-ignore */}For production scenarios, you can use Entity Framework to persist sessions in a database. First, add the NuGet package:
<Code
lang="bash"
title="Terminal"
meta="Terminal"
code={`dotnet add package Duende.BFF.EntityFramework`}/>
Then configure the session store in your Program.cs:
<Code
lang="csharp"
title="Program.cs"
code={`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.