← Default view
Itenium.Forge · Component Catalogue · Section 4 — Auth Subsystems · Edition Jul 2026

BFF Options
for .NET 10

Five ways to put an OIDC session in front of an API. Priced, licensed, weighed by the lines of security-critical code you end up owning.
Sheet1 / 1
DepthExpedition
Refs50
Platformnet10.0
Rev.2026-07-14
00

Selection guide

Two questions, one part number
Q1 · Revenue test
Under $1M gross revenue
and under $3M capital?

The Community Edition gate is org revenue, not app audience. "It's only an internal tool" does not qualify you [2].

If yes
Duende.BFF · $0

Complete, supported, reference implementation. The answer is trivially Duende — take the free Community Edition licence and stop reading [11].

If no · and $5,750/yr is a blocker
YARP + OIDC + ATM

Microsoft's own recipe. Free forever, ~250–450 lines of security-critical code you own forever [5].

⚠ NEVER HAND-ROLL TOKEN REFRESH — refresh races, rotation reuse-detection and multi-instance token stores are where custom BFFs break [8]
01

Assembly diagram

Where the part fits · Fig. 1
SERVER SIDE — TRUST BOUNDARY CLIENT React 19 SPA cookie: __Host- httpOnly tokens: none ✗ PART UNDER TEST THE BFF · cookie ⇄ OIDC handler · token store + refresh · X-CSRF + /bff/user · forwarder (always YARP) DOWNSTREAM .NET 10 API AddJwtBearer — unchanged IDENTITY PROVIDER Keycloak / OpenIddict any OIDC provider cookie Bearer <access_token> code exchange · refresh ↑ strip the Cookie header here THE CHOICE Not Duende vs YARP. Duende.BFF's forwarder IS YARP. You are choosing whether to buy the auth layer that sits on top. Ref. [24]
Fig. 1 — Every option on this sheet occupies the same slot. They differ only in how much of the black box you write yourself. [24]
02

The parts

5 options · ordered by verdict
Part DUE-BFF · v4.2.0 · rel. 2026-06-10 · net10.0 only
The reference implementation. Ships the whole pattern — /bff/login, /bff/logout, /bff/user, CSRF, refresh, server-side sessions, back-channel logout.
⭐ 1.6k
Licence
Commercialfree Community Edition under thresholds
Entry price
$5,750/yr, Lite, billed annually [1]
LOC you own
~50config only [3]
Upgrade burden
Highv3→v4 broke the DB schema [16]

4.7M NuGet downloads [14]. Anti-forgery is a single required X-CSRF: 1 header, which forces a CORS preflight for cross-origin callers — that's what actually blocks CSRF [13]. It does not need Duende IdentityServer: it drives the stock ASP.NET Core OIDC handler, so Keycloak and OpenIddict are just OIDC servers to it [50]. v4 (Dec 2025) added multi-frontend hosting and OpenTelemetry [25].

⚠ Trial mode is not a free tier: no key = a startup error and a 5-sessions-per-host limit that degrades into log noise, not an outage [11]. 4.2.0 dropped net8/net9 — the support window is short [17].

Capability · fitted from the box
Server-side sessionsEF store
Token refreshbundled ATM
CSRFX-CSRF: 1
Back-channel logout
/bff/user endpoint
Keycloak
OpenIddict
Multi-frontend hostv4
✔ Buy — or take it free
The only option that ships the whole pattern. Free under Community Edition; otherwise evaluate the licence at portfolio level, not per app.
Part YRP-OIDC-ATM · YARP 2.3.0 (Feb 2025) + ATM 4.2.0 (Mar 2026)
YARP + AddOpenIdConnect + Duende.AccessTokenManagement
Microsoft's own documented BFF recipe. YARP proxies, the stock OIDC handler does the protocol, Duende's Apache-2.0 library does the refresh cycle.
⭐ 9.6k
Licence
MIT + Apache-2.0no licence key [18] [4]
Entry price
$0forever
LOC you own
~200–400security-critical, see §03
Upgrade burden
Low (libs)but you own the glue

68.7M downloads [21]. YARP itself does no auth: "No authentication or authorization is performed on requests unless enabled in the route or application configuration", and by default your API receives the cookie, not a bearer token — swapping credential types "can be performed using custom request transforms" [27]. The transform is the whole trick:

transformBuilder.AddRequestTransform(async t => { var at = await t.HttpContext.GetTokenAsync("access_token"); t.ProxyRequest.Headers.Authorization = new("Bearer", at); }); — straight out of Microsoft's BlazorWebAppOidcBffAutoYarpAspire ⭐ 1.1k [29]. It is Blazor-shaped, but the BFF half transplants onto a React SPA unchanged [5].

Use GetUserAccessTokenAsync() from ATM, not raw token retrieval — the latter "doesn't automatically handle token expiration and most importantly the refresh cycle" [32]. And add RequestHeaderRemoveTransform("Cookie") [31].

Capability · fitted from the box
Server-side sessionswrite ITicketStore
Token refreshvia ATM
CSRFwrite it
Back-channel logoutwrite it
/bff/user endpointwrite it
Keycloak
OpenIddict
Aspire discoveryresolver
✔ The free path
Take this when the licence is a blocker. You get Microsoft's blessing and a skeleton to copy — and ~250–450 lines of security code you own forever.
Part DIY-000 · unversioned · maintainer: you, forever
Hand-rolled (IHttpForwarder / HttpClient)
One app.MapForwarder("/{**catch-all}", …) plus an HttpMessageInvoker and you have a proxy — no routing, no load balancing, no config discovery [34].
n/a
Licence
it's yours
Entry price
$0up front
LOC you own
~250–500all of it security-critical
Maturity
n/ayou are the maintainer

Legitimate in exactly one shape: serve the SPA from the BFF's own wwwroot and the whole thing is same-origin, so the "proxy" collapses to a thin /api passthrough — which is what damienbod's sample ⭐ 24 does, using YARP only in dev to front the Vite server [35].

⚠ Never hand-roll token refresh. Microsoft's own 102-line CookieOidcRefresher.cs has no locking and no single-flight [36] — and its header comment points straight at dotnet/aspnetcore#8175 ⭐ 38.3k, the still-open request for built-in support [10]. Also don't use ardalis/BFF ⭐ 6 — a stale fork of the archived Duende repo [39].

Capability · fitted from the box
Server-side sessions
Token refreshyou write it — don't
CSRF
Back-channel logout
/bff/user endpoint
Keycloak
OpenIddict
Anyone to call
✘ Not recommended
The refresh cycle is the part that breaks. Every serious write-up ends by migrating to a library. Take the free path instead — it is the hand-rolled path, minus the token code.
Part OPX-NET · LGPL-3.0 · community-maintained
A config-only OSS BFF: an identity-aware reverse proxy built on YARP that implements the token-handler pattern. Keycloak, OpenIddict, Auth0, Entra ID.
⭐ 159
Licence
LGPL-3.0copyleft — check with legal
Entry price
$0
LOC you own
~20configuration
Bus factor
Low⭐ 159 · maintainer unknown

Functionally it is the closest OSS thing to Duende.BFF — Redis-backed sessions, refresh, CSRF, partial back-channel logout, all from config [6]. The question is not capability, it's whether you want your admin console's session security depending on a 159-star project with an unclear maintainer.

Capability · fitted from the box
Server-side sessionsRedis
Token refresh
CSRF
Back-channel logoutpartial
Token-handler shape
Keycloak
OpenIddict
Support to callcommunity
◐ Conditional
Take it if you want a config-only OSS BFF and can accept a small bus factor. Its licence is LGPL-3.0, not MIT — that alone will decide it in some shops.
Part O2P-SIDECAR · v7.15.3 (Jun 2026) · container, not code
oauth2-proxy sidecar
If the console already sits behind an ingress, the BFF can be a container instead of a codebase. Zero .NET security code; the config is YAML.
⭐ 15k
Licence
MIT[7]
Entry price
$0
LOC you own
0YAML + a container tag
Upgrade burden
Lowbump the image

⚠ It gives you a gate, not a BFF. The token flow is header-shaped, not BFF-shaped: pass-access-tokenX-Forwarded-Access-Token; pass-authorization-header sends the ID token as the Bearer; set-authorization-header only helps in nginx auth_request mode [22]. There is no /bff/user shape, CSRF is its own cookie scheme, and back-channel logout is absent. For an internal console fronting your own .NET APIs the impedance mismatch usually costs more than the ~250 lines it saves.

Capability · fitted from the box
Server-side sessionsRedis store
Token refreshcookie-refresh
CSRFown cookie, not a header
Back-channel logout
/bff/user endpoint
Keycloak
OpenIddictgeneric OIDC
Scoped access tokenmay be the ID token
◇ Specialist
Wins only when you already run the reverse proxy, want zero .NET security code, and your API can be told which header to trust.
03

Price list · Duende, 2026

Billed annually · no EUR list price published
Lite — entry
$5,750
per year
2 BFF front-ends
Standard
$12,500
per year
10 BFF front-ends
Advanced
$24,900
per year
30 BFF front-ends
Add-on · SAML$1,500
Add-on · Automatic Key Mgmt$4,000
Add-on · FAPI conformance$7,500

Free for development, testing and personal projects — you only pay for production [1]. Licence validation is fully local, no outbound calls, and the same key may be used in dev/test/QA [11] [26].

Community Edition
$0
revenue < $1M USD/yr
AND capital < $3M USD
OR non-profit budget < $1M

Excludes redistribution and building identity infra for an end customer. If the console runs in your infra for your staff you are judged on your own revenue; if it ships to a client, that client needs the licence [2].

04

Bill of materials — the free path

LOC counted from Microsoft's own .NET 10 BFF sample
CookieOidcRefresher.csrefresh-token exchange at OnValidatePrincipal, re-validate the id_token, re-issue the cookie [36]
102
CookieOidcServiceCollectionExtensions.cswires the refresher, sets SaveTokens, adds offline_access
26
LoginLogoutEndpointRouteBuilderExtensions.cs/login challenge + /logout sign-out
46
auth wiring in Program.csAddOpenIdConnect + AddCookie + MapForwarder + transform [29]
~40
Microsoft's baselineand this is the happy path
~215
+ /bff/user endpoint, X-CSRF middlewareDuende gives these free [13]
~35
+ ITicketStore (server-side sessions)needed once cookies get fat [37]
~80
+ back-channel logout + revocationASP.NET Core has none built in [20]
~60–100
REALISTIC TOTALsecurity-critical · yours forever
~250–450
~250–450 linessecurity-critical code you own
$5,750 / yror $0 under Community Edition

That is the honest exchange rate. Nestenius's series ends by migrating a from-scratch BFF to Duende and reports the collapse: the whole custom BffController becomes app.MapBffManagementEndpoints();, the proxying becomes one line, and the custom CSRF middleware disappears entirely [38].

05

Handling warnings

What breaks a hand-built BFF

Refresh races

Concurrent requests each hit the token endpoint with the same refresh token. With rotation + reuse detection (Keycloak's default posture) that can nuke the session. Microsoft's 102-line refresher has no locking or single-flight. "Real-world incidents have occurred where custom implementations accidentally exposed tokens to wrong users." [8] [36]

SaveTokens blows up the cookie

Cookie limit ~4 KB, header limit ~8 KB. Base64 JWTs plus Keycloak group/role claims overflow it → HTTP 431, silent truncation, intermittent logout, WAF drops. ChunkingCookieManager splits at 3500 bytes but is tactical only. [37]

The real fix is a session store

ITicketStore / server-side sessions — the cookie becomes an opaque key. Duende does this with one call; on the free path you write it. Put it in from day one, not after the first HTTP 431. [12]

CSRF

Cookie auth means the browser attaches credentials automatically. You need SameSite, the __Host- prefix and a required custom header. Deschryver's otherwise-solid YARP walkthrough simply doesn't cover antiforgery. [13] [31]

Back-channel logout

No built-in support anywhere in ASP.NET Core. You implement the webhook, validate the logout token, persist it to a distributed cache, and check it on every request via a cookie event handler. [20]

Forwarding the cookie downstream

Strip it. The API should see only Authorization: Bearer — use RequestHeaderRemoveTransform("Cookie"). [32]

401 vs 302 on proxied calls — fixed in .NET 10

Historically the killer: an expired cookie turned an XHR into a 302 to Keycloak, and every hand-rolled BFF carried a custom OnRedirectToLogin hack to force a 401. As of .NET 10 the cookie handler returns 401/403 for endpoints carrying the new IApiEndpointMetadata — auto-added to [ApiController], JSON minimal APIs, TypedResults and SignalR. Opt out with [AllowCookieRedirect]. [9]

06

Platform delta — what .NET 10 itself ships

Does it shrink the gap?
✓✓
Cookie handler returns 401/403 instead of redirecting for API endpoints. Removes the single most annoying hand-rolled-BFF hack. [9] [40]
Shipped · breaking
Official BFF-with-YARP + Aspire sample (BlazorWebAppOidcBffAutoYarpAspire), plus a MinimalApiJwt downstream API and service-discovery destination resolution. A Microsoft-blessed skeleton to copy. [5] [29]
Shipped · sample, not a package
PAR (RFC 9126) used automatically when the IdP advertises it. Free hardening for the BFF's OIDC leg — Keycloak supports PAR (Entra does not). [29]
Shipped · .NET 9, default in 10 samples
~
Auth/authz metrics (aspnetcore.authentication.authenticate.duration, sign-in/out counters). An ops nicety. [40]
Shipped
Passkeys / WebAuthn in ASP.NET Core Identity. Irrelevant if Keycloak or OpenIddict is the IdP. [41]
Shipped · not applicable
✗✗
OAuth 2 refresh-token support (#8175, open since 2019) — the core gap. Was on the .NET 10 aspirational list; still open, now milestoned .NET 11 Planning. [10] [42]
Not shipped · .NET 11
DPoP (#58016) — the issue itself says it: "The current option in .NET is to either use Duende libraries, or write your own." [43]
Not shipped · .NET 11
A first-party BFF package or dotnet new bff template. Does not exist. No BFF item appears anywhere in the .NET 10 auth roadmap; the token-handler pattern remains third-party. [42]
Does not exist
Back-channel logout, an ITicketStore implementation, antiforgery-for-BFF. Still yours to write. [20]
Still yours
Read: .NET 10 makes the hand-rolled BFF pleasanter — 401s, an official sample, better metrics — not smaller. The two things that would actually shrink the code you own, refresh-token management and DPoP, both slipped to .NET 11 planning. Until then, "free token management" means Duende.AccessTokenManagement (Apache-2.0), which is a Duende library either way. You're just not paying for it. [4]
07

Accessories & third-party fittings

The option the .NET framing misses
⭐ 4.9k · Apache-2.0 · v0.32.9 (Jun 2026)

Identity-aware proxy / zero-trust gateway. Heavier: a policy engine and its own control plane. Overkill for one admin console. [44]

⭐ 2.4k · MIT · v2.3.0

Gate-only. Authenticates the user and forwards identity headers — it does not mint or refresh access tokens for a downstream API. [45]

built into the proxy

Primitives, not solutions. ForwardAuth "delegates authentication to an external service" [46]; auth_request "implements client authorization based on the result of a subrequest" [47]. Traefik's own OIDC middleware is a commercial Hub/Enterprise feature.

⭐ 948 · ARCHIVED DEC 2020 · EOL

Dead. The community successor is gogatekeeper ⭐ 318, Apache-2.0, v4.10.0 (Jun 2026) — alive but tiny. [48] [49]

When the sidecar wins: you already run the reverse proxy, you want zero .NET security code, and the console's APIs can accept a header you configure. When it loses: the sidecar gives you a gate, not a BFF — no /bff/user shape, CSRF is its own scheme, back-channel logout is absent, and the token it forwards may be the ID token rather than a scoped access token.
08

Order recommendation

For itenium-ui
Greenfield · internal · Keycloak-or-OpenIddict · one frontend Fitting instructions

Check Community Edition eligibility first

It is a two-line revenue test [2]. If it passes, Duende.BFF is free, complete and supported, and the answer is trivially Duende. Stop here.

If not eligible: YARP + AddOpenIdConnect + Duende.AccessTokenManagement

Copy Microsoft's BlazorWebAppOidcBffAutoYarpAspire sample [5]. Budget ~250–450 LOC, and put server-side sessions (ITicketStore) in from day one rather than after the first HTTP 431 [37].

Serve the SPA from the BFF's own origin

wwwroot in production, dev-proxy in dev. It makes SameSite and CSRF tractable and removes most of the proxy [35].

Price the licence at portfolio level, not per app

$5,750/yr buys ~350 lines of security code you never write, a DB-backed session store, back-channel logout, and someone to call. For a single internal console that's expensive — but the same licence covers 2 front-ends and the org's other apps [1].

10

Reference index

50 sources
Itenium.Forge · Component Catalogue · Section 4 · Sheet 1/1 Spec-sheet view of the canonical page · Expedition · Atlas