Loading...
+} +else +{ + @foreach (var item in items) + { +@item.Value
+ } +} + +@code { + private Data[]? items; + + protected override async Task OnInitializedAsync() + { + items = await DataAccessor.GetData(); + } +} +``` + +## See Also + +You (@context.User.Identity?.Name) are not authorized to access this resource.
+ } +You (@context.User.Identity?.Name) are not authorized to access this resource.
- } -This component demonstrates showing data.
- -@if (forecasts == null) -{ -Loading...
-} -else -{ -| Date | -Temp. (C) | -Temp. (F) | -Summary | -
|---|---|---|---|
| @forecast.Date.ToShortDateString() | -@forecast.TemperatureC | -@forecast.TemperatureF | -@forecast.Summary | -
This component demonstrates showing data.
+ + @if (forecasts == null) + { +Loading...
+ } + else + { +| Date | +Temp. (C) | +Temp. (F) | +Summary | +
|---|---|---|---|
| @forecast.Date.ToShortDateString() | +@forecast.TemperatureC | +@forecast.TemperatureF | +@forecast.Summary | +
-
-
-
- Load and use the configuration in `Program.cs`:
-
-
-
-
+
+
+
+ Load and use the configuration in `Program.cs`:
+
+
+
+
+
-
-
+ :::tip
+ Always call `.AsBffApiEndpoint()` on your local API routes. Without it, the `X-CSRF` header is not enforced and your endpoints are vulnerable to CSRF attacks. See [Local APIs](/bff/fundamentals/apis/local/) for details.
+ :::
- "hello-world")
+ .AsBffApiEndpoint(); // Adds CSRF protection to the controller endpoints`}/>
+
+
-The BFF extends the capabilities of [Yarp](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/getting-started?view=aspnetcore-9.0) in order to achieve this.
+
-```bash title="Terminal"
-dotnet add package Duende.BFF.Yarp
-```
+
-
- (StringComparer.OrdinalIgnoreCase)
- {
- { "destination_1", new DestinationConfig { Address = "https://remote-api-address" } }
- }
- });
+5. **Adding Remote APIs**
+
+ 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](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/getting-started?view=aspnetcore-9.0) in order to achieve this.
-// ...
+ :::tip
+ For a comparison of local vs. remote vs. YARP-based APIs, see the [API Overview](/bff/fundamentals/apis/).
+ :::
-app.UseAuthorization();
+ ```bash title="Terminal"
+ dotnet add package Duende.BFF.Yarp
+ ```
-// Add the Yarp middleware that will proxy the requests.
-app.MapReverseProxy(proxyApp => {
- proxyApp.UseAntiforgeryCheck();
-});`}/>
+ {/* prettier-ignore */}
+
+ {/* prettier-ignore */}
+
- You can also use an `IConfiguration` instead of programmatically configuring the proxy.
+
-
+ // ...
-### 6. Adding Server-Side Sessions
+ // 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 */}
-
- {/* 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.
+ (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.
+
+
+
+
+6. **Adding Server-Side Sessions**
+
+ {/* 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.
+
+
+
+
+ {/* prettier-ignore */}
+
-
+ For production scenarios, you can use Entity Framework to persist sessions in a database. First, add the NuGet package:
+
+
+
+ Then configure the session store in your `Program.cs`:
+
+
+ {
+ 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.
+
+
+
+
+
+### Check the current user session
- Then configure the session store in your `Program.cs`:
+On load, call `/bff/user` to check whether the user is logged in. This endpoint returns the user's claims as JSON when authenticated, or a `401`/empty response when anonymous.
-
- {
- options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
+```javascript
+// Fetch the current user from the BFF session
+async function getUser() {
+ const response = await fetch('/bff/user', {
+ headers: { 'X-CSRF': '1' }
});
-
-// ...existing code for authentication, authorization, etc.`}/>
- You will also need to run the Entity Framework migrations to create the necessary tables.
+ if (response.ok) {
+ return await response.json(); // Array of { type, value } claim objects
+ }
+
+ return null; // Not authenticated
+}
+```
+
+### Login and logout links
+
+Use plain anchor tags pointing to the BFF management endpoints. Do **not** use `fetch` for these — they must trigger a full browser redirect.
+
+```html
+
+Log in
+
+
+
+Log out
+```
+
+```javascript
+// Wire up logout link with the session-bound URL from /bff/user
+const user = await getUser();
+if (user) {
+ const logoutUrlClaim = user.find(c => c.type === 'bff:logout_url');
+ document.getElementById('logout-link').href = logoutUrlClaim?.value ?? '/bff/logout';
+}
+```
+
+### Calling BFF API endpoints
+
+Every request to a BFF API endpoint **must** include the `X-CSRF: 1` header. Without it, the BFF will reject the request with `401 Unauthorized`.
+
+```javascript
+// Centralized fetch wrapper — always add the X-CSRF header
+async function bffFetch(url, options = {}) {
+ const response = await fetch(url, {
+ ...options,
+ headers: {
+ 'X-CSRF': '1',
+ ...options.headers,
+ },
+ });
+
+ // Redirect to login if the session has expired
+ if (response.status === 401) {
+ window.location.href = `/bff/login?returnUrl=${encodeURIComponent(window.location.pathname)}`;
+ return;
+ }
+
+ return response;
+}
+
+// Example usage
+const data = await bffFetch('/api/weather');
+const json = await data.json();
+```
+
+:::tip
+Use `bffFetch` (or an equivalent interceptor in your framework) consistently throughout your frontend. This ensures every authenticated request includes the CSRF header and gracefully handles session expiry.
+:::
+
+### Proactive session polling (optional)
+
+To detect server-initiated session termination (e.g., back-channel logout), poll `/bff/user` periodically:
+
+```javascript
+// Poll every 60 seconds; redirect to login if session ends
+setInterval(async () => {
+ const user = await getUser();
+ if (!user) {
+ window.location.href = '/bff/login';
+ }
+}, 60_000);
+```
-