# Duende BFF Security Framework v3.0 to v4.0

> Guide for upgrading Duende BFF Security Framework from version 3.x to version 4.0, including migration steps for custom implementations and breaking changes.

Duende BFF Security Framework v4.0 is a significant release that includes:

* Multi-frontend support
* OpenTelemetry support
* Support for login prompts
* Several fixes and improvements

The extensibility approach has been drastically changed, and many `virtual` methods containing implementation logic are now internal instead.

## Upgrading

[Section titled âUpgradingâ](#upgrading)

This release introduces many breaking changes. This upgrade guide covers cases where a breaking change was introduced.

### Remote APIs

[Section titled âRemote APIsâ](#remote-apis)

The syntax for configuring remote APIs has changed slightly:

Program.cs

```diff
// Use a client credentials token
 -app.MapRemoteBffApiEndpoint("/api/client-token", "https://localhost:5010")
    -.RequireAccessToken(TokenType.Client);


 +app.MapRemoteBffApiEndpoint("/api/client-token", new Uri("https://localhost:5010"))
    +.WithAccessToken(RequiredTokenType.Client);


// Use the client token only if the user is logged in
 -app.MapRemoteBffApiEndpoint("/api/optional-user-token", "https://localhost:5010")
    -.WithOptionalUserAccessToken();


 +app.MapRemoteBffApiEndpoint("/api/optional-user-token", new Uri("https://localhost:5010"))
    +.WithAccessToken(RequiredTokenType.UserOrNone);
```

* The enum `TokenType` has been renamed to `RequiredTokenType`, and moved from the `Duende.Bff` to `Duende.Bff.AccessTokenManagement` namespace.
* The methods to configure the token type have all been replaced with a new method `WithAccessToken()`
* Requesting an optional access token should no longer be done by calling `WithOptionalUserAccessToken()`. Use `WithAccessToken(RequiredTokenType.UserOrNone)` instead.

### Configuring Token Types In YARP

[Section titled âConfiguring Token Types In YARPâ](#configuring-token-types-in-yarp)

The required token type configuration in YARP has also changed slightly. It uses the enum values from `RequiredTokenType`.

### Extending The BFF

[Section titled âExtending The BFFâ](#extending-the-bff)

#### Service To Endpoint Updates

[Section titled âService To Endpoint Updatesâ](#service-to-endpoint-updates)

Service interfaces and their default implementations have been renamed and have changed, resulting in an updated extensibility model:

* Generally, the interfaces have been renamed, e.g. from `IUserService` to `IUserEndpoint`.

* Default implementation is now internal, but can be used when overriding the endpoint:

  ```diff
  -public class MyUserService : DefaultUserService
  -{
      -public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct)
  -    {
          // Custom logic here


          -return base.ProcessRequestAsync(context);
  -    }
  -}


  +var bffOptions = app.Services.GetRequiredService<IOptions<BffOptions>>().Value;


  +app.MapGet(bffOptions.UserPath, async (HttpContext context, CancellationToken ct) =>
  +{
      // ... custom logic before calling the endpoint implementation ...


      +var endpointProcessor = context.RequestServices.GetRequiredService<IUserEndpoint>();
      +await endpointProcessor.ProcessRequestAsync(context, ct);


      // ... custom logic after calling the  endpoint implementation ...
  +});
  ```

For more information, see the [endpoints documentation](/bff/extensibility/management/).

#### Custom Session Store

[Section titled âCustom Session Storeâ](#custom-session-store)

If you have a custom implementation of `IUserSessionStore`, the interface has changed to support multiple frontends.

In all methods, the `string key` has been replaced with a strongly typed `UserSessionKey` struct, which contains the `PartitionKey` and `SessionId`:

* `PartitionKey` - Corresponds to the frontend name (or `ApplicationName` in V3).
* `SessionId` - The userâs session identifier.

```diff
public class MySessionStore : IUserSessionStore
{
    -public Task<UserSession> GetUserSessionAsync(string key, CancellationToken cancellationToken)
+    public Task<UserSession> GetUserSessionAsync(UserSessionKey key, CancellationToken cancellationToken)
    {
        // ...
    }


    // ...
}
```

Also see [related database changes and migrations](#server-side-sessions-database-migrations).

#### Access Token Retrieval

[Section titled âAccess Token Retrievalâ](#access-token-retrieval)

The `HttpContext.GetUserAccessTokenAsync` extension method has been removed from the `Duende.Bff` namespace. You should now use the extension method from the `Duende.AccessTokenManagement.OpenIdConnect` namespace.

```csharp
using Duende.AccessTokenManagement.OpenIdConnect;


// ...
var token = await HttpContext.GetUserAccessTokenAsync();
```

#### Simplified Wireup Without Explicit Authentication Setup

[Section titled âSimplified Wireup Without Explicit Authentication Setupâ](#simplified-wireup-without-explicit-authentication-setup)

The V3 style of wireup still works, but BFF V4 comes with a newer style of wireup:

```csharp
services.AddBff()
    .ConfigureOpenIdConnect(options =>
    {
        options.Authority = "your authority";
        options.ClientId = "your client id";
        options.ClientSecret = "secret";


        // ... other OpenID Connect options.
    }
    .ConfigureCookies(options => {
        // The cookie options are automatically configured with recommended practices.
        // However, you can change the config here.
    });
```

Adding this will automatically configure a Cookie and OpenID Connect flow.

#### Adding Multiple Frontends

[Section titled âAdding Multiple Frontendsâ](#adding-multiple-frontends)

You can statically add a list of frontends by calling the `AddFrontends` method.

```csharp
.AddFrontends(
    new BffFrontend(BffFrontendName.Parse("default-frontend"))
        .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")),


    new BffFrontend(BffFrontendName.Parse("with-path"))
        .WithOpenIdConnectOptions(opt =>
        {
            opt.ClientId = "bff.multi-frontend.with-path";
            opt.ClientSecret = "secret";
        })
        .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html"))
        .MapToPath("/with-path"),


    new BffFrontend(BffFrontendName.Parse("with-domain"))
        .WithOpenIdConnectOptions(opt =>
        {
            opt.ClientId = "bff.multi-frontend.with-domain";
            opt.ClientSecret = "secret";
        })
        .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html"))
        .MapToHost(HostHeaderValue.Parse("https://app1.localhost:5005"))
        .WithRemoteApis(
            new RemoteApi("/api/user-token", new Uri("https://localhost:5010")),
            new RemoteApi("/api/client-token", new Uri("https://localhost:5010"))
)
```

#### Loading Configuration From `IConfiguration`

[Section titled âLoading Configuration From IConfigurationâ](#loading-configuration-from-iconfiguration)

Loading configuration, including OpenID Connect configuration from `IConfiguration` is now supported:

```csharp
services.AddBff().LoadConfiguration(bffConfig);
```

This enables you to configure your OpenID Connect options, including secrets, and configure the list of frontends. This also adds a file watcher, to automatically add / remove frontends from the config file.

See the type `BffConfiguration` to see what settings can be configured.

## Handling SPA Static Assets

[Section titled âHandling SPA Static Assetsâ](#handling-spa-static-assets)

The BFF can be configured to handle the static file assets that are typically used when developing SPA based apps.

### Proxying Only `index.html`

[Section titled âProxying Only index.htmlâ](#proxying-only-indexhtml)

When deploying a multi-frontend BFF, it makes most sense to have the frontends configured with an `index.html` file that is retrieved from a Content Delivery Network (CDN).

This can be done in various ways. For example, if you use Vite, you can publish static assets with a base URL configured. This will make sure that any static asset, (such as images, scripts, etc.) are retrieved directly from the CDN for best performance.

```csharp
var frontend = new BffFrontend(BffFrontendName.Parse("frontend1"))
   .WithCdnIndexHtml(new Uri("https://my_cdn/some_app/index.html"))
```

The BFF automatically wires up a catch-all route that serves`index.html` for that specific frontend.

See [Serve the index page from the BFF host](/bff/architecture/ui-hosting/#serve-the-index-page-from-the-bff-host) for more information.

### Proxying All Static Assets

[Section titled âProxying All Static Assetsâ](#proxying-all-static-assets)

When developing a Single-Page Application (SPA), itâs very common to use a development webserver such as Vite. While Vite can publish static assets with a base URL, this doesnât work well during development.

The best development experience can be achieved by configuring the BFF to proxy all static assets from the development server:

```csharp
var frontend = new BffFrontend(BffFrontendName.Parse("frontend1"))
   .WithProxiedStaticAssets(new Uri("https://localhost:3000")); // https://localhost:3000 would be the URL of your development web server.
```

While this can also be done in production, it will proxy all static assets through the BFF. This will increase the bandwidth consumed by the BFF and reduce the overall performance of your application.

### Proxying Assets Based On Environment

[Section titled âProxying Assets Based On Environmentâ](#proxying-assets-based-on-environment)

If youâre using a local development server during development and a CDN in production, you can configure this as follows:

```csharp
// In this example, the environment name from the application builder is used to determine
// if we're running in production or not.
var runningInProduction = () => builder.Environment.EnvironmentName == Environments.Production;


// Then, when configuring the frontend, you can switch when the static assets will be proxied.
new BffFrontend(BffFrontendName.Parse("default-frontend"))
    .WithBffStaticAssets(new Uri("https://localhost:5010/static"), useCdnWhen: runningInProduction);
```

Note

This function is evaluated immediately when calling the`.WithBffStaticAssets()` extension method. When you call this method during startup, the condition is only evaluated at startup time. Itâs not evaluated at runtime for every request.

### Server Side Sessions Database Migrations

[Section titled âServer Side Sessions Database Migrationsâ](#server-side-sessions-database-migrations)

When using the server side sessions feature backed by the `Duende.BFF.EntityFramework` package, you will need to script [Entity Framework database migrations](/bff/fundamentals/session/server-side-sessions/#entity-framework-migrations) and apply these changes to your database.

```shell
dotnet ef migrations add BFFUserSessionsV4 -o Migrations -c SessionDbContext
```

In the `UserSessions` table, a number of changes were introduced:

* The `ApplicationName` column was renamed to `PartitionKey`. This column will contain the BFF frontend name.
* Related indexes were updated.

serversidesessions.sql

```sqlite
ALTER TABLE "UserSessions" RENAME COLUMN "ApplicationName" TO "PartitionKey";


DROP INDEX "IX_UserSessions_ApplicationName_SubjectId_SessionId";
CREATE UNIQUE INDEX "IX_UserSessions_PartitionKey_SubjectId_SessionId" ON "UserSessions" ("PartitionKey", "SubjectId", "SessionId");


DROP INDEX "IX_UserSessions_ApplicationName_SessionId";
CREATE UNIQUE INDEX "IX_UserSessions_PartitionKey_SessionId" ON "UserSessions" ("PartitionKey", "SessionId");


DROP INDEX "IX_UserSessions_ApplicationName_Key";
CREATE UNIQUE INDEX "IX_UserSessions_PartitionKey_Key" ON "UserSessions" ("PartitionKey", "Key");
```

Note

This is a breaking database schema change. If you have multiple BFF V3 applications that share the same database table, you either need to update all BFF applications to V4 at the same time or use a new database for the upgraded BFF V4 application.
-----