Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/content/docs/bff/fundamentals/multi-frontend/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,24 @@ The configuration supports dynamic reloading (so any new frontend added / remove

### BffFrontendConfiguration JSON Properties

- `indexHtmlUrl`
The URL to the main HTML file for this frontend.
Example: `"https://localhost:5005/static/index.html"`
- `cdnIndexHtmlUrl`
The `index.html` that should be used for this frontend (usually on a CDN). When using
this property, a fallback route will be created that only proxies the `index.html`. Other
static assets are supposed to be retrieved directly from the CDN by the browser.
Example: `"https://cdn.yourapp.com/some_app/index.html"`

- `staticAssetsUrl`
The URL where all static assets can be found. This registers a fallback route that will
proxy all static assets from this URL. This is usually used during development, when you're
using a development web server such as Vite.
Example: `"https://localhost:3000/"`


- `matchingPath`
The path prefix for requests routed to this frontend.
Example: `"/from-config"`

- `matchingOrigin`
- `matchingHostHeader`
The origin to match for this frontend.
Example: `"https://localhost:5005"`

Expand All @@ -63,7 +72,7 @@ The configuration supports dynamic reloading (so any new frontend added / remove

### RemoteApiConfiguration JSON Properties

- `localPath`
- `matchingPath`
String. The local path that will be used to access the remote API.
Example: `"/api/user-token"`

Expand Down Expand Up @@ -181,7 +190,7 @@ The configuration supports dynamic reloading (so any new frontend added / remove
"defaultCookieSettings": null,
"frontends": {
"some_frontend": {
"indexHtmlUrl": "https://localhost:5005/static/index.html",
"cdnIndexHtmlUrl": "https://localhost:5005/static/index.html",
"matchingPath": "/from-config",
"oidc": {
"clientId": "frontend1-client",
Expand Down
55 changes: 50 additions & 5 deletions src/content/docs/bff/fundamentals/multi-frontend/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ bffBuilder

// This frontend uses most of the same authentication options,
new BffFrontend(BffFrontendName.Parse("with-path"))
.MappedToPath(LocalPath.Parse("/with-path")),
.MapToPath("/with-path"),
.WithOpenIdConnectOptions(opt =>
{
// but overrides the clientid and client secret.
Expand Down Expand Up @@ -112,6 +112,12 @@ and removed from the [`Path`](https://learn.microsoft.com/en-us/dotnet/api/micro
that happens in the application is relative to the path of the frontend. This also includes the OpenID callback paths.
:::

### Implicit Frontend Disabled

When you don't add any frontends, BFF creates an implicit default frontend. This allows BFF to function correctly in single frontend mode. As soon as you add a frontend, this implicit frontend is disabled.

If you want to use both explicitly matching frontends (on host headers or paths) and a default (fallback) frontend, you should explicitly add this default frontend.

## Adding A Frontend During Startup

The simplest way to add frontends is during startup.
Expand Down Expand Up @@ -143,26 +149,65 @@ A frontend can define its own API surface, by specifying remote APIs.
var frontend = new BffFrontend(BffFrontendName.Parse("frontend1"))
.WithRemoteApis(
// map the local path /path to the remote api
new RemoteApi(LocalPath.Parse("/some_path"), new Uri("https://remote-api")))
new RemoteApi("/some_path", new Uri("https://remote-api")))

// You can also configure various options, such as the type of token,
// retrieval parameters, etc..
new RemoteApi(LocalPath.Parse("/with_options"), new Uri("https://remote-api")))
new RemoteApi("/with_options", new Uri("https://remote-api")))
.WithAccessToken(RequiredTokenType.UserOrClient),
.WithAccessTokenRetriever<ImpersonationAccessTokenRetriever>(),
.WithUserAccessTokenParameters(new BffUserAccessTokenParameters { Resource = Resource.Parse("urn:isolated-api") }));
```

See the topic on [Token Management](/bff/fundamentals/tokens.md) for more information about the various token management options.

## Index HTML Proxying
## Handling SPA Static Assets

BFF can be configured to handle the static file assets that are typically used when developing Single-Page Application (SPA)-based app.

### Proxying Only `index.html`

When deploying a multi-frontend BFF, it makes most sense to have the frontend's 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"))
.WithIndexHtml(new Uri("https://my_cdn/some_app/index.html"))
.WithCdnIndexHtml(new Uri("https://my_cdn/some_app/index.html"))
```

When you do this, the BFF automatically wires up a catch-all route that serves the `index.html` for that specific frontend.
See [Serve the index page from the BFF host](/bff/architecture/ui-hosting.md#serve-the-index-page-from-the-bff-host) for more information.

### 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature would make a good community question. "Would people use ProxiedStaticAssets in development AND production?"

```

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

### Proxying Assets Based On Environment

If you're using a local development server during development and a CDN in production, you can configure asset proxying 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 method `.WithBffStaticAssets()`. If you call this method during startup, the condition is only evaluated at startup time. It's not evaluated at runtime for every request.
:::
4 changes: 2 additions & 2 deletions src/content/docs/bff/fundamentals/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ builder.Services.AddBff(options =>
When applying BFF V4 multiple frontends, a lot of middlewares get automatically added to the pipeline. For example, the frontend selection middleware, the authentication handlers, etc. If you don't want this automatic behavior, then you can turn it off and register these middlewares manually.


* ***IndexHtmlClientName***
If BFF is configured to automatically retrieve the index.html, then it needs a http client to do so. With this name you can automatically configure http client in the http client factory.
* ***StaticAssetsClientName***
If BFF is configured to automatically retrieve the `index.html`, or to proxy the static assets, it needs an HTTP client to do so. With this name, you can automatically configure this HTTP client in the `HttpClientFactory`.

* ***AllowedSilentLoginReferers***
For silent login to work, you normally need to have the BFF backend and the frontend on the same origin. If you have a split host scenario, meaning the backend on a different origin (but same site) as the frontend, then you can use the referer header to differentiate which browser window to post the silent login results to. This array must then contain the list of allowed referer header values.
Expand Down
12 changes: 6 additions & 6 deletions src/content/docs/bff/getting-started/multi-frontend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ app.Run();
code={`builder.Services.AddBff()
.AddFrontends(
new BffFrontend(BffFrontendName.Parse("default-frontend"))
.WithIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")),
.WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")),
new BffFrontend(BffFrontendName.Parse("admin-frontend"))
.WithIndexHtmlUrl(new Uri("https://localhost:5005/admin/index.html"))
.WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/admin/index.html"))
);

// ...existing code for authentication, authorization, etc.`}/>
Expand All @@ -124,14 +124,14 @@ app.Run();
"defaultCookieSettings": null,
"frontends": {
"from_config": {
"indexHtmlUrl": "https://localhost:5005/static/index.html",
"cdnIndexHtmlUrl": "https://localhost:5005/static/index.html",
"matchingPath": "/from-config",
"oidc": {
"clientId": "bff.multi-frontend.config"
},
"remoteApis": [
{
"localPath": "/api/client-token",
"matchingPath": "/api/client-token",
"targetUri": "https://localhost:5010",
"tokenRequirement": "Client"
}
Expand Down Expand Up @@ -173,9 +173,9 @@ You can configure remote API proxying in two ways:
builder.Services.AddBff()
.AddFrontends(
new BffFrontend(BffFrontendName.Parse("default-frontend"))
.WithIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html"))
.WithCdnIndexUrl(new Uri("https://localhost:5005/static/index.html"))
.WithRemoteApis(
new RemoteApi(LocalPath.Parse("/api/user-token"), new Uri("https://localhost:5010"))
new RemoteApi("/api/user-token", new Uri("https://localhost:5010"))
)
);
```
Expand Down
4 changes: 4 additions & 0 deletions src/content/docs/bff/getting-started/single-frontend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Duende.BFF (Backend for Frontend) is a library that helps you build secure, mode

:::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](/bff/upgrading/bff-v3-to-v4.md).

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](/bff/fundamentals/multi-frontend/) for more information on this topic.
:::

## Prerequisites
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/bff/upgrading/bff-v2-to-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ a custom `IAccessTokenRetriever`, then you should adjust their usage accordingly
/// <summary>
/// The locally requested path.
/// </summary>
public required PathString LocalPath { get; set; }
public required PathString PathMatch { get; set; }

/// <summary>
/// The remote address of the API.
Expand Down
67 changes: 55 additions & 12 deletions src/content/docs/bff/upgrading/bff-v3-to-v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,28 @@ You can statically add a list of frontends by calling the `AddFrontends` method.
```csharp
.AddFrontends(
new BffFrontend(BffFrontendName.Parse("default-frontend"))
.WithIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")),
.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";
})
.WithIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html"))
.MappedToPath(LocalPath.Parse("/with-path")),
.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";
})
.WithIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html"))
.MappedToOrigin(Origin.Parse("https://app1.localhost:5005"))
.WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html"))
.MapToHost(HostHeaderValue.Parse("https://app1.localhost:5005"))
.WithRemoteApis(
new RemoteApi(LocalPath.Parse("/api/user-token"), new Uri("https://localhost:5010")),
new RemoteApi(LocalPath.Parse("/api/client-token"), new Uri("https://localhost:5010"))
new RemoteApi("/api/user-token", new Uri("https://localhost:5010")),
new RemoteApi("/api/client-token", new Uri("https://localhost:5010"))
)
```

Expand All @@ -118,18 +118,61 @@ This enables you to configure your OpenID Connect options, including secrets, an

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

#### Index HTML Retrieval
## Handling SPA Static Assets

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.
The BFF can be configured to handle the static file assets that are typically used when developing SPA based apps.

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.
### Proxying Only `index.html`

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.md#serve-the-index-page-from-the-bff-host) for more information.

### 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:

You can configure the location of your `index.html` by specifying:

```csharp
.WithIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html"))
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

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

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.mdx#entity-framework-migrations) and apply these changes to your database.
Expand Down
Loading