Skip to content
API and Integration

Backend for Frontend (BFF): API Patterns for Mobile and Web Clients

18 min read

The Backend for Frontend (BFF) pattern gives each client type (mobile, web, admin) its own backend service that aggregates and optimises data from downstream APIs. It solves the problem of one API trying to serve clients with fundamentally different needs. This guide covers when BFF is worth the overhead, the BFF-for-SPA security pattern, how to build it on .NET and Azure, the anti-patterns that cause the most damage, and when simpler alternatives are better. Our API and integration services cover BFF design and implementation as part of broader API programmes.

The problem BFF solves

Most applications start with a single API that serves all clients. Early on, this works. The web app and the mobile app need roughly the same data, and one set of endpoints handles both.

The problems emerge as clients diverge:

  • The mobile app needs smaller payloads. It fetches the same endpoints as the web app but discards half the fields. Bandwidth is wasted, and the app feels slower than it should.
  • The web dashboard needs aggregated data. A single screen requires data from three different microservices. The front-end makes three calls and stitches them together, adding latency and complexity to the client.
  • The internal admin tool needs richer access. Admin users need fields and operations that should never be exposed to external clients. The API accumulates permission checks and conditional logic.
  • Versioning becomes painful. A change that benefits the web app breaks the mobile app. Every API change requires coordination across all client teams.

The single API becomes a bottleneck: too fat for mobile, too thin for dashboards, too permissive for security, and too coupled for independent releases.

The pattern that solves this is not new. It emerged at SoundCloud around 2011 and was named and documented by Sam Newman in Backends For Frontends. Microsoft now documents it formally in the Azure Architecture Center, which is worth reading alongside this guide for the Well-Architected Framework trade-offs.

What BFF looks like in practice

A BFF architecture introduces a thin service layer between each client type and the downstream services.

Mobile App  ──▶  Mobile BFF  ──▶  ┌─ User Service
                                   ├─ Order Service
Web App     ──▶  Web BFF     ──▶  ├─ Product Service
                                   └─ Analytics Service
Admin Tool  ──▶  Admin BFF   ──▶

Each BFF:

  • Aggregates data from multiple downstream services into a single response tailored to its client
  • Transforms payloads to match the client’s needs (fewer fields for mobile, richer data for admin)
  • Owns its own API contract and can evolve independently of other BFFs
  • Handles client-specific auth flows (PKCE for mobile, session cookies for web, service tokens for admin)

The downstream services remain generic and reusable. The client-specific logic lives in the BFF, not in the downstream services or the front end.

There is a second, quite different reason to build a BFF, and it has nothing to do with payload shape. For browser applications, a BFF is the mechanism that keeps OAuth tokens out of JavaScript entirely. That security case is covered in its own section below, and it is often the stronger justification of the two.

BFF vs single API vs API gateway

These three patterns solve different problems and often coexist. The table below summarises where each one fits.

ConcernSingle APIAPI gatewayBFF
Client-specific payload shapingNoNoYes
Cross-service aggregationPossible, pollutes the modelNoYes
Authentication and token validationYesYesYes, per client type
Rate limiting and quotasApplication codeYesNo, belongs at the gateway
Routing and versioningApplication codeYesNo, belongs at the gateway
Independent client team release cadenceNoNoYes
Keeps OAuth tokens out of the browserNoNoYes
Operational costLowestLowHighest
Sensible starting pointYesOnce you have more than one backendOnly when a real constraint appears

Single API

One API serves all clients. This is the right starting point for most projects and remains the right architecture when:

  • All clients need the same data in the same shape
  • The API surface is small (under roughly 30 endpoints)
  • There is one client team or the client teams are tightly coordinated
  • Payload optimisation is not a priority

When it breaks down: when client requirements diverge enough that the API accumulates client-specific logic, conditional fields, and endpoint sprawl.

API gateway

A gateway like Azure API Management handles cross-cutting concerns: routing, authentication, rate limiting, and monitoring. It does not contain business logic.

The gateway works well with both single-API and BFF architectures:

  • Single API and gateway: the gateway fronts one backend
  • BFF and gateway: the gateway fronts multiple BFFs, routing by client type or API version

The gateway is not a replacement for BFF. It operates at a different layer. Microsoft’s guidance reinforces this split: cross-cutting features such as monitoring and authorisation should be abstracted out of the BFF and handled by the gateway using the Gatekeeper, Rate Limiting, and Gateway Routing patterns.

BFF

BFF adds a per-client service layer that contains client-specific aggregation and transformation logic. It makes sense when:

  • Different client types have genuinely different data and performance requirements
  • Client teams want to iterate independently on their API contracts
  • You need different auth patterns per client type
  • Payload optimisation matters (mobile bandwidth, admin tool data richness)
  • A browser client needs OAuth tokens kept server-side

The cost: more services to build, deploy, and maintain. Only adopt BFF when the complexity of a single API exceeds the complexity of multiple BFF services.

The BFF-for-SPA security pattern

This is the part of the pattern most often skipped, and for a single-page application it is usually the main event.

The problem with tokens in the browser

A single-page application that performs its own authorization code flow ends up holding an access token, and often a refresh token, somewhere the browser can reach. That means localStorage, sessionStorage, or a JavaScript variable. All three are readable by any script running on the page.

This matters because a single cross-site scripting (XSS) flaw stops being a session problem and becomes a token theft problem. An attacker who can run script in your page can exfiltrate the token and use it directly against your API, from their own infrastructure, for as long as the token lives. Refresh token rotation reduces the window but does not close it.

The IETF’s OAuth 2.0 for Browser-Based Applications working document, which is progressing as a Best Current Practice, works through these threats in detail. Its central architectural recommendation is the token-mediating backend: a server-side component that holds the tokens on the browser’s behalf. That component is a BFF.

How the pattern works

The flow is straightforward once the responsibility moves server-side:

  1. The browser hits a login route on the BFF. The BFF, not the SPA, starts the OpenID Connect authorization code flow with Proof Key for Code Exchange, defined in RFC 7636.
  2. The identity provider redirects back to the BFF with an authorization code. The BFF exchanges it for tokens using its client secret, which never leaves the server.
  3. The BFF stores the access and refresh tokens server-side, keyed to a session, and issues the browser an encrypted, HttpOnly, Secure, SameSite cookie.
  4. The SPA calls its API through the BFF on the same origin. The BFF looks up the session, attaches the access token, and proxies the call to the downstream API.
  5. Token refresh happens on the server. The SPA never sees, stores, or handles a token.

The browser holds a cookie it cannot read. An XSS flaw can still make requests as the user while the page is open, which is bad, but the attacker cannot lift a bearer token and replay it elsewhere. That is a meaningful reduction in blast radius.

Cookies, CSRF, and same-origin

Moving to cookies reintroduces cross-site request forgery (CSRF) as a concern, so the pattern layers three defences:

  • SameSite=Strict cookies, so the browser will not attach the session cookie to cross-site requests
  • A required custom header on every API call, which a cross-site form post cannot set
  • A restrictive CORS policy, so only allowed origins can make credentialed requests

Duende’s BFF Security Framework implements exactly this combination, and requires a custom x-csrf header on API endpoints by default. Prefixing the cookie name with __Host- is worth doing as well, because it forces the cookie to be secure, host-only, and path-scoped to the root.

Building it on .NET

Two libraries do most of the work, and they compose.

Duende.BFF is the purpose-built option. It packages session management, the OIDC interactions, CSRF protection, and API proxying for React, Angular, Vue, and Blazor WebAssembly front ends. The registration follows the standard ASP.NET Core authentication pattern:

builder.Services.AddBff()
    .ConfigureOpenIdConnect(options =>
    {
        options.Authority = builder.Configuration["Oidc:Authority"];
        options.ClientId = builder.Configuration["Oidc:ClientId"];
        options.ClientSecret = builder.Configuration["Oidc:ClientSecret"];
        options.ResponseType = "code";

        // offline_access is what makes server-side refresh possible.
        options.Scope.Clear();
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("offline_access");

        options.GetClaimsFromUserInfoEndpoint = true;
        options.SaveTokens = true;
    })
    .ConfigureCookies(options =>
    {
        // __Host- forces Secure, host-only, and path=/.
        options.Cookie.Name = "__Host-bff";
        options.Cookie.SameSite = SameSiteMode.Strict;
    });

Note the licensing before you commit: Duende.BFF is free for development, testing, and personal projects, but production use requires a paid licence. Budget for it at design time rather than at go-live.

YARP, Microsoft’s reverse proxy library for ASP.NET Core, is the other half. It handles the proxying of front-end calls to downstream APIs with full programmatic control over routing and request transformation, inside the ASP.NET Core pipeline. Duende.BFF integrates with YARP directly, and YARP on its own is a reasonable choice if your BFF is mostly pass-through and you would rather not take a licence dependency.

If you are running the wider system on .NET Aspire, both fit its service defaults and service discovery without special handling.

Implementing BFF on Azure

Architecture

A typical BFF deployment on Azure:

  • Azure API Management as the unified gateway, routing to BFF backends by path prefix or custom header
  • BFF services on Azure App Service (simple), Azure Container Apps (autoscaling and container flexibility), or Azure Functions (the option Microsoft’s own reference example uses)
  • Downstream services on App Service, AKS, or Azure Functions
  • Azure Entra ID for authentication, with each BFF validating tokens appropriate to its client type
  • Azure Cache for Redis for BFF session state, which is required as soon as you run more than one instance

Routing strategy

APIM routes traffic to the correct BFF based on a path prefix:

  • /api/mobile/* routes to the mobile BFF
  • /api/web/* routes to the web BFF
  • /api/admin/* routes to the admin BFF

Alternatively, use a custom X-Client-Type header. The path prefix approach is simpler and more transparent for debugging.

Auth per client type

Each BFF handles the auth flow appropriate to its client:

  • Mobile BFF: OAuth 2.0 with PKCE, refresh tokens, biometric-triggered re-auth. Native apps can hold tokens in platform-secured storage, so the token-mediating pattern is less critical here than it is in a browser. See our mobile app backends service for the full pattern.
  • Web BFF: the token-mediating pattern described above, with server-side sessions and a hardened cookie.
  • Admin BFF: Azure Entra ID with role-based access control (RBAC) and conditional access policies.

APIM validates the token at the gateway layer. The BFF validates claims and applies client-specific authorisation logic. Do not let the BFF trust a client-supplied user identifier just because the request arrived through the gateway.

A worked aggregation endpoint

This is what the aggregation half of a BFF actually looks like. The screen needs an order, its customer, and its delivery tracking. Three downstream calls become one client call, the two independent calls run concurrently, and a failure in the non-critical service degrades rather than breaking the screen.

// Mobile BFF: one call backs the whole order summary screen.
app.MapGet("/orders/{id}/summary", async (
    string id,
    IOrderService orders,
    ICustomerService customers,
    IDeliveryService delivery,
    ILogger<Program> log,
    CancellationToken ct) =>
{
    var order = await orders.GetAsync(id, ct);
    if (order is null)
    {
        // RFC 9457 problem details, not a raw downstream error.
        return Results.Problem(
            title: "Order not found",
            statusCode: StatusCodes.Status404NotFound);
    }

    // Independent calls run concurrently. Serial awaits here would add
    // the two latencies together for no reason.
    var customerTask = customers.GetAsync(order.CustomerId, ct);
    var trackingTask = delivery.GetTrackingAsync(id, ct);

    await Task.WhenAll(customerTask, trackingTask);

    return Results.Ok(new OrderSummary(
        order.Id,
        order.Total,
        customerTask.Result.DisplayName,
        // Tracking is decoration, not the point of the screen.
        // A delivery outage should not 500 the order summary.
        trackingTask.Result?.EstimatedArrival));
});

Three things in that example are the actual work, and they are what separates a BFF from a passthrough:

  • Concurrency. Independent downstream calls should overlap. Awaiting them in sequence is the most common performance mistake in aggregation code, and it silently makes the BFF slower than the client-side stitching it replaced.
  • Graceful degradation. Decide per field whether a downstream failure is fatal to the response. The BFF is the right place to make that call, because only the BFF knows what the screen needs.
  • Error translation. Downstream errors get translated into a consistent contract. RFC 9457 problem details is the standard worth adopting, and it replaced the older RFC 7807.

Add a timeout and a circuit breaker per downstream dependency. A BFF that waits indefinitely on a slow service turns one degraded dependency into a fully degraded client.

Caching

BFFs are excellent caching points. Each BFF knows exactly what its client needs and how often that data changes:

  • Mobile BFF: aggressive caching with ETags and short time-to-live values for frequently changing data, long values for reference data
  • Web BFF: cache aggregated dashboard responses with a lifetime matched to the reporting refresh interval
  • Admin BFF: minimal caching, because admins usually need fresh data

Use Azure Cache for Redis or in-memory caching in the BFF process, depending on scale. Be careful with per-user data: a cache key that omits the user identity is a data leak, not a performance optimisation.

When to use GraphQL instead of BFF

GraphQL and BFF solve overlapping problems. Both address the “different clients need different data shapes” challenge, and Microsoft’s pattern documentation now says so directly: if you use GraphQL with frontend-specific resolvers, BFF services may add no value.

GraphQL as BFF replacement: a single GraphQL API lets each client query exactly the fields it needs. The mobile app requests a compact field set; the web dashboard requests a richer set. No separate BFF services needed.

When GraphQL works better than BFF:

  • The data model is graph-shaped, with entities in deep relationships
  • Client teams want maximum flexibility to change queries without backend changes
  • You have the team skills to manage schema design, resolver performance, and query cost control

When BFF works better than GraphQL:

  • Different clients need fundamentally different auth flows, not just different data shapes
  • You need OAuth tokens kept out of the browser, which GraphQL does nothing to address
  • Some clients need non-query operations (file uploads, streaming, WebSocket connections) that GraphQL handles awkwardly
  • The team is more comfortable with REST-style services than GraphQL schema management
  • You need strict control over what data each client type can access, which BFF makes explicit per service

The two also combine. A GraphQL server can sit inside a BFF, giving you the token-mediating security properties and flexible querying together. For a deeper comparison of API styles, see our guide on REST vs GraphQL vs gRPC.

BFF anti-patterns

These are the failure modes worth naming, because each one is easy to walk into and expensive to walk back out of.

Business logic in the BFF

The BFF should aggregate, transform, and optimise. If it is calculating prices, enforcing entitlement rules, or managing state, that logic belongs in a downstream service where every client can reach it.

The damage is not theoretical. Once pricing logic lives in the mobile BFF, the web BFF grows its own copy, and the two drift. You then have two answers to the same business question and no authoritative one. Microsoft’s guidance flags code duplication as a probable outcome of this pattern, and this is the form of it that actually hurts.

One BFF per application rather than per client type

If your iOS and Android apps have identical data needs, they share one mobile BFF. Splitting by application rather than by client type multiplies your operational surface without giving you anything.

The test is simple: if two clients would consume an identical contract, they do not need separate BFFs. Microsoft’s guidance is that the pattern may not suit you at all when interfaces make the same or similar requests.

The distributed monolith

This is the worst outcome. Every BFF is coupled tightly enough to the downstream services that a change to one service requires a coordinated release of all the BFFs.

You now have the operational cost of many services and the release coupling of one. The usual cause is BFFs reaching into downstream data models rather than consuming a stable contract. If your release plan involves the phrase “deploy all of them together”, you have built this.

Treating the BFF as a security boundary while trusting the client

A BFF is a good place to enforce authorisation, and a bad place to assume the caller is honest. If the BFF reads a user identifier from the request body or a header rather than from the validated token, an attacker will use it to read someone else’s data.

The identity used for downstream calls should come from validated token claims, every time, with no exceptions for internal tooling.

Adopting BFF too early

The most common one. Start with a single API. Introduce BFFs when client requirements genuinely diverge, when the browser token problem needs solving, or when the single API is visibly accumulating client-specific complexity.

Three BFFs means three deployment pipelines, three sets of alerts, three on-call surfaces, and one extra network hop on every request. Microsoft’s guidance calls out that added latency explicitly. If you cannot name the constraint the BFF removes, you do not need one yet.

Inconsistent error handling

Each BFF should return errors in a consistent format, and RFC 9457 problem details is the right default. Downstream service errors should be translated, not leaked through to the client as raw internal errors. Leaking them couples your client to your internal topology and hands an attacker a free map of your services.

Where to start

If you are considering BFF for your architecture:

  1. Work out which problem you are solving. Payload and aggregation pressure, and browser token security, are different problems with the same solution. The second one justifies a BFF on its own, even for a single client.
  2. Document where your current API serves clients differently. Look for conditional logic, client-specific query parameters, and endpoints used by only one client type. These are the pressure points where a single API is stretching.
  3. Evaluate whether GraphQL could solve the data-shape problem. If the issue is primarily flexibility and all clients share the same auth model, GraphQL may be simpler than multiple BFF services. It will not solve the token problem.
  4. Start with one BFF for the client type that differs most. Usually this is the browser SPA (token security) or mobile (smaller payloads, different auth flow, offline support). Keep the other clients on the single API until they need their own BFF.
  5. Set the operational baseline before the second BFF. Distributed tracing across the gateway, BFF, and downstream services, plus per-dependency timeouts and circuit breakers. Retrofitting this across three BFFs is considerably harder than building it into the first one.

For help designing a BFF architecture or evaluating whether it is the right fit, see our mobile app backends and API and integration services or book a consultation.

Frequently asked questions

What is the Backend for Frontend pattern?
The Backend for Frontend (BFF) pattern uses a separate backend service for each type of client (mobile app, web app, internal tool). Each BFF aggregates, transforms, and optimises data from downstream services for its specific client. This avoids the problem of a single API trying to serve all clients with different data, performance, and auth requirements. The pattern was first used at SoundCloud and named by Sam Newman, and Microsoft documents it in the Azure Architecture Center.
How is BFF different from an API gateway?
An API gateway (like Azure API Management) handles cross-cutting concerns: authentication, rate limiting, routing, and monitoring. It does not typically contain business logic or data transformation. A BFF sits behind the gateway and contains client-specific logic: aggregating calls to multiple services, reshaping payloads, and optimising responses for a particular client type. The two work together.
When should I not use BFF?
If all your clients need the same data in the same shape, a single API is simpler and cheaper to maintain. BFF adds operational overhead (more services to deploy, monitor, and version). Microsoft's guidance is explicit that the pattern may not suit you when interfaces make the same or similar requests, or when only one interface talks to the backend. Use BFF when you have genuinely different client requirements, not as a default pattern for every project.
How many BFFs should I have?
One per distinct client type, not one per client application. If your iOS and Android apps have identical data needs, they share one mobile BFF. A web dashboard with different data requirements gets its own BFF. An internal admin tool that needs richer data access gets a third. Start with the minimum and split only when client needs genuinely diverge.
What is a BFF anti-pattern?
The four that cause the most damage are: putting business logic in the BFF so rules drift between clients; building one BFF per application rather than per client type, which multiplies services without adding value; creating a distributed monolith where every BFF must deploy alongside every downstream change; and treating the BFF as a security boundary while still trusting client-supplied identity. A fifth, adopting BFF before a single API has actually started to strain, is the most common of all.
What is the BFF pattern for single-page applications?
The BFF-for-SPA security pattern keeps OAuth tokens on the server instead of in the browser. The BFF performs the authorization code flow, stores the tokens server-side, and gives the browser only an encrypted, HttpOnly, SameSite cookie. API calls go through the BFF, which attaches the access token. This removes token exfiltration through cross-site scripting as a threat, which is why the IETF's browser-based apps guidance treats it as the most secure option available to browser applications.
Can I use GraphQL as a BFF?
Yes, and this is a common pattern. A GraphQL server acts as the BFF layer, letting each client query exactly the data it needs from a unified schema. Microsoft's own guidance notes that if you already use GraphQL with frontend-specific resolvers, separate BFF services may add no value. The trade-off is that you take on GraphQL's complexity (schema management, query cost control, caching) instead of maintaining multiple simpler BFF services.
How does BFF work with Azure API Management?
Azure APIM sits in front of all BFFs as the unified gateway. Each BFF registers as a separate backend in APIM. The gateway handles authentication, rate limiting, and routing. The BFF handles client-specific data aggregation and transformation. This gives you centralised governance with per-client optimisation.

Ready to transform your software?

Let's talk about your project. Contact us for a free consultation and see how we can deliver a business-critical solution at startup speed.