Duende BFF vs custom YARP BFF is the buy-vs-build decision for the same architecture: browser session cookies on the SPA origin, tokens off JavaScript, bearer attachment at the proxy — packaged product versus code you own.
Same goal: Angular ──cookie──► gateway ──Bearer──► APIs
Duende BFF = documented /bff/login, CSRF, OIDC glue + license
Custom YARP = you write cookie auth + SaveTokens + CSRF + on-call
Picture renting vs building a security desk: Duende ships the desk, badge printer, and runbook. Custom YARP is carpentry — cheaper upfront if you have staff, expensive at 2 a.m. when GetTokenAsync returns null.
New to this → read BFF architecture first. HttpOnly refresh without full BFF → refresh token cookie. Interview prep → If an interviewer asks.
What you are actually choosing
Both options need:
- A browser session (httpOnly cookie)
- OIDC or cookie login that deposits access/refresh tokens on the server
- A proxy that adds
Authorizationto downstream APIs - CSRF defense because cookies auto-send
- A session store that survives App Service swap
Duende BFF is a supported protocol for (1)–(4) aimed at SPAs, often sitting in front of YARP or its own proxy helpers. Custom YARP is you writing (1)–(4) with cookie auth + AddReverseProxy.
The architecture article already shows a YARP sketch. Do not clone it here. The question is who owns the session protocol.
Side-by-side (operations, not slogans)
| Concern | Duende BFF | Custom YARP BFF |
|---|---|---|
| Session + anti-forgery conventions | Documented BFF endpoints (/bff/login, user, logout) | You invent names and forget one in Angular |
| OIDC integration | First-class with Duende IdentityServer (and other OIDC) | Cookie handler + SaveTokens + your login path |
| CSRF | Package conventions (header requirements) | SameSite + custom header + antiforgery you must test |
| Proxy | Can sit with YARP; you still configure clusters | YARP is the product; middleware attaches tokens |
| License / support | Commercial relationship | Your on-call |
| Fit with ASP.NET Identity only | Possible, more glue | Natural if you already issue your own tokens |
| Fit with Duende IdentityServer already paid | Default | You would be paying and then ignoring the BFF package |
If the client already pays for Duende and the SPA is internet-facing, I start with Duende BFF unless there is a concrete reason not to. Rebuilding session middleware to "stay independent" is not independence. It is a second auth stack.
If the client will not take a Duende dependency, and identity is ASP.NET Identity + JWTs we issue, I build a thin YARP BFF and I budget tests for cookie flags. I do not pretend it is free.
When Duende BFF is the shorter path
- Multiple first-party Angular apps, one login, IdentityServer already in the diagram
- Security review wants a named BFF protocol, not a wiki page titled "our gateway"
- You need login/logout/userinfo endpoints that Angular developers can grep in docs
- Token server-side storage and refresh are in-package instead of a custom ticket store you will get wrong on the second instance
The win is not "Duende is more secure by magic." The win is fewer original cookie bugs. Most BFF incidents I have seen were SameSite, __Host- vs Domain=, or a leftover Angular apiUrl pointing at the resource API.
When a custom YARP BFF is the honest path
- No Duende in the account, and nobody will approve it this quarter
- You already have a YARP gateway for path-based routing (claims API, catalog API, ops API)
- Login is not OIDC yet — Identity cookie on the BFF, APIs still validate JWTs you mint at login
- Native mobile apps will never use this BFF (they keep bearer tokens). The gateway is browser-only; do not force phones through it
Custom is also right when the "BFF" is really an API gateway that happens to attach tokens, and the SPA is hosted from the same origin. Keep the cookie surface small: login, logout, user, proxy. Do not put business MVC in the BFF "because it is already ASP.NET Core."
The license conversation I actually have
I put two numbers on a slide: estimated engineering days to own cookie + CSRF + session store + on-call, versus Duende list price for this product. I do not invent a moral winner.
Teams that "cannot afford Duende" sometimes spend more than the license on a custom BFF that still has no CSRF tests. Teams that "must use Duende" sometimes have one SPA and would have been fine with httpOnly refresh cookies without a full BFF.
If the threat model is XSS on a public clinic portal, some BFF (or equivalent) is the point. Which package is a procurement decision sitting on top of that.
Angular contract (both options)
The SPA should look the same:
- Same-origin
/api/...or/bff/... - No Bearer interceptor for cookie-backed calls
GETuser endpoint for guardsPOSTlogout- CSRF header on mutations
If you switch from custom YARP to Duende BFF later, keep this contract and only change the path prefix. That is cheaper than teaching every feature module a new auth story.
Guards still belong in Angular. They check session, not localStorage. See auth guards.
Failure modes that are product-specific
Duende BFF
- Angular still calls the resource API host (CORS returns, you think auth is "flaky")
- License / package version skew with IdentityServer
- Assuming BFF removes XSS. It removes token theft as the default prize. You still need CSP.
Custom YARP
GetTokenAsync("access_token")is null because login never calledSaveTokens- In-memory auth ticket: first scale-out logs everyone out
- YARP logs the
Authorizationheader into App Insights - CSRF only "SameSite=Lax" on a site that also has a GET that mutates
Both
- SignalR negotiate bypasses the proxy (JWT over WebSocket is a different attach)
- Refresh token reuse if you still rotate in a world with two tabs and no server session (rotation)
A decision I write in the architecture doc
I will not leave this as "we'll see." One paragraph:
Browser apps use a BFF. Tokens never enter JavaScript. We use Duende BFF because we already operate IdentityServer and will not staff a custom session protocol. Resource APIs stay JWT. Mobile uses bearer directly. EDI jobs use client credentials. CSRF is required on cookie mutations.
Or:
Browser apps use a custom YARP BFF on the same site as Angular. Cookie
__Host-bff, antiforgery headerX-CSRF. We do not take a Duende BFF dependency. Identity is ASP.NET Identity. Session store is distributed cache before the second instance.
If you cannot write one of those paragraphs, you do not have a BFF. You have a backlog item named BFF.
When I still skip BFF entirely
Internal admin on VPN, ten users, accepted XSS risk in writing — SPA JWT is fine. Machine clients never needed a BFF. MapIdentityApi from a first-party SPA is a different product; do not wrap it in YARP as theatre.
If an interviewer asks
"Is Duende BFF required for the BFF pattern?"
Strong answer: No. The pattern is tokens off JavaScript and same-origin cookies. Duende is one packaged implementation with OIDC login, CSRF conventions, and documented endpoints. Custom YARP with cookie auth achieves the same architecture if you implement session, CSRF, and token storage yourself.
"When would you choose custom YARP over Duende?"
Strong answer: No Duende license approved, existing YARP gateway team, ASP.NET Identity-only login without OIDC, or browser-only scope (mobile keeps bearer). Budget engineering days for cookie bugs and distributed session store before the second App Service instance.
"What does YARP alone not give you?"
Strong answer: YARP only proxies. It does not login, store tokens, set cookie flags, defend CSRF, or logout. A BFF is cookie session + token attach + CSRF — YARP is the forwarding layer inside that.