This article compares the security architecture of an application implemented using a public UI SPA with a trusted API backend and the same solution implemented using the backend for frontend (BFF) security architecture. The main difference is that the first solution is separated into two applications, implemented and deployed as two where as the second application is a single deployment and secured as a single application. The BFF has less risks and is a better security architecture but as always, no solution is perfect.
Setup BFF
The BFF solution is implemented and deployed as a single trusted application. All security is implemented in the trusted backend. The UI part of the application can only use the same domain APIs and cannot use APIs from separate domains. This architecture is the same as a standard ASP.NET Core Razor page UI confidential client. All APIs can be implemented in the same server part of the application. There is no requirement for downstream APIs. Due to this architecture, no sensitive data needs to be saved in the browser. This is effectively a trusted server rendered application. As with any server rendered application, this is protected using cookies with the required cookie protections and normally authenticates against an OIDC server using a trusted, confidential client with code flow and PKCE protection. Because the application is trusted, further protections can be added as required for example MTLS, further OIDC FAPI requirements and so on. If downstream APIs are required, these APIs do not need to be exposed in the public zone (internet) and can be implemented using a trusted client and with token binding between the client and the server. The XSS protection can be improved using a better CSP and all front-channel cross-domain calls can be completely blocked. Dynamic data (ie nonces) can be used to produce the CSP. The UI can be hosted using a server rendered page and dynamic meta data and settings can easily be added for the UI without further architecture or DevOps flows. I always host the UI part in a BFF using a server rendered file. A big win with the BFF architecture, is that the access tokens and the refresh tokens are not stored publicly in the browser. When using SignalR, the secure same site HTTP only cookie can be used and no token for authorization is required in the URL. This is an improvement as long as CSRF protection is applied. Extra CSRF protection is required for all server requests because cookies are used (as well as same site). This can be implemented using anti-forgery tokens or forcing CORS preflight using a custom header. This must be enforced on the backend for all HTTP requests where required. Because only a single application needs to be deployed, DevOPs is simpler and reduces complexity, this is my experience after using this in production with Blazor. Reduced complexity is reduced costs.

Setup SPA with public API
An SPA page solution is deployed as a separate UI application and a separate public API application. Two security flows are used in this setup and are two completely separate applications, even though the API and UI are “business” tightly coupled. The best and most productive solutions with this setup are where the backend APIs are made specifically and optimized for the UI. The API must be public if the SPA is public. The SPA has no backend which can be used for security, tokens and sensitive data are stored in the browser and needs to be accessed using Javascript. As XSS is very hard to protect against, this will always have security risks. When using SPAs, as the access tokens are shared around the browser or added to URLs for web sockets, it is really important to revoke the tokens on a logout. The refresh token requires specific protections for usage in SPAs. Access tokens cannot be revoked and reference tokens with introspection used in the API is the preferred security solution. A logout is possible with introspection and reference tokens using the revocation endpoint. It is very hard to implement a SSO logout when using an SPA. This is because only the front-channel logout is possible in an SPA and not a back-channel logout as with a server rendered application. This setup has performance advantages compared to the BFF architecture when using downstream APIs. The APIs from different domains can be used directly. Implementing UIs with PWA requirements is easier to implement compared to the BFF architecture. CSRF attacks are easier to secure against using tokens but has more risk with an XSS attack due to sensitive data in the public client.

Advantages using BFF
- Single trusted application instead of two apps, public untrusted UI + public trusted API (reduced attack surface)
- Trusted client protected with a secret or certificate
- No access/reference tokens in the browser
- No refresh token in the browser
- Web sockets security improved (SignalR), no access/reference token in the URL
- Backchannel logout, SSO logout possible
- Improved CSP and security headers (can use dynamic data and block all other domains) => possible for better protection against XSS (depends on UI tech stack)
- Can use MTLS, OIDC FAPI, client binding for all downstream API calls from trusted UI app, so much improved security possible for the downstream API calls.
- No architecture requirement for public APIs outside same domain, downstream APIs can be deployed in a private trusted zone.
- Easier to build, deploy (my experience so far), Easier for me means reduced costs.
- Reduced maintenance due to reduced complexity. (This is my experience so far)
Disadvantages using BFF
- Downstream APIs require redirect or second API call (YARP, OBO, •OAuth2 Resource Owner Credentials Flow, certificate auth )
- PWA support not out of the box
- Performance worse if downstream APIs required (i.e an API call not on the same domain)
- All UI API POST, DELETE, PATCH, PUT HTTP requests must use anti-forgery token or force CORS preflight as well as same site protection.
- Cookies are hard to invalidate, requires extra logic (Is this required for a secure HTTP only same site cookie? low risk)
Discussions
I have had some excellent discussions on this topic and very valid points and arguments against some of points above. I would recommend reading these (link below) to get a bigger picture. Thanks kevin_chalet for the great feedback and comments.
https://github.com/openiddict/openiddict-samples/issues/180
Notes
A lot of opinions exist with this setup and I am sure lots of people see this in a different way with very valid points. Others are following software tech religions which prevents them accessing, evaluating different solution architectures. Nothing is ever black or white. No one solution is best for everything and all solutions have problems, or future problems with any setup will always happen. I believe using the BFF architecture, I can increase the security for the solutions with less effort and reduce the security risks and costs, thus creating more value for my clients. I still use SPA with APIs and see this as a valuable and good security solution for some systems. The entry level for BFF architecture with some tech stacks is still very high.
Links
https://github.com/damienbod/Blazor.BFF.AzureAD.Template
https://github.com/damienbod/Blazor.BFF.AzureB2C.Template
https://github.com/damienbod/Blazor.BFF.OpenIDConnect.Template
https://github.com/DuendeSoftware/BFF
https://github.com/manfredsteyer/yarp-auth-proxy
https://docs.microsoft.com/en-us/aspnet/core/blazor/
https://docs.duendesoftware.com/identityserver/v5/bff/overview/
https://github.com/berhir/BlazorWebAssemblyCookieAuth
https://github.com/manfredsteyer/yarp-auth-proxy
https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/
https://csp-evaluator.withgoogle.com/
https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow
https://microsoft.github.io/reverse-proxy/index.html
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/certauth
https://github.com/openiddict/openiddict-samples/issues/180
https://content-security-policy.com/
https://csp.withgoogle.com/docs/strict-csp.html
https://github.com/manfredsteyer/angular-oauth2-oidc
https://github.com/damienbod/angular-auth-oidc-client
https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-overview
https://github.com/AzureAD/microsoft-identity-web
[…] Comparing the backend for frontend (BFF) security architecture with an SPA UI using a public API [#OAuth2 #Security #BFF #OIDC #OpenId connect #SPA] […]
[…] Comparing the backend for frontend (BFF) security architecture with an SPA UI using a public API (Damien Bowden) […]
[…] Comparing the backend for frontend (BFF) security architecture with an SPA UI using a public API – Damien Bowden […]
[…] Comparing the backend for frontend (BFF) security architecture with an SPA UI using a public API […]
[…] Comparing the backend for frontend (BFF) security architecture with an SPA UI using a public AP… […]