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
30 changes: 30 additions & 0 deletions BFF/v2/Angular/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# BFF Angular Sample

This sample shows an [Angular](https://angular.dev/) single-page application hosted by a BFF. The BFF is the only thing the browser talks to: it serves the Angular app, runs the OpenID Connect flow, and holds the tokens, so nothing sensitive ever lands in JavaScript.

There is also a separate JWT-protected ToDo API in the solution. It is not used in the default configuration — the BFF hosts the ToDo endpoints itself — but the sample ships it so you can switch to the reverse-proxy style with a one-line change.

Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the structure of our samples and how to run them.

## Sample Structure

- **`Angular.Bff`** — the BFF, running on `https://localhost:6001`. It serves the Angular app from the build output, maps the `/bff/*` management endpoints, and hosts the ToDo API in the same process.
- **`angular.client`** — the Angular SPA, an `.esproj` project so it can take part in the .NET solution and the VS launch profiles. Its dev server runs on `https://localhost:4200`.
- **`Angular.Api`** — a separate JWT-protected ToDo API on `https://localhost:7001`. Not called by default.

## How to Run

1. Run the `Angular.Bff` project. This starts the Angular dev server through the SPA proxy and the BFF together.
1. Browse to `https://localhost:6001` and use the login button.
1. Log in at the [Duende demo identity server](https://demo.duendesoftware.com) with username `bob` and password `bob` (or `alice` / `alice`).
1. Create and delete ToDo items, open **Show User Session** to see the claims the BFF returns, and then log out.

## What to Look For

- **The SPA proxy** — `Angular.Bff.csproj` sets `SpaRoot`, `SpaProxyLaunchCommand` (`npm start`) and `SpaProxyServerUrl` (`https://localhost:4200`). During development the browser actually talks to the Angular dev server, and the dev server forwards `/bff`, `/signin-oidc`, `/signout-callback-oidc` and `/todos` on to the BFF (see `proxy.conf.js`). That is what keeps everything same-origin from the browser's point of view.
- **The authentication service** — `src/app/authentication.service.ts` calls `bff/user` once and caches the result with `shareReplay(1)`. `getIsAuthenticated`, `getUsername` and `getLogoutUrl` are just projections over that one request, and a failed call is turned into an anonymous session rather than an error.
- **The CSRF header** — `src/app/csrf-header.interceptor.ts` is a class-based `HttpInterceptor` registered through `HTTP_INTERCEPTORS` (with `provideHttpClient(withInterceptorsFromDi())`). It adds `X-CSRF: 1` to every outgoing request, which is what lets the BFF reject cross-site request forgery on its API endpoints.
- **Login and logout are redirects** — the nav menu links to `/bff/login`, and the logout link is the `bff:logout_url` claim from `/bff/user`. The client never constructs a token request itself.
- **The local API** — `Program.cs` maps `/todos` with `MapGroup("/todos").ToDoGroup().RequireAuthorization().AsBffApiEndpoint()`. The `AsBffApiEndpoint()` part is what requires both a valid session and a valid CSRF header.
- **Switching to the remote API** — `Program.cs` also contains a commented-out `MapRemoteBffApiEndpoint("/todos", "https://localhost:7001/todos").RequireAccessToken(TokenType.User)`. Comment the local group out and this one in, and the same Angular code now talks to `Angular.Api` through the YARP proxy with the user's access token attached.
- **Client-side routing** — `app.MapFallbackToFile("/index.html")` makes the deep links (`/user-session`) work when the BFF serves the built app rather than the dev server.
35 changes: 35 additions & 0 deletions BFF/v2/BlazorWasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# BFF Blazor WebAssembly Sample

This sample shows a standalone Blazor WebAssembly application protected by a [Backend-for-Frontend (BFF)](https://docs.duendesoftware.com/identityserver/bff), so that no tokens ever reach the browser.

An ordinary ASP.NET Core application authenticates the user with OIDC and then serves the Blazor WebAssembly app with `UseBlazorFrameworkFiles()` and `MapFallbackToFile("index.html")`. The WASM app talks to the BFF's own endpoints and never sees an access token.

This version deliberately does the client-side plumbing **by hand**, which makes it useful for understanding what the official Blazor packages do for you in later versions.

Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the structure of our samples and how to run them.

## Sample Structure

- **`BlazorWasm.Server`** — the BFF host, running on `https://localhost:7189`. It handles the OIDC login, maps the `/bff/*` management endpoints, and serves the WebAssembly app itself.
- **`BlazorWasm.Client`** — the Blazor WebAssembly application.
- **`BlazorWasm.Shared`** — a small class library holding the `WeatherForecast` model that both projects use.

Because the host serves the WebAssembly app, **the only URL you need is `https://localhost:7189`**.

## How to Run

1. Run the `BlazorWasm.Server` project.
1. Browse to `https://localhost:7189` and use the login link in the navigation.
1. Log in at the [Duende demo identity server](https://demo.duendesoftware.com) with username `bob` and password `bob` (or `alice` / `alice`).
1. Visit the Fetch Data page, which calls a protected BFF endpoint, then sign out.

This sample does not run its own identity server, so you need internet access to reach the demo server.

## What to Look For

- **The custom authentication state provider** — `BlazorWasm.Client/BFF/BffAuthenticationStateProvider.cs` is an `AuthenticationStateProvider` that reads the session by calling `bff/user?slide=false`, deserialises the claim list, and builds a `ClaimsIdentity`. It caches the result for 60 seconds and runs a background timer to notice when the session disappears. This is roughly what `AddBffBlazorClient()` gives you in later versions.
- **The custom CSRF handler** — `BlazorWasm.Client/BFF/AntiforgeryHandler.cs` is an eleven-line `DelegatingHandler` that adds the `X-CSRF: 1` header to every outgoing request. Registering it with `AddHttpClient("backend", ...).AddHttpMessageHandler<AntiforgeryHandler>()` is how the client satisfies the CSRF requirement of a BFF API endpoint.
- **Server-side sessions** — the `__Host-blazor` cookie is `SameSite=Strict`, so the BFF does not need a server-side session store; the token is held in the encrypted cookie.
- **Protecting an endpoint** — `BlazorWasm.Server/Program.cs` maps its controllers with `MapControllers().RequireAuthorization().AsBffApiEndpoint()`. The `AsBffApiEndpoint()` call is what adds the CSRF header check on top of ordinary authorization.
- **Middleware order** — `app.UseBff()` sits between `UseAuthentication()` and `UseAuthorization()`.
- **The CSRF header itself** — this version relies on the header check performed by `UseBff()`; it does not additionally call ASP.NET Core's `UseAntiforgery()` middleware.
30 changes: 30 additions & 0 deletions BFF/v2/DPoP/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# BFF DPoP Sample

This sample shows a BFF that requests *DPoP-bound* access tokens (Demonstration of Proof of Possession, [RFC 9449](https://www.rfc-editor.org/rfc/rfc9449)) and calls a downstream API with them.

Instead of a bearer token that anyone can replay, DPoP binds the access token to a key that only the BFF holds. For every API call the BFF generates a short-lived *DPoP proof* JWT that covers the HTTP method and target URI, and the API checks that proof against the request it actually received. A stolen access token on its own is therefore useless without the corresponding private key.

The sample shows the four access-token flavours side by side — anonymous, user, client credentials, and user-or-client — both through the BFF's own endpoint helpers and through raw YARP routes.

Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the structure of our samples and how to run them.

## Sample Structure

- **`DPoP.Api`** — the downstream API, running on `https://localhost:6001`. It validates the access token *and* the DPoP proof on every request, and echoes back the path, message and headers. It ships a self-contained DPoP validation implementation under `DPoP.Api/DPoP/`, so you can see exactly what a resource server has to do.
- **`DPoP.Bff`** — the BFF, running on `https://localhost:5002`. It hosts a small static UI and proxies the API calls on the browser's behalf.

## How to Run

1. Run the `DPoP.Bff` and `DPoP.Api` projects.
1. Browse to `https://localhost:5002` and log in.
1. Log in at the [Duende demo identity server](https://demo.duendesoftware.com) with username `bob` and password `bob` (or `alice` / `alice`).
1. Try the different endpoints and watch the requests and responses in the browser's network tab.

## What to Look For

- **Enabling DPoP in the BFF** — `Program.cs` in `DPoP.Bff` creates a 2048-bit RSA key, converts it to a JWK, sets `Alg` to `PS256`, and assigns the serialized JWK to `options.DPoPJsonWebKey` inside `AddBff(options => { ... })`. Setting this key is all it takes to switch the BFF over to DPoP.
- **Token types** — the BFF exposes `/api/anonymous`, `/api/user-token`, `/api/client-token` and `/api/user-or-client-token`, each guarded by the matching token requirement, plus `/api/optional-user-token` for the "send a token if we have one" case.
- **YARP routes** — `/yarp/anonymous`, `/yarp/user-token`, `/yarp/client-token` and `/yarp/user-or-client-token` show the same calls driven purely by the YARP configuration in `YarpConfigurator.cs`.
- **Proof validation on the API** — `DPoP.Api/DPoP/DPoPProofValidator.cs` validates the proof's `htm`, `htu` and `ath` claims against the incoming request and access token, and enforces a one-second proof validity window.
- **DPoP nonces** — the API supports server-issued nonces. `DPoPOptions.ValidateNonce` defaults to `false`; when enabled the API responds with a `DPoP-Nonce` header and rejects the request until the client retries with that nonce.
- **No forwarded headers on the API** — the API deliberately does *not* use forwarded-headers middleware, because it would change the `htu` that the DPoP proof is validated against.
27 changes: 27 additions & 0 deletions BFF/v2/JsBffSample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# BFF JavaScript Sample

This sample shows the most basic shape of a BFF: an ASP.NET Core application that serves a plain JavaScript UI and handles the authentication and token handling for the browser, so no tokens ever reach JavaScript.

The UI is served from `wwwroot` and talks to same-origin endpoints only. Log in and out happen through the BFF's management endpoints, and the session is read back from `/bff/user`.

Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the structure of our samples and how to run them.

## Sample Structure

- **`FrontendHost`** — the BFF, running on `https://localhost:5010`. It serves the static UI (`index.html`, `todo.js`, `session.js`), maps the `/bff/*` management endpoints, and hosts the ToDo API in the same process.
- **`BackendApiHost`** — a separate JWT-protected ToDo API, running on `https://localhost:5020`. It is not used in the default configuration, but it is there so you can compare the two styles.

## How to Run

1. Run the `FrontendHost` and `BackendApiHost` projects.
1. Browse to `https://localhost:5010` and use the login button.
1. Log in at the [Duende demo identity server](https://demo.duendesoftware.com) with username `bob` and password `bob` (or `alice` / `alice`).
1. Create, update and delete ToDo items, then reload the page to see the session restored from the cookie.

## What to Look For

- **Login and logout** — the UI simply links to `/bff/login?returnUrl=/` and `/bff/logout?returnUrl=/`. All of the OAuth/OIDC flow happens in the BFF.
- **Reading the session** — `wwwroot/session.js` fetches `/bff/user` and renders the user's name and claims. JavaScript never sees an access token; it only learns *who* is logged in.
- **The CSRF header** — every call from the UI sends an `x-csrf: 1` header. This is what lets the BFF reject cross-site request forgery on its API endpoints.
- **The local API** — `FrontendHost/ToDoController.cs` is an ordinary MVC controller marked with `.RequireAuthorization().AsBffApiEndpoint()`. That marks it as a BFF endpoint, which requires both a valid session and a valid CSRF header.
- **Switching to a remote API** — `Program.cs` contains a commented-out `MapRemoteBffApiEndpoint("/todos", "https://localhost:5020/todos")` showing the alternative: keep the same UI and let the BFF proxy the calls to a real downstream API instead of handling them locally.
28 changes: 28 additions & 0 deletions BFF/v2/JsBffYarpSample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# BFF JavaScript Sample with YARP

This sample is the reverse-proxy flavour of the [JavaScript BFF sample](../v4/JsBffSample). The UI is identical, but instead of handling the ToDo calls itself, the BFF runs a [YARP](https://github.com/dotnet/yarp) reverse proxy that forwards `/todos/**` to a real downstream API, attaching the user's access token on the way out.

This is the pattern to reach for when the API already exists and you do not want to hand-write a controller per endpoint — the BFF becomes a secure gateway instead of a second implementation of your API.

Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the structure of our samples and how to run them.

## Sample Structure

- **`FrontendHost`** — the BFF, running on `https://localhost:5010`. It serves the static UI (`index.html`, `todo.js`, `session.js`), maps the `/bff/*` management endpoints, and hosts the reverse proxy.
- **`BackendApiHost`** — the downstream JWT-protected ToDo API, running on `https://localhost:5020`. Unlike in the non-YARP sample, this one really is called.

## How to Run

1. Run the `FrontendHost` and `BackendApiHost` projects.
1. Browse to `https://localhost:5010` and use the login button.
1. Log in at the [Duende demo identity server](https://demo.duendesoftware.com) with username `bob` and password `bob` (or `alice` / `alice`).
1. Create and delete ToDo items, and watch the proxied calls in the browser's network tab.

## What to Look For

- **The proxy pipeline** — `Program.cs` calls `AddBff()`, then `AddReverseProxy().AddTransforms<AccessTokenTransformProvider>()`, then the sample's own `YarpConfigurator.Configure()`. The provider is the piece that attaches the user's access token to each outgoing request.
- **Routes in code** — `YarpConfigurator.cs` builds the route and cluster configuration in C# with `LoadFromMemory`. The route matches `/todos/{**catch-all}`, points at the `cluster1` cluster, and declares the token requirement with `.WithAccessToken(TokenType.User)`. The cluster's single destination is `https://localhost:5020`.
- **Loading config from memory** — `InMemoryConfigProvider.cs` implements `IProxyConfigProvider` and `IProxyConfig` so the routes can be handed to YARP as an in-memory object rather than a `appsettings.json` section. That is what makes the route able to carry BFF-specific configuration such as the access token requirement.
- **Mapping the proxy** — `app.MapBffReverseProxy()` is the shorthand for `MapReverseProxy().AsBffApiEndpoint()`. The `AsBffApiEndpoint()` part is what enforces the session check and the CSRF header.
- **Going back to a local API** — the sample keeps `ToDoController.cs` and the matching `MapControllers().AsBffApiEndpoint()` call, commented out, so you can switch between the two styles and compare.
- **The CSRF header** — the UI sends `x-csrf: 1` on its requests, which the BFF requires before it will forward anything.
43 changes: 43 additions & 0 deletions BFF/v2/React/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# BFF React Sample

This sample shows a [React](https://react.dev/) single-page application hosted by a BFF project. The SPA is developed with [Vite](https://vite.dev/), but in production it is served as static files from the BFF itself, so everything ends up same-origin and no tokens ever reach JavaScript.

The BFF runs the authorization code flow against the [Duende demo identity server](https://demo.duendesoftware.com), keeps the tokens server-side, and exposes the ToDo API to the browser through its own BFF endpoints. A second, standalone JWT-protected API is included so you can compare hosting the API in the BFF process against proxying to a real downstream service.

Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the structure of our samples and how to run them.

## Sample Structure

- **`React.Bff`** — the BFF, running on `https://localhost:6001`. It serves the SPA, maps the `/bff/*` management endpoints, and hosts the ToDo API in the same process.
- **`React.Api`** — a separate JWT-protected ToDo API, running on `https://localhost:7001`. It is not used in the default configuration, but it is there so you can compare the two styles.
- **`react.client`** — the Vite dev server, running on `https://localhost:5173`. It is a build-time dependency of `React.Bff` and is only used during development.

## How to Run

1. Run the `React.Bff` and `React.Api` projects.
1. Browse to `https://localhost:6001` and use the login button.
1. Log in at the [Duende demo identity server](https://demo.duendesoftware.com) with username `bob` and password `bob` (or `alice` / `alice`).
1. Create, update and delete ToDo items, then reload the page to see the session restored from the cookie.

Running `React.Bff` starts the Vite dev server through the ASP.NET Core SPA proxy, so you get hot module replacement while developing. The Vite server proxies `/bff/*`, `/todos`, `/signin-oidc` and `/signout-callback-oidc` back to the BFF, which is why the app can use same-origin relative URLs in development and in production alike.

## What to Look For

- **Login and logout** — the UI links to `/bff/login` and `/bff/logout`. All of the OAuth/OIDC flow happens in the BFF.
- **Reading the session** — `react.client/src/components/UserSession.jsx` fetches `bff/user` and renders the user's claims. JavaScript never sees an access token; it only learns *who* is logged in.
- **The CSRF header** — every call from the UI sends an `X-CSRF: 1` header. This is what lets the BFF reject cross-site request forgery on its API endpoints.
- **The local API** — `React.Bff/ToDoEndpointGroup.cs` holds a small in-memory ToDo store, and `Program.cs` maps it with `.RequireAuthorization().AsBffApiEndpoint()`. That marks it as a BFF endpoint, which requires both a valid session and a valid CSRF header.
- **Switching to a remote API** — `Program.cs` contains a commented-out `MapRemoteBffApiEndpoint("/todos", "https://localhost:7001/todos").RequireAccessToken(Duende.Bff.TokenType.User)` showing the alternative: keep the same UI and let the BFF proxy the calls to a real downstream API instead of handling them locally. The UI does not change at all.
- **Validating tokens in the API** — `React.Api` is a plain JWT bearer resource server. It checks the `api` audience and guards the ToDo endpoints with two policies: `ApiCaller` (the token carries the `api` scope) and `InteractiveUser` (the token carries a `sub`, so a real user is behind it).
- **Client-side routing** — `app.MapFallbackToFile("/index.html")` makes the BFF return the SPA for unknown paths, so a deep link such as `/users` still boots the React app.
- **Explicit authentication schemes** — in BFF 2.x the cookie and OIDC handlers are registered by hand and the default schemes are named `cookie` / `oidc`:
```csharp
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
}).AddCookie("cookie", options => { /* ... */ })
.AddOpenIdConnect("oidc", options => { /* ... */ });
```
BFF 4.x folds this into the BFF builder via `.ConfigureOpenIdConnect(...)` and `.ConfigureCookies(...)`. See the [BFF v4 sample](../v4/React) for that style.
Loading
Loading