Development teams look very different from teams 20 years ago, heck, even 5 years ago. Here at Duende, we have developed with .NET since its inception, and we know many of you have as well. The technology has been foundational for building solutions for decades now. Still, in our time, we’ve seen organizations also begin to evolve, adopting new technology, deploying to new devices, and delivering new user experiences. To say we, as a professional industry, have come a long way would be an understatement.
The umbrella term “development” now sees teams adopting practices in frontend, backend, operations, database management, and many other areas. Professionals’ skills and discipline coalesce to deliver outcomes that bring joy to stakeholders and, most importantly, users. While your users may experience positive emotions using software you’ve developed, quietly in the background, the unsung hero of security ensures they do so in a safe and secure environment.
Let’s examine why now is an excellent time to consider Backend for Frontend (BFF) when building new solutions or modernizing existing ones.
Rise of the Polyglot Teams
Many teams choose to deliver solutions in what we affectionately refer to as “Enterprise Architecture”.
flowchart LR
subgraph Frontend
Other[etc...]
FE[Web App]
M[Mobile]
end
subgraph Backend
API[APIs]
DB[(Database)]
end
FE <--> API
M <--> API
Other <--> API
API <--> DB
The architecture isn’t as important as what it clearly shows. As an organization’s solutions become increasingly complex with multiple frontends, its development team needs to specialize in specific technologies to deliver enhanced user experiences and meet business demands. This specialization is visible in most teams today, with a clear delineation between frontend and backend developers. Frontend developers' responsibilities may focus on technologies such as JavaScript, CSS, and HTML, while backend developers concentrate on technologies like C#, LINQ, ADO.NET, and others. Say hello to the polyglot team.
Challenge of Delivering Security on the Polyglot Team
While a polyglot team may have a set of different responsibilities, they all share a common responsibility of security. Thankfully, the software development community has a communal sense of security, with professionals dedicating time, effort, and resources to offering guidance like the individuals who work at Duende Software. Security can be complicated, but being secure doesn’t have to be. When you find a trusted source that offers industry-leading advice and SDK implementations, it can simplify the process.
As of this post, we recommend that development teams with frontend applications begin modernizing their frontend solutions to leverage the BFF pattern. BFF is the most secure way to protect your users from becoming compromised while using your software.
In short, the BFF pattern utilizes hardened and battle-tested security patterns to keep your API access tokens out of the frontend client, while still ensuring a secure communication pathway between frontend and backend services.
flowchart LR
subgraph Frontend
Other[etc...]
FE[Web App]
M[Mobile]
end
subgraph Backend
BFF[BFF]
API[APIs️]
DB[(Database)]
end
FE <-->|Encrypted Cookies| BFF
BFF <-->|Tokens| API
M <--> API
Other <--> API
API <--> DB
As shown in the diagram, the BFF is a new addition to the previous architecture. Still, it introduces significant additional security to a development and deployment process that many development teams are already familiar with. While your team evolves to meet the needs of a changing landscape with new technologies, now is a perfect time to consider BFF as another step towards your modernization goals.
Less Security Code To Maintain with BFF
While the BFF pattern introduces a new element in your solution's architecture, it more than compensates for this by reducing the burden of requesting, storing, and managing tokens on your frontend client.
Let’s take a look at some JavaScript code a frontend developer may write to request data from a backend API. We don’t recommend doing this, and this code is provided for example purposes only. We recommend using the BFF pattern.
// axios-token-client.js
import axios from "axios";
/**
* Simple token store. Replace with your own (e.g., Secure storage).
*/
let inMemoryAccessToken = null;
export function getAccessToken() {
// Prefer in-memory (avoids repeated parsing), fallback to localStorage if needed.
return inMemoryAccessToken ?? window.localStorage.getItem("access_token");
}
export function setAccessToken(token) {
inMemoryAccessToken = token;
if (token) {
window.localStorage.setItem("access_token", token);
} else {
window.localStorage.removeItem("access_token");
}
}
/**
* Example refresh logic. Adapt to your API contract:
* - If your refresh token is an HttpOnly cookie, you may not need to pass anything here.
* - If stored in JS, pass it explicitly (but cookies are safer).
*/
let isRefreshing = false;
let refreshPromise = null;
const requestQueue = []; // queued resolvers for requests waiting on refresh
async function refreshAccessToken() {
// Avoid parallel refresh calls:
if (isRefreshing && refreshPromise) return refreshPromise;
isRefreshing = true;
refreshPromise = (async () => {
try {
// Example: POST /auth/refresh returns { accessToken: "..." }
const res = await axios.post("/auth/refresh", null, {
// include cookies if server uses HttpOnly refresh token cookies
withCredentials: true,
});
const newToken = res.data?.accessToken;
if (!newToken) throw new Error("No access token in refresh response");
setAccessToken(newToken);
return newToken;
} finally {
isRefreshing = false;
}
})();
try {
const token = await refreshPromise;
// Resolve queued requests
requestQueue.splice(0).forEach(({ resolve }) => resolve(token));
return token;
} catch (err) {
// Reject queued requests
requestQueue.splice(0).forEach(({ reject }) => reject(err));
throw err;
} finally {
refreshPromise = null;
}
}
/**
* Create an axios instance and wire up interceptors
*/
export const api = axios.create({
baseURL: "/api", // change to your API base URL
withCredentials: true, // if your API needs cookies (CSRF or refresh cookie)
});
// Request: attach Authorization header
api.interceptors.request.use((config) => {
const token = getAccessToken();
if (token) {
config.headers = config.headers ?? {};
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response: on 401, try to refresh and retry once
api.interceptors.response.use(
(response) => response,
async (error) => {
const original = error.config;
// Only attempt refresh for 401, not already retried, and only for our axios instance
const shouldAttemptRefresh =
error?.response?.status === 401 && !original?._retry && !original?.__isRefreshRequest;
if (!shouldAttemptRefresh) {
return Promise.reject(error);
}
original._retry = true;
try {
// If a refresh is already in progress, wait for it
if (isRefreshing && refreshPromise) {
await new Promise((resolve, reject) => requestQueue.push({ resolve, reject }));
} else {
await refreshAccessToken();
}
// After refresh, retry original request with new token
const newToken = getAccessToken();
original.headers = original.headers ?? {};
if (newToken) {
original.headers.Authorization = `Bearer ${newToken}`;
} else {
delete original.headers.Authorization;
}
return api(original);
} catch (refreshErr) {
// Optional: emit an event or redirect to login
setAccessToken(null);
return Promise.reject(refreshErr);
}
}
);
We still haven’t made an API request, let’s do that now.
// somewhere-in-app.js
import { api, setAccessToken } from "./axios-token-client.js";
// After login:
async function login(username, password) {
const { data } = await api.post("/auth/login", { username, password }, { withCredentials: true });
// Suppose server returns { accessToken }
setAccessToken(data.accessToken);
}
// Later, making authenticated requests:
async function fetchProfile() {
const { data } = await api.get("/users/me");
return data;
}
That’s a lot of JavaScript code, just to make a secure API request, and it’s still not the most secure experience you can deliver to your users.
Want to see the BFF equivalent?
const req = new Request("/bff/user", {
headers: new Headers({
'X-CSRF': '1'
})
});
let resp = await fetch(req);
let claims = await resp.json();
Voila! We promise, that’s it.
How is that possible? Well, BFF uses encrypted cookies to create a trusted session between your frontend clients and the resources it needs to deliver a user experience. When adopting BFF, your development team can focus more of their time on delivering value to users and less time on insecurely juggling tokens. BFF also means that no hoops are necessary for backend API implementation teams, as they’ll continue to receive the OAuth tokens they’ve grown to love.
If you want to see more of our code samples, you can check them out on our publicly available GitHub repositories. We have the most popular frontend frameworks represented, including React, Angular, and VueJS. BFF is also frontend-tech agnostic, so feel free to choose any technology you prefer or go with vanilla JavaScript.
BFF Fits Right In
Many security solutions want you to treat security as an outlier in your development process, a bolt-on accessory to a development style and process your team has honed over the years. Some are third-party services that operate entirely outside your organization’s operational and development security practices. It can be particularly awkward for development teams that rely on DevOps and GitOps practices to accelerate the time from ideation to production.
With Duende’s BFF, you can integrate our solution into any development style because it’s just another application in your growing solution. Our approach has been to deliver guidance to customers by codifying our expertise into simple-to-integrate C# SDKs.
What does that mean for your team? Well, it means a lot of essential things:
- Leverage the skills and expertise of your development team members to build, maintain, and extend solutions tailored to business needs.
- Reuse the pipelines you’ve built to help you test, verify, and deploy solutions to environments without any new conceptual investments or awkward caveats.
- The freedom to prioritize and plan development around these systems, including upgrades and enhancements. Don’t be rushed into unnecessary upgrades or breaking changes.
- Optimize organizational investments to maximize operating costs, whether on-premise or in the cloud.
There’s even more, but in general, BFF is Duende’s industry-leading guidance for teams to accelerate the development of their applications. BFF is here to help you build apps the way you want to develop with no compromises.
You’ve Got Options
With BFF v4, we’re introducing more options for solution architects to design, develop, and deploy solutions while considering key aspects such as resource utilization, compartmentalization, and overall comprehension.
Previously, the BFF implementation was “one frontend paired with one BFF”. We heard from many customers that this limited their deployment options to a particular architecture, even if all BFF hosts were identical.
flowchart LR
subgraph Backend
API
end
subgraph App 1
App3[App] <--> BFF3[BFF]
BFF3 <--> API[API]
end
subgraph App 2
App2[App] <--> BFF2[BFF]
BFF2 <--> API[API]
end
subgraph App N...
App1[App] <--> BFF1[BFF]
BFF1 <--> API[API]
end
We heard you loud and clear, and that’s why with BFF v4, we are introducing Multi-frontend support, which allows a single BFF host to manage multiple frontend applications and their associated tokens. This flexibility can provide hosting options to fit within your current operational and financial thresholds. Let’s examine a potentially simplified redesign.
flowchart LR
subgraph Backend
BFF <-->|Tokens| API
end
subgraph Frontend
App3[App] <-->|/app1 Encrypted Cookies| BFF[BFF]
App2[App] <-->|/app2 Encrypted Cookies| BFF[BFF]
App1[App] <-->|/app3 Encrypted Cookies| BFF[BFF]
end
The diagram shows only one option in a world of possibilities. You can also choose any configuration between the first and second that suits your needs.
Security is a Marathon, Not a Sprint.
While the C# SDK style makes Duende products ideal for fitting within a development sprint, security diligence remains an ongoing organizational responsibility. BFF is the best current practice recommended by the Internet Engineering Task Force (IETF) for securing frontend applications, and there’s no better time to establish your security posture than now.
If you’re nervous about modernizing your security, have no fear - Duende’s experts are here to help, along with the great and passionate community we’ve built over the years. We’re here for you, whether you're ready to dive in headfirst with BFF or prefer to ease into the process slowly.
Final Thoughts
At Duende, we operate on a set of principles to make our expertise in securing applications easy to understand and even more straightforward to implement. With the best current practices of the BFF pattern and the release of our BFF v4 SDK, now is the perfect time to modernize your solutions to address the security challenges facing organizations.
Feel confident that as your team adopts new technologies, you can maintain security without compromise. Additionally, you can adapt our SDK options to your philosophies; you don’t have to adapt to ours, allowing you to maximize the benefits of all your organizational investments. In summary, we look forward to helping you achieve your goals in building secure applications.
As always, if you have any questions, please don't hesitate to let us know in the comments. Be sure to follow us on Bluesky, X, or YouTube, and join our discussions on GitHub.