Lately the internet has been abuzz about micro-frontends. Micro-frontends are a great pattern for enabling teams to deliver customer facing value quickly and independently. However like any choice in software development the benefits also come with some cost. By breaking up the application sharing state becomes harder, and that includes sharing authentication. In this post I'll have a look at a way to have different components know if a user is authenticated.
First a super fast overview of what a micro-frontend is.
Micro-frontends
With a traditional application the monolith frontend dominated, even as back end services were moving to micro-service architectures. This architecture presents a single application to the world which consumes one or more back end services.
Micro-frontends are an approach to front end architecture where the client facing Single Page Application is broken down into multiple code bases rather than being one large code base. From the users perspective the multiple applications are presented as a single cohesive experience even though they are moving between
multiple code bases.
This has a range of benefits, including allowing teams to work on a vertical slice of the application rather than having one team own the entire frontend or having multiple teams contributing to one code base. Micro-frontends are typically combined with micro-service backend architectures and allows teams to scale and make decisions independently. Because each micro-frontend is a separate code base they are not required to share the same technology stack or front end architecture. One may be a JAM Stack SPA written in Vue.js, while another may be a server rendered application written in React and Next.js while yet another may be a Ruby on Rails application. The underlying technology doesn't matter as long as a user can move between them seamlessly. There are multiple ways of knitting together these frontends into a single user facing experience. The simplest of them is to use a reverse proxy to direct the traffic to different applications based on the path that a users is accessing.
Authentication
Single frontend
In a monolith frontend application the front end takes the users credentials and sends them to a server that is able to validate the credentials and returns an authentication token if they're correct. The front end is then able to use this authentication to prove it's identity to the APIs it's consuming.
Micro-frontend
When moving to a micro-frontend architecture, with each of the frontends being their own application there is no shared state that can be used to store authentication tokens once the user has logged into the first application. Our goal is to have each of these application acting as though they are one application to the user so we don't want each of the front ends to require that the user enters when they move between the frontends.
Sharing authentication
The way that this can be overcome is by using the Open ID Connect protocol and introducing an application that is responsible for handling the authentication running on a separate domain. If your app is running on my.app.com
then this authentication application might be hosted at id.my.app.com
. When a frontend wants to authenticate a user it redirects the user out to this application. The first time the user arrives on this page they are presented with a credential entry screen and go through a standard authentication process. When the process is completed they are redirected back to the original location with an access token, identity token and optional refresh token in the query. Before this redirect though the authentication application bakes a cookie that is stored on the domain. Now when a subsequent front end redirects the user back the authentication application checks this cookie and uses it to assert that the user has previously proved their identity and the user is redirected back without being forced to prove their identity a 2nd time.
Silent authentication
Sharing authentication through redirecting users to a central location solves the problem of being able to share the one authentication session between applications without forcing the user to log in. Unfortunately it can be a jolting experience for the users. Redirects aren't free even if the user is on a fast internet connection they are going to notice the constant redirecting. The solution to this is to do the authentication in a way that is hidden from the user. One method of approaching this is to perform the authentication exchange inside a hidden iframe. In this solution the application uses JavaScript to add a 1 pixel iframe into the DOM that handles the authentication experience and passes the resulting tokens back using a window.postMessage
call]. If the silent authentication fails the message instead informs the application that a login is required and the user can be redirected to provide their credentials.
Making it easier
If you're like me that all sounds too complicated to implement myself. I'm bound to muck it up somewhere, fortunately if you're using Auth0 the SPA SDK take care of everything for you. It provides options for authenticating your users using silent authentication, redirecting them to a hosted login or using a popup window so that your micro frontend can maintain its current state.
Top comments (12)
nice blog! I wonder how one could manage Auth0 token lifecycle with micro-frontends. I am building a React SPA which has a container that contain other micro-frontends and am considering the PKCE flow. What do you think would be the most effective approach to share the authentication? I think that one approach is to inject the token to micro-frontends by the container (that handles authentication) during initialization and another approach is to share the token via the cookie.
It sounds like you're achieving micro frontends by joining together multiple React components/apps either at runtime or at compile time. In that case you should be able to have your authentication done at a higher level than these app then either inject it as you say, or provide it through a context provider. I would consider abstracting it away entirely and providing your components with an authenticated fetch api where the abstraction handles attaching authentication to requests and handling token lifecycles. That api could then be injected or provided. A caveat on that is that I haven't thought deeply on potential edge cases.
Very interesting, however is the workaround with the 1px hidden iframe really the best practice?
To my understanding is that the 1px iframe method is the best way to get fresh access, identity and rotating refresh tokens (if required / supported) without having your user see the redirects that are happening as they move between multiple applications that are masquerading as a single user experience.
This method is for micro front ends, where multiple distinct front + back end stacks work as a single seamless experience for a user. For different application architectures I would consider different implementations. For instance I wouldn't use it if I had a single server that multiple front ends communicated with a single back end I would consider HTTPOnly cookies.
As far as I know, hidden iframes don't work in Safari and support in Chrome is also scheduled to be discontinued.
Interesting, thanks for letting me know. Do you have any links so I can read more?
Sorry, I was a bit imprecise here. With Safari and ITP, cookies in cross-origin iframes are blocked. Chromium plans to discontinue third-party cookies as well (blog.chromium.org/2020/01/building...)
Thanks for the info. My reading of this is we should be able to continue using iframes for authentication as long as we keep our cookies as first party cookies by having our authentication server on the same domain as the website the user is using.
In the short term the cookies will continue to work even across domains as long as the authentication server is setting a same site lax cookie.
Thanks again for responding with that info.
And "same domain" means the exact same domain, right?
Auth server on auth.acme.com and application on app.acme.com wouldn't work.
My understanding is that depends on where the cookie is set,
auth.acme.com
could set a cookie onauth.acme.com
in which case it would not be 1st party onapp.acme.com
but if it was set set it on the root domainacme.com
then it would be accessible on all subdomains ofacme.com
includingapp.acme.com
.this sounds like google OAuth, the idea of redirecting the user out somewhere to be authenticated, but I am actually trying to put this together right now with a react microfrontend not built with CRA and an Express API and I am hitting a wall where the proxy is not working, I get a 404 on that localhost:5000/auth/google proxy. Anything a bit more involved than what you have here on how to resolve that?
Hi Daniel, good pickup. This is based on Open ID Connect (OIDC) which is built on top of the OAuth2 psec.
Sorry I don't think I can help you without seeing the code on this one. Best of luck figuring it out.