{"id":4572,"date":"2025-08-11T09:46:35","date_gmt":"2025-08-11T08:46:35","guid":{"rendered":"https:\/\/discovery.cevolution.co.uk\/ciam\/?p=4572"},"modified":"2026-04-05T13:19:23","modified_gmt":"2026-04-05T12:19:23","slug":"single-page-survival-when-integrating-with-oauth-2","status":"publish","type":"post","link":"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/08\/11\/single-page-survival-when-integrating-with-oauth-2\/","title":{"rendered":"Single Page Survival When Integrating With OAuth 2"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 16<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>\n<p>Like the article I came across recently, in which theoretical physicist Michio Kaku claimed that <em>&#8220;time travel is now simply an engineering problem&#8221;<\/em>, I sometimes find that developing applications in the world of Customer Identity is not dissimilar, particularly if you&#8217;re building a <span class=\"popup-trigger popmake-2828\" data-popup-id=\"2828\" data-do-default=\"0\">SPA<\/span>. <\/p>\n\n\n\n<div class=\"wp-block-group has-text-align-center has-global-padding is-layout-constrained wp-container-core-group-is-layout-7db9d80f wp-block-group-is-layout-constrained\" style=\"padding-right:0;padding-left:0\">\n<iframe loading=\"lazy\" src=\"https:\/\/www.linkedin.com\/embed\/feed\/update\/urn:li:share:7275753734377238528?collapsed=1\" height=\"590\" width=\"95%\" frameborder=\"0\" allowfullscreen=\"\" title=\"Embedded post\"><\/iframe>\n<\/div>\n\n\n\n<p>&#8220;You&#8217;re vulnerable to XSS attacks if you keep tokens in Local Storage!&#8221;, &#8220;Prefer short-lived tokens&#8221;, &#8220;Use a <span class=\"popup-trigger popmake-2873\" data-popup-id=\"2873\" data-do-default=\"0\">BFF<\/span>&#8220;, and &#8220;Follow the <span class=\"popup-trigger popmake-4375\" data-popup-id=\"4375\" data-do-default=\"0\">Principle of Least Privilege<\/span>&#8221; are just some of the pieces of advice you&#8217;ll hear as a software engineer. I myself have said some very similar things in many of the articles I&#8217;ve written.<\/p>\n\n\n\n<p>The challenge most software engineering folks have, though, is exactly how to do all that?!? SPAs are notoriously challenging when it comes to Customer Identity, particularly from a security perspective (as outlined in the IETF draft document <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-ietf-oauth-browser-based-apps#name-browser-based-oauth-20-clie\" target=\"_blank\" rel=\"noopener\" title=\"\">here<\/a>). And so, just like the mechanical, electrical and electronic engineers left to figure out how to make time travel a reality, the software engineers integrating with a <span class=\"popup-trigger popmake-1185\" data-popup-id=\"1185\" data-do-default=\"0\">CIAM<\/span> solution are more often than not left to figure things out too! <\/p>\n\n\n\n<p>My name\u2019s Peter Fernandez, and in this article, I\u2019m going to discuss one of the more challenging use cases: how to integrate Identity from an Access Management perspective into a Single Page Application in the most secure and user-friendly way possible. Hopefully providing some practical insight along the way as well \ud83d\ude0e<\/p>\n\n\n<h2 class=\"wp-block-heading\" id=\"what-is-a-single-page-application\">What is a Single Page Application?<\/h2>\n\n\n<p>Fueled by frameworks like React, Vue, Angular, etc., the Single Page Application paradigm (or SPA for short) has grown in prominence over the years, providing a means for building web applications that interact with the user by dynamically updating the current web page with new data instead of loading entirely new pages.&nbsp;This results in a predominantly faster, more app-like overall experience for the user, as page reload isn&#8217;t required for each user interaction.<\/p>\n\n\n\n<p>From a developer perspective, a SPA also offers significant cost savings, as it can be loaded from what is essentially a CDN and requires little to no persistent web server interaction. <\/p>\n\n\n\n<p>To achieve its goals, a SPA will typically leverage one or more resource servers (APIs), which are server-side components implemented in a non-persistent fashion. Whilst calling an API will consume resources and will likely incur a cost where third-party hosting is concerned, once the API completes, no further resources are typically used (nor charges incurred). If you want to learn more, James Quick provides a contrasting comparison in his video entitled <em><a href=\"https:\/\/youtu.be\/me5lS00Nj1k\" target=\"_blank\" rel=\"noopener\" title=\"\">Multi page vs Single Page Applications &#8211; Which One Is Right For You?!<\/a><\/em><\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-b6fc1811f2c2a3171a3bd5b4d3afb427 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Whilst a discussion concerning SPAs vs Regular (Multi-page) Web Applications is beyond the scope of this article, it&#8217;s a reasonable argument to say that with Server Side Rendering (SSR), the line between Single Page Applications and Regular Web Applications \u2014 a.k.a. Multi Page Applications \u2014 blurs to some extent.<\/em><\/p>\n<\/div>\n\n\n\n<p>While the Single Page Application paradigm offers an attractive proposition to the application developer, they are particularly susceptible to certain security vulnerabilities, e.g. Cross-Site Scripting (XSS), as a prime example. In essence, if a SPA implementation isn&#8217;t using security-conscious practices,&nbsp;then, without the <span class=\"popup-trigger popmake-578 \" data-popup-id=\"578\" data-do-default=\"0\">confidential-client<\/span> aspect provided by a dedicated backend, a SPA is typically forced to rely on client-side storage mechanisms that are inherently insecure.<\/p>\n\n\n<h2 class=\"wp-block-heading\" id=\"challenges-integrating-a-ciam-solution\">Challenges Integrating a CIAM Solution<\/h2>\n\n\n<p>From a Customer Identity perspective, integration as part of a SPA is a challenge. Particularly when it comes to API usage. As discussed, the best-practice advice you&#8217;ll typically encounter advises against the use of anything that can facilitate the likes of XSS attacks, including the avoidance of insecure storage mechanisms, together with anything that gives a would-be attacker something that they can ultimately wreak havoc with.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-632ce220b917cace5fccc969d4911e41 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>XSS  \u2014 short for Cross Site Scripting \u2014 is a threat that has been around almost as long as the web itself! It&#8217;s beyond the scope of this article to explain in detail, but Jeff Crume, an IBM Distinguished Engineer, does a good job in his video entitled &#8220;<a href=\"https:\/\/youtu.be\/z4LhLJnmoZ0\" target=\"_blank\" rel=\"noopener\" title=\"\">Cross-Site Scripting: A 25-Year Threat That Is Still Going Strong<\/a>&#8220;, and I&#8217;d recommend checking it out to learn more.<\/em><\/p>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"token-safety\">Token Safety<\/h3>\n\n\n<p>When it comes to an XSS attack, the ability to steal tokens \u2014 as in the <span class=\"popup-trigger popmake-1393\" data-popup-id=\"1393\" data-do-default=\"0\">ID Token<\/span> and, more importantly, the <span class=\"popup-trigger popmake-1400\" data-popup-id=\"1400\" data-do-default=\"0\">Access Token<\/span> and\/or <span class=\"popup-trigger popmake-4495\" data-popup-id=\"4495\" data-do-default=\"0\">Refresh Token<\/span> used when accessing <span class=\"popup-trigger popmake-2876\" data-popup-id=\"2876\" data-do-default=\"0\">APIs<\/span> \u2014 is arguably the most concerning issue. <\/p>\n\n\n\n<p>As described in my previous article entitled <em><a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/03\/07\/oidc-saml-and-oauth-2-0\/\" target=\"_blank\" rel=\"noopener\" title=\"OIDC, SAML and OAuth 2.0\">OIDC, SAML and OAuth 2.0<\/a><\/em>, the likes of <span class=\"popup-trigger popmake-407\" data-popup-id=\"407\" data-do-default=\"0\">OIDC<\/span> and <span class=\"popup-trigger popmake-467\" data-popup-id=\"467\" data-do-default=\"0\">OAuth 2.0<\/span> make use of these tokens as part of each protocol, and when used as intended, provide both Application developers and API developers with great power and flexibility when it comes to customer identity. However, with great power comes great responsibility&#8230;.<\/p>\n\n\n\n<div class=\"wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-23441af8 wp-block-group-is-layout-flex\">\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-discover-ciam wp-block-embed-discover-ciam\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"WIv0454Mwa\"><a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/07\/06\/spotlight-on-the-ciam-token-storage-conundrum\/\">Spotlight on the CIAM Token Storage Conundrum<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;Spotlight on the CIAM Token Storage Conundrum&#8221; &#8212; Discover CIAM\" src=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/07\/06\/spotlight-on-the-ciam-token-storage-conundrum\/embed\/#?secret=eDmIFa5vAB#?secret=WIv0454Mwa\" data-secret=\"WIv0454Mwa\" width=\"500\" height=\"282\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n<\/div>\n\n\n\n<p>It&#8217;s hardly surprising, then, that stealing tokens has become the target for many of the <span class=\"popup-trigger popmake-1754\" data-popup-id=\"1754\" data-do-default=\"0\">bad actors<\/span> out there, making XSS still one of the top 3 attacks after more than two decades! For any application, the safe storage of tokens is of paramount importance, but for a SPA \u2014 which has a limited set of choices when it comes to secure storage \u2014 it&#8217;s even more of a challenge. You can read more about token storage and the various options available in my recent article:<\/p>\n\n\n\n<p>With a Single Page Application, the malicious acquisition of an ID Token has the potential for leaking Personally Identifiable Information (<span class=\"popup-trigger popmake-2915\" data-popup-id=\"2915\" data-do-default=\"0\">PII<\/span>) \u2014 something which could end up being costly from a regulatory compliance perspective, namely <span class=\"popup-trigger popmake-399\" data-popup-id=\"399\" data-do-default=\"0\">GDPR<\/span>, the CCPA or the like. <\/p>\n\n\n\n<p>With Access Tokens \u2014 and more importantly, Refresh Tokens (used extensively if you follow the best practice guidance of &#8220;Prefer short-lived tokens&#8221;) \u2014 then stolen tokens are even more of a concern. Particularly if you also observe the guidance &#8220;Follow the <span class=\"popup-trigger popmake-4375\" data-popup-id=\"4375\" data-do-default=\"0\">Principle of Least Privilege<\/span>&#8221; \u2014 which typically means that your SPA will be using <strong>more than one<\/strong> Access Token and correspondingly more than one Refresh Token! Stolen OAuth 2.0 tokens mean the potential for far more than just the loss of PII! \ud83d\ude33<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-12579978fcc31c526769ef53998356b3 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>A Refresh Token and Access Token invariably come as a pair (which can also include the ID Token, depending on the CIAM implementation being used). Following the Principle of Least Privilege, an application can conceivably end up in possession of multiple token sets, each of which needs to be handled in a safe and secure manner. <\/em><\/p>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"resource-security\">Resource Security<\/h3>\n\n\n<p>Now you may be thinking that following the best practice of &#8220;Prefer short-lived tokens&#8221; and observing the guidance to &#8220;Follow the Principle of Least Privilege&#8221; is simply creating unnecessary problems. And, you could be forgiven for thinking that too: just like the claim that &#8220;time travel is now simply an engineering problem&#8221; is not a statement that either helps or informs physical engineering implementation, neither of the aforementioned addresses the challenges from the perspective of a software engineering standpoint. <\/p>\n\n\n\n<p>However, they do make sense from the perspective of resource security, and one can&#8217;t simply ignore them (or at least will do so at their own peril). In a recent post, Andrea Chiarelli \u2014 an ex-colleague of mine \u2014 neatly describes why both practices are important when it comes to protecting resources:<\/p>\n\n\n\n<div class=\"wp-block-group has-text-align-center has-global-padding is-layout-constrained wp-container-core-group-is-layout-7db9d80f wp-block-group-is-layout-constrained\" style=\"padding-right:0;padding-left:0\">\n<iframe loading=\"lazy\" src=\"https:\/\/www.linkedin.com\/embed\/feed\/update\/urn:li:share:7351644923768516610?collapsed=1\" height=\"699\" width=\"95%\" frameborder=\"0\" allowfullscreen=\"\" title=\"Embedded post\"><\/iframe>\n<\/div>\n\n\n\n<p>Taking into consideration one of the key points of a SPA being that page refresh is kept to a minimum, we somehow need to store tokens \u2014 and tokens in the plural because there&#8217;s likely to be more than one \u2014 so that we don&#8217;t have to continuously redirect back to the CIAM provider in order to obtain new ones. And that brings us back to the advice, &#8220;You&#8217;re vulnerable to XSS attacks if you keep tokens in Local Storage!&#8221; <\/p>\n\n\n\n<p>If you haven&#8217;t already, you can probably now see where I&#8217;m coming from when I say it&#8217;s a challenge for most software engineering folks to know exactly what to do for the best. And, more importantly, how to do it! When it comes to Customer Identity, a lot of the guidance from subject matter experts can seem tautological and contradictory.<\/p>\n\n\n<h3 class=\"wp-block-heading\" id=\"user-experience\">User Experience<\/h3>\n\n\n<p>There&#8217;s also the user experience to consider. Poor UX designs when it comes to <a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/authorize\/access-control\/\" target=\"_blank\" rel=\"noopener\" title=\"Access Control\">Access Control<\/a> can often leave users frustrated, to say the least. Examples will often show single-shot operations \u2014 like clicking a &#8220;Delete&#8221; button, say \u2014 being declined as the API rejects the request due to insufficient privilege at the point at which the user performs the operation. Arguably, that&#8217;s OK. However, if I&#8217;ve filled in a whole form of data just to be told &#8220;sorry, you can&#8217;t do that&#8221; when I click &#8220;Submit&#8221;, I&#8217;m going to be pretty annoyed \ud83d\ude20<\/p>\n\n\n<h2 class=\"wp-block-heading\" id=\"better-protection-via-the-bff-pattern\">Better Protection via the BFF Pattern<\/h2>\n\n\n<p>One architectural strategy that has gained prominence to at least improve the security posture of OAuth token handling is the&nbsp;<strong>Backend-For-Frontend (BFF)<\/strong>&nbsp;pattern; you can read about it in detail <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-ietf-oauth-browser-based-apps#name-backend-for-frontend-bff\" target=\"_blank\" rel=\"noopener\" title=\"\">here<\/a> in the IETF draft entitled <em><a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-ietf-oauth-browser-based-apps\" target=\"_blank\" rel=\"noopener\" title=\"\">OAuth 2.0 for Browser-Based Applications<\/a><\/em>. <\/p>\n\n\n\n<p>As described by Sam Newman in the article <a href=\"https:\/\/samnewman.io\/patterns\/architectural\/bff\/\" target=\"_blank\" rel=\"noopener\" title=\"\">here<\/a>, BFF was a pattern originally designed to make it easier when adapting APIs for use by multiple clients, but in recent years has been somewhat repurposed for Customer Identity. Essentially, this is an architecture pattern in which a server-side component, the BFF, is specifically designed as part of the frontend application and implemented to securely proxy all API calls:<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-47afc5c6dcbe8365cc555ad821c70f98 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>To avoid confusion with other architectural concepts, such as API gateways and reverse proxies, etc., it is important to keep in mind that the BFF pattern is specifically designed as part of the application itself, effectively becoming the OAuth client for the frontend.<\/em><\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-group has-accent-5-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-b70568bb wp-block-group-is-layout-constrained\" style=\"padding-top:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50)\">\n<pre class=\"wp-block-code ciamcenter\"><code>SPA (React\/Vue\/Angular) &lt;---&gt; BFF (Node.js\/.NET\/etc) &lt;---&gt; OAuth2 Provider<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Redirects the user to the CIAM provider.<\/li>\n\n\n\n<li>Handles the redirect back with authorization code.<\/li>\n\n\n\n<li>Exchanges the code for access and refresh tokens.<\/li>\n\n\n\n<li>Stores tokens securely (e.g. Server Memory, Session, or Database).<\/li>\n\n\n\n<li>Provides authenticated user info to the SPA (e.g., via a <code>\/me<\/code> endpoint).<\/li>\n\n\n\n<li>Proxies authenticated API requests on behalf of the SPA using the stored Access\/Refresh Tokens.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code ciamcenter\"><code>SPA &#91;GET \/api\/data] &lt;--&gt; BFF &#91;GET \/api\/data] &lt;--&gt; Protected API &#91;GET \/api\/data] (using access token)<\/code><\/pre>\n<\/div>\n\n\n\n<p>A typical BFF would use <strong><code>HTTP-only<\/code>, <code>Secure<\/code>, <code>SameSite<\/code> cookies<\/strong> for session storage (though can use other mechanisms such as server-side storage), avoiding having to store tokens in <em><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\" target=\"_blank\" rel=\"noopener\" title=\"\">localStorage<\/a><\/em> or <em><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/sessionStorage\" target=\"_blank\" rel=\"noopener\" title=\"\">sessionStorage<\/a><\/em>, both of which are susceptible to XSS. <\/p>\n\n\n\n<p>A BFF is also typically hosted on the <strong>same domain<\/strong> as the application\/API, and will typically employ a rotational strategy when using <strong>Refresh Tokens<\/strong>; a BFF will typically request short-lived Access Tokens and then use a Refresh Token approach to renew them (thus avoiding any redirect to the CIAM provider). <\/p>\n\n\n\n<p>As with any CIAM integration, the use of TLS (<strong>HTTPS<\/strong>) would be used everywhere; all of this provides a number of benefits, including:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Token Isolation<\/strong>: Tokens never reside in the browser-based storage that&#8217;s susceptible to an XSS attack.<\/li>\n\n\n\n<li><strong>Secure Storage<\/strong>: Tokens are stored in secure backend storage or in <code>HttpOnly<\/code> cookies.<\/li>\n\n\n\n<li><strong>Centralised Token Management<\/strong>: Token refresh and rotation are handled on the server side<\/li>\n\n\n\n<li><strong>Session Control<\/strong>: Facilitates tighter control over user sessions, including logout and inactivity management.<\/li>\n<\/ul>\n\n\n\n<p>In a recent post, Kim Maida, Senior Director of Developer Relations over at <a href=\"https:\/\/fusionauth.io\/\" target=\"_blank\" rel=\"noopener\" title=\"\">FusionAuth<\/a>, shared details of a demo implementation for Backend-For-Frontend authentication and authorization, and the GitHub repo she mentions is available <a href=\"https:\/\/github.com\/kmaida\/auth-architecture\/tree\/main\" target=\"_blank\" rel=\"noopener\" title=\"\">here<\/a>. Kim does a really great job of discussing how to build a BFF using both a standards-based approach and also using FusionAuth&#8217;s own <a href=\"https:\/\/fusionauth.io\/docs\/apis\/hosted-backend\" title=\"\">Hosted Backend<\/a> technology; a great illustration of how a BFF pattern can be implemented in a way that can avoid any impact of potential vendor lock-in. <\/p>\n\n\n\n<div class=\"wp-block-group has-text-align-center has-global-padding is-layout-constrained wp-container-core-group-is-layout-7db9d80f wp-block-group-is-layout-constrained\" style=\"padding-right:0;padding-left:0\">\n<iframe loading=\"lazy\" src=\"https:\/\/www.linkedin.com\/embed\/feed\/update\/urn:li:share:7345605532424028161?collapsed=1\" height=\"649\" width=\"95%\" frameborder=\"0\" allowfullscreen=\"\" title=\"Embedded post\"><\/iframe>\n<\/div>\n\n\n\n<p>I love this kind of &#8220;hands-on&#8221; approach as it provides explicit, concrete implementation that helps take the guesswork out of the engineering required. However, with my engineering hat on, there are a couple of observations I have when it comes to the BFF approach that aren&#8217;t criticisms per se, but predominantly hark back to that frustrating situation where, as an engineer, one is left to work things out for oneself:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>How does the pattern help me when it comes to structuring my UI in a way that reflects what a user is allowed to do? <\/li>\n\n\n\n<li>How does the pattern help me adhere to the Principle of Least Privilege?<\/li>\n\n\n\n<li>How do I integrate the BFF pattern whilst still using existing CIAM SDKs?<\/li>\n\n\n\n<li>How exactly is my BFF being protected? It is an API after all \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f<\/li>\n\n\n\n<li>Do I really have to proxy every single call to my own API? \ud83e\udd14<\/li>\n<\/ul>\n\n\n<h2 class=\"wp-block-heading\" id=\"the-acme-pattern-revolution-through-evolution\">The ACME Pattern: Revolution Through Evolution<\/h2>\n\n\n<p>The BFF pattern certainly provides a solution to some of the challenges engineers face, and with the kind of work done by folks like Kim, there&#8217;s collateral that can be leveraged to build implementation, meaning we&#8217;re not left guessing what we have to do. <\/p>\n\n\n\n<p>But it&#8217;s not the whole story; certainly, there are some gaps which I&#8217;m left to fill in myself, and for me, it doesn&#8217;t solve my requirements when it comes to Access Control (more on that <a href=\"#ace\" title=\"\">later<\/a>). Enter the ACME pattern.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"ACME In Action\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/HQEfggEDO3s?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>Now, ACME isn&#8217;t intended to be a replacement for the BFF pattern, or anything else for that matter. In fact, if you are building a BFF for what it was originally intended to provide (i.e. a pattern designed to make it easier when adapting APIs for use by multiple clients), then you can certainly incorporate ACME as part of the design. More than anything, the ACME pattern is designed to complement rather than replace.<\/p>\n\n\n\n<p>ACME \u2014 which stands for <strong>Access Control Mediation Endpoint<\/strong> \u2014 is an architectural pattern that leverages existing concepts from the BFF pattern, the <em><a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-ietf-oauth-browser-based-apps#name-token-mediating-backend\" title=\"\">Token-Mediating Backend<\/a><\/em> pattern, and a number of other sources, that essentially collapses more of the Auth responsibility into an API itself. <\/p>\n\n\n\n<p>ACME is a pattern I&#8217;m adopting as part of the <span class=\"popup-trigger popmake-418\" data-popup-id=\"418\" data-do-default=\"0\">B2B<\/span> SaaS solution I&#8217;m building (<em><a href=\"https:\/\/theatrical.com\" target=\"_blank\" rel=\"noopener\" title=\"\">TheatricalPA<\/a><\/em>; for vertical market theatrical production management), and I&#8217;ll happily claim the accolade of inventing it&#8230;which, in a way, I suppose is true, I have. However, think of it more as an approach that has evolved out of existing patterns like BFF et al, together with concepts borrowed from the likes of OAuth 2.0 itself. Let me elaborate.<\/p>\n\n\n<h3 class=\"wp-block-heading\" id=\"overview\">Overview<\/h3>\n\n\n<p>ACME is a pattern I&#8217;m going to discuss in more detail in future articles, and I&#8217;m even going to be building some code samples that will make it easier to adopt; it shouldn&#8217;t just be about what you could engineer, but what you can engineer and also how to engineer it! <\/p>\n\n\n\n<p>For now, though, let me start by providing an overview of how it works. The first part of the process involves the use of the standard Authorization Code Flow (with <span class=\"popup-trigger popmake-1895\" data-popup-id=\"1895\" data-do-default=\"0\">PKCE<\/span>, specifically as we&#8217;re in a SPA context), as illustrated in the diagram below:<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-297084d998b217d29d2af2a89a9b4a1c is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>At this point, it&#8217;s worth mentioning that, just like the BFF pattern, the ACME Pattern \u2014  or to give it its full title, the<strong> ACME Pattern for Resource Authorization<\/strong> \u2014 isn&#8217;t exclusively for use in a SPA. However, it does arguably provide the most benefit in a Single Page Application context, particularly from the perspective of token protection.<\/em><\/p>\n<\/div>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"665\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-1024x665.png\" alt=\"\" class=\"wp-image-1404\" style=\"width:1024px;height:auto\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-1024x665.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-300x195.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-768x499.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE.png 1444w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Image courtesy of <a href=\"https:\/\/cloudentity.com\/developers\/basics\/oauth-grant-types\/authorization-code-flow\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudentity<\/a><\/figcaption><\/figure>\n\n\n\n<ul class=\"wp-block-list\">\n<li>This is a well-known process that, in particular, will establish the identity of the user and will also establish an <span class=\"popup-trigger popmake-397\" data-popup-id=\"397\" data-do-default=\"0\">SSO<\/span> session with the CIAM provider. Additionally, this is the standard out-of-box process that is typically performed by most third-party CIAM provider SPA SDKs (such as <a style=\"font-style: italic;\" href=\"https:\/\/www.npmjs.com\/package\/keycloak-js\" target=\"_blank\" rel=\"noopener\" title=\"\">keycloak.js<\/a>, <em><a href=\"https:\/\/www.npmjs.com\/package\/@react-keycloak\/web\" target=\"_blank\" rel=\"noopener\" title=\"\">@react-keycloak\/web<\/a><\/em>, <a href=\"https:\/\/www.npmjs.com\/package\/@auth0\/auth0-spa-js\" target=\"_blank\" rel=\"noopener\" title=\"\"><em>auth0-spa-js<\/em><\/a>, <em><a href=\"https:\/\/www.npmjs.com\/package\/@auth0\/auth0-react\" target=\"_blank\" rel=\"noopener\" title=\"\">@auth0\/auth0-react<\/a><\/em>, <em><a href=\"https:\/\/www.npmjs.com\/package\/@fusionauth\/react-sdk\" target=\"_blank\" rel=\"noopener\" title=\"\">@fusionauth\/react-sdk<\/a><\/em>, etc).<\/li>\n\n\n\n<li>As part of the standard out-of-box process, we&#8217;re going to be requesting an Access Token with a single scope that we&#8217;ll refer to as <code>endpoint:mediate<\/code> (the name is immaterial, but using the convention of <em>resource:action<\/em>, this seems a fitting choice given the part it will play). Depending on the CIAM provider you&#8217;re integrating with, this will likely be defined as a custom scope, and the use of a custom <code>audience<\/code> may also be required.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An SDK will typically establish an application session context using the returned ID Token, and the process of doing so should follow best practice guidance. Arguably, it isn&#8217;t always the case that best practice guidance is observed by the SDK vendor, and there&#8217;ve been some interesting differences of opinion regarding this in recent times (see the thread <a href=\"https:\/\/twitter.com\/tweetsbycolin\/status\/1868044046381568275\" title=\"\">here<\/a> for an example).\n<ul class=\"wp-block-list\">\n<li>If you are in doubt as to whether the SDK offered by your CIAM provider gives you the best protection, there is an alternative you can adopt using the ACME pattern. It largely borrows from the aforementioned BFF pattern <code>\/me<\/code> endpoint functionality, and I&#8217;ll be discussing it in a future article.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>The Access Token returned will be used to call your API (step 10 onwards in the diagram above). Now, the eagle-eyed amongst you may already be thinking &#8220;&#8230;.hang on, but I&#8217;ve not specified the scope(s) I need in order to make the Access Token usable by my API \ud83e\udd14&#8230;&#8221; That&#8217;s correct. What we have at the moment is an Access Token that will be used to <em>facilitate<\/em> API calls, but it doesn&#8217;t actually &#8220;carry&#8221; the security that the resource server will typically require. This makes the Access Token fairly inert from a security perspective, so it&#8217;s relatively innocuous, which, in turn, makes the storage of it relatively straightforward.\n<ul class=\"wp-block-list\">\n<li>Most third-party provider SDKs \u2014 e.g. <em>@react-keycloak\/web<\/em> \u2014 will likely store the Access Token in memory, but <em>localStorage<\/em> or even <em>sessionStorage<\/em> should be OK too. As the token is not directly valuable from a resource perspective, should it be leaked through something like an XSS attack, a bad actor cannot successfully use it to directly access resources.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>For each &#8220;page&#8221; rendered by the application, a <code>GET<\/code> API call will first be made to an <code>\/authorize<\/code> endpoint associated with the accompanying API route hierarchy (or hierarchies if there&#8217;s more than one). This endpoint is what I refer to as the <strong>API Route Authority<\/strong> (or Route Authority for short), and again, the name of the endpoint is fairly immaterial. I&#8217;ve used <code>\/authorize<\/code> because it makes sense in the context of what the endpoint actually does. For example, in the B2B SaaS I&#8217;m building, I have the notion of a <em>Production<\/em> and various aspects associated with it, so I have the corresponding associations:\n<ul class=\"wp-block-list\">\n<li>Route(s) in my application that refer to discrete <em>Production<\/em> functionality \u2014 for example, the page which surfaces the UX for assembling a team of production participants \u2014 and as such, I include a call executed as part of the page&#8217;s <code>useEffect<\/code> that makes the initial <code>GET<\/code> to the corresponding API route authority.<\/li>\n\n\n\n<li>An API route hierarchy associated with <em>Production<\/em> functionality, as, essentially, a collection of API routes and sub-routes:\n<ul class=\"wp-block-list\">\n<li><code>\/production\/authorize<\/code> &#8211; the production route authority<\/li>\n\n\n\n<li><code>\/production\/<em>id<\/em><\/code> &#8211; to manage a specific production with the <code><em>id<\/em><\/code> identifier<\/li>\n\n\n\n<li><code>\/production\/<em>id<\/em>\/team<\/code> &#8211; to manage a team associated with production <code><em>id<\/em><\/code><\/li>\n\n\n\n<li><code>\/production\/<em>id<\/em>\/schedule<\/code> &#8211; to manage a schedule associated with production <code><em>id<\/em><\/code><\/li>\n\n\n\n<li>etc.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n<h4 class=\"wp-block-heading\" id=\"route-authority\">Route Authority<\/h4>\n\n\n<p>Now, you may already be asking yourself why there is potentially more than one <code>\/authorize<\/code> endpoint implemented as part of the ACME pattern? As I say, each <code>\/authorize<\/code> endpoint \u2014 each Route Authority \u2014 is associated with an API route hierarchy, making it possible to define discrete areas of discrete functionality and thus satisfy the &#8220;Principle of Least Privilege&#8221; recommended best practice: the Access Token used for <em>Production<\/em> functionality doesn&#8217;t need to carry the scopes required for <em>Publicity<\/em> or <em>Rehearsal<\/em> management, say, and vice versa.   <\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-24f12990b623879c8ff1fa3b45d50ae6 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Implementing Route Authority also makes you consider the nature of your API and helps focus the development of endpoints in a way that makes logical sense.<\/em><\/p>\n<\/div>\n\n\n\n<p>The Route Authority also kind of echoes the <code>\/authorize<\/code> endpoint provided by the authorization server (as illustrated in the above diagram and as described in the <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc6749#section-4.1.1\" target=\"_blank\" rel=\"noopener\" title=\"\">OAuth 2.0 spec<\/a>), save for the following fundamental differences:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Your API will typically surface more than one <code>\/authorize<\/code> endpoint (though not specifically required),<\/li>\n\n\n\n<li>The only supported parameters are <code>scope<\/code> and <code>redirectUri<\/code>, and<\/li>\n\n\n\n<li>Each instance is multi-functional:\n<ul class=\"wp-block-list\">\n<li>Via an HTTP redirect, mediation can be triggered, which can perform what I&#8217;ll refer to as <em>Bearer Token elevation<\/em>; <a href=\"#abe\" title=\"\">more on that later<\/a>.<\/li>\n\n\n\n<li>Via an HTTP <code>GET<\/code>, that includes a Bearer Token possessing the <code>endpoint:mediate<\/code> scope, a list of available\/required permissions can be obtained.\n<ul class=\"wp-block-list\">\n<li>A <code>GET<\/code> API call to an <code>\/authorize<\/code> endpoint, with a supplied <code>scope<\/code> parameter, can return a list of permissions which can be used to inform the UX as described in the <em><a href=\"#ace\" title=\"\">Access Control Enablement<\/a><\/em> section below.<\/li>\n\n\n\n<li>A <code>GET<\/code> API call to an <code>\/authorize<\/code> endpoint, with a supplied <code>scope<\/code> parameter, can return HTTP <code>401<\/code> (Unauthorised), meaning the user represented by the Bearer Token does not have the desired access.<\/li>\n\n\n\n<li>A <code>GET<\/code> API call to an <code>\/authorize<\/code> endpoint, with a supplied <code>scope<\/code> parameter, can return HTTP <code>401<\/code> (Unauthorised) with a specific reason code. The reason code can essentially be anything, but again, using accepted convention based on its use, let&#8217;s go with <code>\"Mediation Advised\"<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n<h4 class=\"wp-block-heading\" id=\"medad\">Mediation Advisory<\/h4>\n\n\n<p>For now, let&#8217;s focus on the latter. On receipt of an HTTP <code>401<\/code> with reason code <code>\"Mediation Advised\"<\/code>, the application (i.e. the SPA in this case) would redirect to the same route authority (<code>\/authorize<\/code>) endpoint. To start with, the redirect is essentially going to trigger first-factor authentication, via whatever CIAM solution is employed; this should effectively be &#8220;silent&#8221; due to the SSO session already established.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-70fd89b84214cdfb93b1f0ae23b42ca9 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Actually, if the SSO session has terminated, then interactive first-factor authentication will be triggered, which automatically handles the case where a user needs to re-enter their credentials because of session expiry. In fact, in a similar fashion, this also automatically handles the case where step-up authentication is required, too.  <\/em><\/p>\n<\/div>\n\n\n\n<p>The redirect is also going to return an Access Token containing the necessary scopes, together with a corresponding Refresh Token. This shares similarities with the <em><a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-ietf-oauth-browser-based-apps#name-token-mediating-backend\" target=\"_blank\" rel=\"noopener\" title=\"\">Token-Mediating Backend<\/a><\/em> pattern <strong>except<\/strong> that (as with the BFF pattern) neither the Access Token nor the Refresh Token is returned to the client application. <\/p>\n\n\n\n<p>Instead, the route authority associates both tokens with the API route it services. There are various ways to do this, but the simplest and most effective way is, by default, to create an <code>HttpOnly<\/code> cookie \u2014 or, rather, two cookies, one for each token \u2014 setting the <code>Path<\/code> for each to the base of the route hierarchy.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-a60a028f87ec87bf19f87135b343b6b0 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc6265#section-5.1.4\" target=\"_blank\" rel=\"noopener\" title=\"\">Section 5.1.4 of RFC 6265<\/a> (HTTP State Management Mechanism) describes Cookie Path and path matching, and how a subordinate path (route) can access a cookie and under what circumstances.<\/em><\/p>\n<\/div>\n\n\n\n<p>Now the API route authority can essentially use any secure storage mechanism, but cookies \u2014 specifically <code>HTTPOnly<\/code> cookies \u2014 are really useful as, for a SPA, the browser will typically take care of things like cookie transport and cookie cleanup without further intervention by an API or application. There are some specific considerations regarding these cookies, and I&#8217;ll go into more details regarding these in the future; suffice it to say that each cookie will use the <code>HttpOnly<\/code>, <code>Secure<\/code>, <code>Path<\/code> and <code>SameSite<\/code> attributes.<\/p>\n\n\n\n<p>Finally, the API authority will redirect via the supplied <code>redirectUri<\/code> to the application. For simplicity&#8217;s sake, I haven&#8217;t implemented <strong>CORS<\/strong> as both my first-party application and my first-party API are surfaced on the same domain. However, there isn&#8217;t anything to stop <em>Cross-Origin Resource Sharing<\/em> from being implemented, and this is probably something I&#8217;ll discuss more in a future article.<\/p>\n\n\n\n<p>Execution continues, and with control now handed back to the application \u2014 i.e. the SPA in this case \u2014 page load again causes the <code>GET<\/code> API call to be made to the <code>\/authorize<\/code> endpoint associated with the accompanying API route hierarchy. This time, it returns the list of available permissions for the user. The UX can be rendered accordingly, and calls to the API can be made as the user interacts with the UI. <\/p>\n\n\n<h4 class=\"wp-block-heading\" id=\"abe\">Bearer Token Elevation<\/h4>\n\n\n<p>So what about API calls? Well, remember, at this point, the application holds the one and only innocuous Access Token with <code>endpoint:mediate<\/code> scope, so that gets used as part of each API call (set as an <code>Authorization Bearer<\/code> header in the usual way). Nothing out of the ordinary so far. The novelty comes in how each API endpoint is implemented to handle the bearer token supplied.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-4c6706f6c42b7ac0de84938cf98e00f5 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Unlike with the BFF pattern, ACME is designed so that the API participates directly without the need for any additional overhead introduced by using a BFF proxy. <\/em><\/p>\n<\/div>\n\n\n\n<p>If an API endpoint receives a Bearer Token with the appropriate scope(s), then it operates in accordance with the standard best practices associated with the OAuth 2 protocol. If the Bearer Token an API endpoint receives does not contain the appropriate scope(s), but does have the <code>endpoint:mediate<\/code> scope, then a sequence is performed (ideally via some implemented middleware) in what I like to call <em>Bearer Token Elevation<\/em>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The API will attempt to locate the corresponding (elevated) token generated as part of the advised mediation discussed <a href=\"#medad\" title=\"\">above<\/a>. If you&#8217;re using <code>HttpOnly<\/code> cookie storage, then the API will attempt to retrieve\/use the Access Token from the appropriate cookie, using the token received in the <code>Authorization Bearer<\/code> header as a guide.<\/li>\n\n\n\n<li>If no Access Token is found, or the Access Token has expired, but there is a corresponding Refresh Token, then the API will attempt to generate a new Access Token via a call to the integrated CIAM provider, and use that.\n<ul class=\"wp-block-list\">\n<li>If the Refresh Token has been revoked, then the API will return with a <code>401<\/code> and typically with <code>\"Mediation Advised\"<\/code> as the reason code. <\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>With the elevated token, if the user cannot perform the requisite action due to a lack of permission, then a standard <code>401<\/code> will be returned.<\/li>\n\n\n\n<li>With the elevated token, if the user can perform the action then the API will execute as normal.    <\/li>\n<\/ul>\n\n\n\n<p>By incorporating the use of standard <code>Authorization Bearer<\/code> header workflow with token elevation, the API can be securely accessed and presented with enough information to also securely enable the additional (elevation) processing. It also means that if your CIAM provider supports the likes of Token Binding (<a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8471\" target=\"_blank\" rel=\"noopener\" title=\"\">RFC 8471<\/a> \/ <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8473\" target=\"_blank\" rel=\"noopener\" title=\"\">RFC 8473<\/a>), then you can extend an ACME implementation to make use of that too; particularly useful if you also want to support CORS (directly) from your API. <\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-25b56a4c21529476aebaecac575760ad is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Whilst the specification for OAuth 2.0 (<a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc6749\" title=\"\">RFC 6749<\/a>) describes Bearer Token usage (<a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc6750\" title=\"\">RFC 6750<\/a>), neither spec mandates that the Bearer Token ultimately used by a resource server must be directly obtained from an HTTP  <code>Authorization Bearer<\/code> header. <\/em><\/p>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"ace\">Access Control Enablement<\/h3>\n\n\n<p>Aside from how easy it lets you observe the recommended best practices advised by CIAM specialists \u2014 particularly when building a SPA \u2014 the enhanced security it automatically provides, and the reduction in overhead compared to implementing the BFF pattern, the ACME pattern facilitates access control enablement in a way that is easy to implement and easy to manage across all first-party applications using your API. <\/p>\n\n\n\n<p>Adopting the ACME pattern also opens the door to integrating the fine-grained access control mechanisms provided by the likes of <a href=\"https:\/\/openfga.dev\" target=\"_blank\" rel=\"noopener\" title=\"\">OpenFGA<\/a>, <a href=\"https:\/\/www.permit.io\" title=\"\">Permit.io<\/a>, et al, all with minimal additional impact to application or API design. I&#8217;ll be sharing more on that in future articles.<\/p>\n\n\n<h3 class=\"wp-block-heading\" id=\"ultimate-protection\">Ultimate Protection<\/h3>\n\n\n<p>Ultimate may be a strong word, but with ACME, we satisfy the best practice recommendations, which typically state: &#8220;You&#8217;re vulnerable to XSS attacks if you keep tokens in Local Storage!&#8221;, &#8220;Prefer short-lived tokens&#8221;, &#8220;Use a <span class=\"popup-trigger popmake-2873\" data-popup-id=\"2873\" data-do-default=\"0\">BFF<\/span>&#8220;, <strong>and<\/strong> &#8220;Follow the <span class=\"popup-trigger popmake-4375\" data-popup-id=\"4375\" data-do-default=\"0\">Principle of Least Privilege<\/span>&#8220;. <\/p>\n\n\n\n<p>Whilst not a BFF exactly, ACME does serve as a secure intermediary, particularly for Single Page Applications, and unlike a BFF, it doesn&#8217;t require the overhead of proxying <em>every<\/em> API call. Further, ACME opens the door to integrating the fine-grained access control mechanisms with minimal additional impact on application or API design. I&#8217;m even starting to think about how ACME can help with AI implementations, particularly when it comes to the likes of MCP integration.<\/p>\n\n\n\n<p>So, perhaps &#8220;ultimate&#8221; isn&#8217;t such a strong word after all \ud83d\ude09 Anyway, I hope you&#8217;ve enjoyed this article, and please feel free to reach out with your thoughts and let me know what you think. Bye for now \ud83d\ude0e<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Whilst the SPA paradigm has grown in prominence over the years, little has changed in the guidance provided for integrating Customer Identity. With the necessity to provide a compelling UX, whilst protecting from attacks like XSS, this article introduces the ACME pattern and how it has been designed to tackle the challenge.<\/p>\n","protected":false},"author":1,"featured_media":4670,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"authenticate":"","authentication":"","authenticatedMethod":"","authenticatedMember":"","authorizedPermissions":[],"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_links_to":"","_links_to_target":""},"categories":[14,8,7],"tags":[83,22,20,84],"class_list":["post-4572","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-authentication","category-authorization","category-integration","tag-authorization","tag-ciam","tag-oauth2","tag-spa"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/08\/10123016\/create-a-highly-detailed-high-resolution-image-that-visually-represents-the-1.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts\/4572","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/comments?post=4572"}],"version-history":[{"count":105,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts\/4572\/revisions"}],"predecessor-version":[{"id":5630,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts\/4572\/revisions\/5630"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/media\/4670"}],"wp:attachment":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/media?parent=4572"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/categories?post=4572"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/tags?post=4572"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}