Authentication and Authorization in Microfrontends
A Product Does Not Need a Separate Login for Every Remote
Section titled “A Product Does Not Need a Separate Login for Every Remote”Microfrontends separate responsibilities. Teams can develop, test, and ship product areas independently. That does not mean, however, that every technically independent unit also has to implement every cross-cutting capability on its own.
Authentication makes this especially visible.
A product may consist of one host and ten dynamically loaded remotes. If every remote authenticated the user on its own, the result would be ten integrations with the identity server, ten login lifecycles, and ten places where tokens are stored, renewed, and discarded. The supposed autonomy would mainly multiply the same technical responsibility.
A product with ten remotes does not need ten logins.
For mounted microfrontends, a different model makes more sense:
Not every remote authenticates the user. The application authenticates the user once.
The platform owns the login lifecycle and establishes the application’s authentication context. The remotes consume that context through a small contract. They do not need to understand the OpenID Connect flow or manage tokens themselves.
Protected backends have a different responsibility. They decide whether a concrete action on a concrete resource is allowed. A remote may adapt the UI to known UI capabilities. It cannot, however, replace effective authorization.
That creates three clearly separated responsibilities:
OpenID Provider└── authenticates the person
Host / Platform└── owns the authentication context
Resource Server└── authorizes access to protected resourcesThis separation matters more than the specific OIDC library, the frontend framework in use, or whether the application organizes its API access through a BFF or directly from the browser.
The Few OIDC Terms That Actually Matter
Section titled “The Few OIDC Terms That Actually Matter”OpenID Connect uses a number of standardized terms. For the architecture model of a microfrontend application, a small subset is enough.
| Term | Meaning in the model described here |
|---|---|
| End-User | The person using the product and authenticating. |
| User-Agent | The program through which the end-user acts. In this case, that is the browser. |
| Client / Relying Party | The application that needs a login and trusts the result returned by the OpenID Provider. |
| OpenID Provider | The identity server that authenticates the person. |
| Authorization Endpoint | The entry point of the login flow at the OpenID Provider. |
| Redirect URI | The registered address to which the browser returns after login. |
| Authorization Code | A short-lived intermediate result of a successful login flow. |
| ID Token | A verifiable statement from the OpenID Provider about the completed authentication. |
| Access Token | A credential intended for access to a protected resource. |
| Resource Server | The protected API that verifies the access token intended for it and the domain authorization of the access. |
What matters first is the role of the client. In a purely browser-based application, the host can implement the OIDC client. In a BFF architecture, the actual protocol handling may live on the server side.
For the remotes, that technical variant changes nothing: they are parts of the same application and do not automatically become additional clients with respect to the OpenID Provider.
One Login, One Host, Many Remotes
Section titled “One Login, One Host, Many Remotes”The basic login flow does not have to be repeated for every remote. It happens at the boundary between the application and the OpenID Provider.
1. The browser opens the application.2. The platform detects that no valid authentication context exists yet.3. The application's auth layer starts the OIDC flow and redirects the browser to the OpenID Provider.4. The OpenID Provider authenticates the end-user.5. The browser returns to the Redirect URI with an Authorization Code.6. The application processes the result and establishes its authentication context.7. It then activates the protected areas of the product.Browser-based clients typically use the Authorization Code Flow with PKCE today. For the distribution of responsibilities between platform and remotes, however, the cryptographic details are not what matters.
The architecture model stays manageable:
End-User │ ▼Browser │ ▼Host / Platform ─────────► OpenID Provider │ │ establishes authentication context ▼Remotes │ ▼protected APIsThe platform determines whether a valid authentication context already exists. If not, it starts the login. After the successful return, its auth layer processes the result and makes the context needed for the product UI available.
A remote does not need to know the Authorization Endpoint or the Redirect URI. It does not need its own OIDC configuration and does not need to know how the Authorization Code is processed or how a session or token lifecycle is managed.
It only needs to know whether the user is authenticated and which pieces of information relevant to its UI are available.

A Deployment Unit Is Not Yet an OIDC Client
Section titled “A Deployment Unit Is Not Yet an OIDC Client”Microfrontends are often defined through their technical delivery model. A remote can be built and deployed independently. Its bundle may live on its own server or be served from its own domain.
That deployment autonomy says nothing, however, about who acts as the client with respect to the OpenID Provider.
A separate deployment unit does not make a remote its own OIDC client.
If the remote is loaded into a host and runs within that application’s context, it remains part of the application. It does not gain its own login lifecycle simply because its JavaScript was built separately. In this model, the remotes need neither their own redirect URIs nor their own login flows.
Independently navigable applications must be distinguished from that. They can each have their own client registrations and still benefit from the OpenID Provider’s SSO session. In that case, the person normally does not need to enter credentials again.
For the meta-model, this distinction is enough:
Client ID└── identifies an application
SSO session at the OpenID Provider└── recognizes an already authenticated end-userDeployment autonomy neither automatically creates a new OIDC client nor its own user session.
The Session Does Not Need to Be Shared Between Remotes
Section titled “The Session Does Not Need to Be Shared Between Remotes”In discussions about authentication in microfrontends, one sentence comes up again and again:
Then the remotes have to share the session.
That statement already starts from a problematic model. It assumes that each remote first owns its own session and then has to synchronize it with other remotes.
That leads to a picture like this:
Remote A owns a session ⇅Remote B owns a session ⇅Remote C owns a sessionFor mounted remotes, however, a different model is appropriate:
Application owns authentication context │ ├── Remote A uses it ├── Remote B uses it └── Remote C uses itA shared application session does not need to be distributed between remotes. It must be centrally owned and made usable through a clear contract.
Three levels have to be distinguished here.
The OpenID Provider may own its own SSO session. That allows it to recognize an already authenticated end-user during a later login process.
The application may additionally manage its own session or its own token lifecycle. How it does that technically is an internal architecture decision.
The remotes see only the authentication context they need for rendering and interaction.
A session is therefore not synchronized between remotes. It is owned by the application.
The Remote Needs Context, Not Tokens
Section titled “The Remote Needs Context, Not Tokens”A remote often needs to know whether a user is signed in. It may need a display name, a stable user identifier, or information about which functions should be offered in the UI.
That does not mean, however, that it needs access to ID tokens, access tokens, or the concrete OIDC client instance.
A framework-neutral platform contract can stay small:
interface AuthContext { status: 'anonymous' | 'authenticated';
user: { id: string; displayName: string; } | null;
uiCapabilities: readonly string[];
requestLogin(): void; requestLogout(): void;}status and user allow the remote to determine its visible state. An anonymous user may receive a login hint. An authenticated user sees the protected area of the product and their display name.
uiCapabilities help adapt the UI to generally available functions. They can hide navigation entries, disable actions, or avoid unnecessary requests. They are a projection for the UI, not a final authorization decision.
requestLogin() and requestLogout() express intents. A remote can request a login or trigger a logout. The actual execution is delegated to the platform.
The contract deliberately contains no raw tokens. It also contains no concrete OIDC library, no storage keys, no refresh callbacks, and no provider-specific data structures.
The platform provides a capability, not its internal authentication implementation.
That is the same boundary that also applies to other platform capabilities. A remote should be able to use navigation without taking ownership of the host’s router. It should be able to show notifications without owning their central infrastructure. In the same way, it should be able to react to the authentication context without becoming an OIDC client itself.
The remote needs the user context, not the token.
Claims Are Translated into a Product Contract
Section titled “Claims Are Translated into a Product Contract”After successful authentication, ID tokens or other responses from the identity provider may contain various claims. Claims are statements about the authenticated person or about the context of the authentication.
Technically, it would be easy to pass that structure in full to every remote. Architecturally, that is usually the wrong boundary.
OIDC Claims │ ▼Platform Adapter │ ▼stable AuthContextThe platform adapter translates technical and provider-specific information into the stable contract of the product.
A remote should therefore not depend directly on arbitrary role paths, group structures, or custom provider claims. Such fields couple the product UI to a specific identity provider and its current configuration. If the provider or authorization model changes, many remotes would otherwise have to be adapted.
Role names are not automatically suitable product contracts either. A technical role may bundle multiple domain meanings. Conversely, a UI capability may be derived from several roles, attributes, or domain rules.
The adapter encapsulates those differences:
provider-specific claims │ ▼product-oriented UI capabilitiesThat keeps the remotes independent of the concrete identity infrastructure. They receive only the information they actually need for their UI.
It also limits the distribution of identity data. Not every remote needs every claim about the user. A small contract makes visible which pieces of information are part of the shared product platform.
ID Token, Access Token, and JWT Are Not the Same Thing
Section titled “ID Token, Access Token, and JWT Are Not the Same Thing”In frontend projects, the terms ID token, access token, and JWT are often used almost synonymously. That blurs different responsibilities.
The ID token describes the completed authentication with respect to the client. It is a verifiable statement from the OpenID Provider about the fact that the end-user was authenticated and in what context.
The access token, by contrast, is intended for access to a protected resource. The resource server checks whether the credential is meant for it and whether it can be used for the requested access.
The ID token is therefore not automatically the credential for a domain API.
A JWT, in turn, is first of all a format in which structured statements can be transported. An ID token is commonly represented as a JWT. An access token may also be a JWT, but it does not have to be.
JWT describes a format. It does not describe the purpose of a token or a session architecture.
A string with three dot-separated parts is therefore not enough to derive a meaningful architecture. What matters is who issued the token, which audience it is intended for, what purpose it serves, and which component owns its lifecycle.
Why a Token in localStorage Is Not a Shared Login
Section titled “Why a Token in localStorage Is Not a Shared Login”A widespread model for browser-based applications looks roughly like this:
localStorage.setItem('token', accessToken);That makes the credential appear easy to access for all parts of the application. Every remote running in the same document can read it and add it to its requests.
That apparent simplicity is precisely the problem.
localStorage is reachable by JavaScript running in the current document. In mounted microfrontends, that code may come from several independently developed and delivered remotes. A bearer credential stored there becomes a globally reachable technical resource within the application.
That does not create a clear owner. Instead, it creates an implicit convention: many parts are allowed to read the credential, but responsibility for its lifecycle remains vague.
A JWT in localStorage is not a shared session. It is a credential reachable by JavaScript.
That observation is not a blanket statement that all browser-based authentication is invalid. It describes the architectural consequence of a freely accessible token store.
The credential becomes shared infrastructure for all remotes. Changes to its name, format, renewal, or expiration potentially affect the entire frontend. Remotes may make their own assumptions about its validity or accidentally log it, copy it, and store it in additional states.
A clean platform boundary enables protected calls without exposing the underlying credential to remotes as a global resource.
Tokens Do Not Belong in Global Events
Section titled “Tokens Do Not Belong in Global Events”The same mistake can occur in a more decoupled form. The host does not put the token into global browser storage, but distributes it through events:
Host└── publishes "token-changed"
Remote A stores the tokenRemote B stores the tokenRemote C stores the tokenFormally, the remotes are now separated from one another. In practice, however, they own copies of the same credential and must react to the same technical lifecycle.
What happens when the token expires? Which remote renews it? What happens when one remote processes an outdated event? How is it ensured that all copies are discarded after logout? Which component handles errors during renewal?
One owner becomes multiple owners. One lifecycle becomes several partially synchronized lifecycles.
In addition, horizontal coupling emerges through the global channel. Every remote has to know the token event and its payload. That binds the remotes directly to the platform’s authentication implementation.
Token sharing is often not a solution for microfrontends. It is a sign that the authentication responsibility has not been cut cleanly.

The better boundary looks like this:
Remote└── uses a platform or API contract
Platform└── manages session or credentialTokens should therefore not be distributed through global events, shared stores, or freely accessible browser objects. A global store merely changes the transport path. It does not answer the question of who is the responsible owner.
Protected API Calls as a Platform Capability
Section titled “Protected API Calls as a Platform Capability”A remote must be able to access protected APIs. For that, however, it does not need to own an access token itself.
The boundary may look like this:
Remote└── domain gateway └── authenticated transport adapter └── protected APIThe remote calls a domain function:
interface InvoiceGateway { approve(invoiceId: string): Promise<void>;}Internally, the implementation may use an application session, call a server-side BFF, attach an access token, or organize a necessary renewal. Those technical decisions remain hidden behind the gateway or transport adapter.
The remote should be able to execute a protected operation. It does not need to know which credential the platform uses for it.
A generic HTTP client may be useful in some products. A domain gateway often describes the required capability more precisely and prevents URL structures and authentication details from leaking into the presentation logic.
What matters is not the exact shape of the interface but the encapsulated responsibility. A remote may depend on protected communication. It should not depend on the application’s internal token management.
BFF or Browser Client Does Not Change the Remote Contract
Section titled “BFF or Browser Client Does Not Change the Remote Contract”The application can organize its session and API access in different ways.
One variant uses a server-side BFF. The browser communicates with that backend while the actual OIDC and token management remain encapsulated on the server side.
Another variant implements the OIDC client and the necessary access tokens centrally in the browser host.
Which variant is appropriate depends on the product’s constraints. This article does not need to decide that.
For the remotes, the difference should remain as invisible as possible.
Whether the application organizes its API access through a server-side session and a BFF or through a central token handler in the browser is a downstream architecture decision. For the remotes, the contract should not change.
In both cases, not every remote authenticates itself. In both cases, not every remote manages its own token lifecycle. And in both cases, a change of the authentication implementation should not require a migration of all product areas.
This is exactly where the quality of the platform contract becomes visible: it separates the required capability from its current technical implementation.
Authentication Is Not Authorization
Section titled “Authentication Is Not Authorization”Authentication and authorization answer different questions:
Authentication└── Who is the person?
Authorization└── May this person perform this specific action on this resource?The OpenID Provider authenticates the end-user. It confirms to the application that a specific identity has been successfully verified.
That still does not automatically decide whether that person may approve an invoice, delete a project, edit someone else’s record, or open a confidential report.
The application can use the authentication context to compose its UI sensibly. A remote can hide navigation, disable actions, and show understandable hints.
The final authorization decision, however, belongs in the protected API or the resource server.
A hidden menu item is not authorization.
The UI can be bypassed. Requests can be generated outside the intended interaction flow. A URL can be called directly, and an API can be addressed independently of the visible remote.
A remote that is not mounted does not prevent a direct API call.
That is why the resource server must check the identity, the requested action, the affected resource, and the domain context. Only it can effectively allow or deny access.
The identity provider may supply identity information, roles, or other characteristics. The domain decision still remains the responsibility of the protected resource. The identity provider does not automatically know all the rules of the domain model.
The UI May Help, but the Backend Must Decide
Section titled “The UI May Help, but the Backend Must Decide”UI capabilities in the authentication context are still useful. They improve the product experience and prevent users from repeatedly running into predictable errors.
The frontend can make known constraints visible:
Frontend├── shapes the allowed paths├── hides irrelevant functions├── shows understandable states└── prevents unnecessary usage errorsThe backend, by contrast, enforces the effective boundary:
Backend├── checks identity├── checks action and resource├── considers the domain context└── allows or denies accessA UI capability such as invoice:approve may signal to the remote that the approval button may generally be offered. Whether a specific invoice can actually be approved may additionally depend on its status, the responsible business area, an amount limit, or a prior approval.
That decision cannot be derived solely from a claim issued during login. It belongs to the protected operation.
The frontend shapes the allowed path. The backend enforces it.

The frontend thus provides the most helpful possible projection of permissions. It still has to cope with a rejected operation. Permissions and domain states can change without the browser’s authentication context being rebuilt immediately.
Login and Logout Remain a Platform Responsibility
Section titled “Login and Logout Remain a Platform Responsibility”A remote may trigger a login or logout intent. That still does not mean it implements the OIDC flow itself.
A public product area may, for example, detect that an action requires an authenticated user. It then calls auth.requestLogin(). The platform decides how the current state is preserved, which redirect URI is used, and how the OIDC flow is started.
Likewise, a remote can trigger auth.requestLogout(). The platform then clears its authentication context and performs the designated logout logic.
The remote only reacts to the visible state:
switch (auth.status) { case 'anonymous': return showLoginHint();
case 'authenticated': return showProtectedContent();}In a real application, an additional temporary loading or error state may also be useful. That should not turn into a separate authentication state machine inside every remote, however.
Expiration, renewal, and error handling of the central authentication context remain platform responsibilities. Otherwise, several remotes could start redirects simultaneously, request renewals, or log out the user based on different assumptions.
A session is not synchronized between remotes. It is owned by the application.
Dynamic Remotes Do Not Need a Real Login for Every Test
Section titled “Dynamic Remotes Do Not Need a Real Login for Every Test”Dynamic remotes should be developed and tested independently. That sometimes creates the demand that every remote must also implement a full OIDC login locally.
What is required, however, is only the contract, not the production implementation.
A mini-host can provide controlled authentication states:
Production└── real host provides the AuthContext
Local development└── mini-host provides defined test identitiesThat makes it possible to test different states deliberately:
- unauthenticated,
- normal user,
- user with certain UI capabilities,
- expired or unavailable context.
The mini-host does not need real production tokens for that. It can simulate a stable user context and predictable API responses.
The mini-host simulates the platform contract. It does not turn the remote into its own authentication system.
This style of development strengthens the autonomy of the remote without duplicating the platform responsibility. The team can test its UI independently and still does not have to maintain a second login architecture.
At the same time, the contract becomes explicit. A remote that only works with a concrete OIDC client instance or with a specific token in localStorage is not truly independent. It is only implicitly coupled to the production environment.
What Looks Like Autonomy Often Just Multiplies Infrastructure
Section titled “What Looks Like Autonomy Often Just Multiplies Infrastructure”If authentication is moved into every remote, that does not automatically create domain-autonomous product areas. It first creates several implementations of the same platform function.
Each team may choose its own OIDC library or a different version of the same library. Callback handling, token storage, renewal, and logout are implemented multiple times. Error states differ. Some remotes redirect to login immediately, others show a hint, and still others remain in an inconsistent loading state.
Coupling to the identity provider also grows. Provider-specific claims, configuration keys, and role structures spread throughout the product. A migration then no longer affects one platform component but many independently delivered remotes.
That additional technical freedom therefore leads to higher security, maintenance, and coordination costs.
What looks like additional autonomy of the remote often just multiplies the same infrastructure responsibility.
A central platform contract, by contrast, creates a visible responsibility. Login and logout behave consistently across the entire product. The identity provider can be replaced or connected differently without every remote having to know its internal structures.
The contracts become smaller. Tests become easier. Remotes can use defined identities without requiring production tokens. Users experience one application instead of a collection of competing login implementations.
Authentication autonomy in mounted remotes is rarely real domain autonomy. Most of the time, it is duplicated platform technology.
Authentication Is a Platform Capability
Section titled “Authentication Is a Platform Capability”Microfrontends are meant to separate domain responsibility. They are meant to enable teams to evolve product areas independently. That independence does not arise by implementing every global capability multiple times.
For mounted microfrontends, authentication belongs to the application or platform.
The platform owns the OIDC client responsibility and establishes the authentication context. In a purely browser-based implementation, the host may perform the protocol flow; in a BFF architecture, that responsibility may be implemented on the server side. For the remotes, the contract remains the same.
They receive a small, stable, and framework-neutral authentication context. They know the user and UI context, but normally not raw tokens, redirect URIs, or a concrete OIDC client instance.
A separate deployment unit does not make a remote its own OIDC client. Nor is a shared session a state that has to be distributed and synchronized between remotes.
A product with ten remotes does not need ten logins.
A session is not synchronized between remotes. It is owned by the application.
The remote needs the user context, not the token.
The UI can use UI capabilities to offer understandable paths and hide unavailable functions. The final decision about a protected operation, however, is made by the resource server.
The frontend shapes the allowed path. The backend enforces it.
Authentication is a platform capability. Authorization remains a responsibility of the protected resource.