github DuendeSoftware/products bff-4.0.0-rc.2
Duende.Bff V4 - RC.2

pre-release7 hours ago

This release is a major change from the previous release. The biggest improvement here is multi-frontend support.

The extensibility approach has been drastically changed. To reduce the public API surface, and improve our ability to evolve the library, we no longer make every class in the system public with virtual methods. Implementation logic now is internal.

Changes and improvements

  • Support for login prompts, such as prompt = create => #1701
  • Deprecated silent login endpoint in favor of prompt=none
  • Allow anti forgery check to be disabled via a delegate => #1932
  • Multi-frontend support => #2032
  • Duende.AccessTokenManagement V4 => #2032
  • Open Telemetry support => #1945
  • X-forward header support => #2129
  • Make sure compatible with non-standard compliant openid connect providers => #2132
  • Added License enforcement warnings => #2130

Changes since RC1:

  • BFF now references Duende.AccessTokenManagement 4.0.0
  • Added support for management basepaths with templates: #2185
  • Allow management endpoints to be overwritten manually: #2186
  • Adjusted loglevel for several log messages : #2192
  • Fix issue that openid connect handlers are not registered correctly if only relying on config: #2194
  • remove LocalPath and rename Origin to HostHeaderValue #2208
  • Adding development time static files proxying #2207
  • Disable default frontend when using multiple frontends #2202

Breaking changes since RC2:

Based on feedback from the community, we have made a couple of functional improvements and renamed certain properties to clarify the API.

Renamed properties

The following concepts have been renamed:

  • Path mapping used to use a strongly typed object called LocalPath. It was used to map properties from a path local to the BFF to a remote path or a url. We received feedback that this was confusing. Since .Net already contains a property that could help PathString we decided to remove LocalPath and rely on PathString only.
  • Origin has been renamed to HostHeaderValue. Technically, we're binding frontends to an origin, which is a data structure that contains the scheme, host and port number). However, we're not binding the frontend to the Origin Header*, but to the Host Header. This caused quite a bit of confusion as origins are also used in Cross Origin Resource Sharing.
  • Renamed BffFrontend.MappedToPath() to BffFrontend.MapToPath
  • Renamed BffFrontend.MappedToOrigin() to BffFrontend.MapToHost()
  • Renamed RemoteApi.LocalPath to RemoteApi.MatchingPath
  • Renamed BffFrontend.SelectionCriteria to BffFrontend.MatchingCriteria
  • Renamed BffFrontendConfiguration.MatchingOrigin to BffFrontendConfiguration.MatchingHostHeader

The methods BffFrontend.MapToHost and BffFrontend.MapToPath are now mutually exclusive. Setting both will throw. If you want to match a frontend based on both a Host and a Path, you should use BffFrontend.MapTo()

  • Renamed BffFrontend.WithIndexHtml to BffFrontend.WithCdnIndexUrl (to make it's purpose clearer).
  • Renamed IIndexHtmlClient to IStaticFilesHttpClient
  • Renamed IndexHtmlClientName to StaticAssetsClientName

Development time Static files proxying

We introduced additional functionality for proxying static files. Initially, we only support proxying an index.html file from a CDN. This is still the recommended way to deploy a frontend. There was no support for local development however.

We now also allow you to use a frontend development server, such as Vite.

The method BffFrontend.WithProxiedStaticAssets() allows you to proxy all static assets from a development web server. This provides a very nice development workflow (which also works with hot reloading).

If you want to use WithProxiedStaticAssets during development, but WithCdnIndexUrl in production (recommended), then there is also another method: BffFrontend.WithBffStaticAssets() This provides a delegate (useCdnWhen) that allows you to control under which conditions you'd like to use the proxying. IE:

new BffFrontend(BffFrontendName.Parse("default-frontend"))
            .WithBffStaticAssets(new Uri("https://localhost:5010/static"), 
                 useCdnWhen: () => builder.Environment.EnvironmentName == Environments.Production);

Disable implicit frontend when using multiple frontends

If you don't use the multi-frontend feature, then the BFF uses an implicit frontend. So by default, it automatically adds the management endpoints and allows you to log in.

In Rc1, this 'implicit' frontend was also available if you did add multiple frontends. This caused confusion because it wasn't always 100% clear which frontend was selected.

So, in RC2, the implicit frontend is disabled as soon as there is a single frontend added to the IFrontendCollection.

Changes since preview 2:

  • BffBuilder type (returned from services.AddBff()) is now an interface called IBffServicesBuilder. This is used as the basis for extension methods.

  • IBffServicesBuilder.WithDefaultCookieOptions is now called IBffServicesBuilder.ConfigureCookies()

  • IBffServicesBuilder.WithDefaultOpenIdConnectOptions is now called IBffServicesBuilder.ConfigureOpenIdConnect()

  • To support split host login scenarios', you can use a referer header in the Silent Login, coupled with BffOptions.AllowedSilentLoginReferers to create a list of allowed referers.

  • IFrontendCollection no longer exposes a GetAll method, but now implements IEnumerable.

  • ReturnUrlValidator.IsValidAsync now only accepts a uri

  • User sessions are now partitioned per frontend. IUserSessionStore has been updated to reflect this change. Note, you will have to run a migration to rename the column "ApplicationName" to "PartitionKey", including corresponding interfaces. Note, the migrations project has been updated to reflect this change.

  • When you add EntityFramework based sessions to your system, it no longer automatically enables session cleanup. The property BffOptions.EnableSessionCleanup is removed. You now have to call AddSessionCleanupBackgroundProcess() to enable session migration.

Changes since preview 1:

  • Removed custom signin url => #2056
  • Fixed server side sessions & sliding cookie configuration => #2041
  • Fixed logout => #2060

Upgrade guide

This release introduces many breaking changes. Some small, some large. We'll do our best to document how to upgrade here. Should you run into an upgrade issue that's not documented, please reach out to our discussion forum and we'll do our best to help.

Remote APIs

The syntax for configuring remote APIs has changed slightly:

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

// Use the client token only if the user is logged in
app.MapRemoteBffApiEndpoint("/api/optional-user-token", "https://localhost:5010")
-    .WithOptionalUserAccessToken();
+    .WithAccessToken(RequiredTokenType.UserOrNone);            
  • The enum TokenType has been renamed to RequiredTokenType.
  • The method to require the token type is renamed to WithAccessToken()
  • Requesting an optional access token should not be done with the method WithOptionalUserAccessToken() but with RequiredTokenType.UserOrNone

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

Simplified Wireup Without Explicit Authentication Setup

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

services.AddBff()
    .WithDefaultOpenIdConnectOptions(options =>
    {
        options.Authority = "your authority";
        options.ClientId = "your client id";
        options.ClientSecret = "secret";
        // ... other OpenID Connect options. 
    }
    .WithDefaultCookieOptions(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

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

.AddFrontends(
    new BffFrontend(BffFrontendName.Parse("default-frontend"))
        .WithCdnIndexUrl(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";
        })
        .WithCdnIndexUrl(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";
        })
        .WithCdnIndexUrl(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

Loading configuration, including openid connect configuration from an IConfiguration is now supported:

services.AddBff().LoadConfiguration(bffConfig);

Using this, you can 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.

Index HTML Retrieval

It's fairly common to deploy your application in such a way to have the BFF be the first entrypoint for your application. It should serve an index.html that will bootstrap your frontend. However, your static content should be loaded from a CDN.

If you publish your frontend code to a cdn with absolute paths (for example by specifying a base path in your vite config), then all static content is loaded directly from the CDN.

You can configure the location of your Index HTML by specifying:

.WithCdnIndexUrl(new Uri("https://localhost:5005/static/index.html"))

The method BffFrontend.WithProxiedStaticAssets() allows you to proxy all static assets from a vite development web server. This provides a very nice development workflow (which also works with hot reloading). Note, this is not recommended to use in production as it will proxy all assets through the BFF.

If you want to use WithProxiedStaticAssets during development, but WithCdnIndexUrl in production (recommended), then there is also another method: BffFrontend.WithBffStaticAssets() This provides a delegate (useCdnWhen) that allows you to control under which conditions you'd like to use the proxying. IE:

new BffFrontend(BffFrontendName.Parse("default-frontend"))
            .WithBffStaticAssets(new Uri("https://localhost:5010/static"), 
                 useCdnWhen: () => builder.Environment.EnvironmentName == Environments.Production);

Running migrations to usersession db.

A partition key has been added to the usersession db, to support the multi-frontend feature. If you're using the entity framework UserSessionDb provider, you'll need to run the migrations.

cd .\bff\migrations\UserSessionDb
dotnet tool install --global dotnet-ef
dotnet ef database update

Alternatively, here's the script to update the migrations for SqlLocalDB. It may have to be slightly adjusted to match your database of choice.

BEGIN TRANSACTION;

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");

INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20250701130814_PartitionKey', '8.0.11');

COMMIT;

Don't miss a new products release

NewReleases is sending notifications on new releases.