Web development has seen a shift, a significant one. It’s moving towards a modular approach. Microservices, Microfrontends — It’s the era of independence and swift value delivery. But, it’s not all sunshine and rainbows. New challenges loom, especially in authentication.
Microfrontends, despite their many benefits, complicate the state sharing process. Authentication, for instance, is a complex task. This article explores that complexity. We will dive deep into two key strategies for handling authentication in a microfrontend environment - Backend for Frontend (BFF) Authorization and Frontend Side Silent Login.
Both strategies revolve around tokens and managing scopes. For instance, we know in short that scopes are space-delimited, case-sensitive strings. Each string represents a different access range. Think of an application requesting access to a user’s email and profile picture. Two strings. Two scopes.
OAuth 2.0 specification describes scope as a request parameter. The client specifies it when requesting access. The server uses it to inform the client of the access token’s scope. If the client omits scope, the server uses a default value or fails the request.
Remember, scopes and internal permissions of an API are not the same — scopes limit what an application can do within the user’s capabilities.
During authorization, the application’s requested scopes are shown to the user. This informs the user what permissions they are granting to the application.
For instance, an application might request “email” and “profile” scopes. This is expressed as a space-delimited string in the scope parameter of the authorization request.
In the world of microfrontends, the concept of scopes is key. Scopes, representing different levels of access, are tied to different client IDs. Through these scopes, we define what each microfrontend can access, ensuring a secure and organized system.
In the case of a backend for frontend strategy, we generate an access token for each new request. This token is based on the user authority on BFF. Then, we send the request to the target service. It’s a straightforward approach. But it has its drawbacks, scalability issues and performance issues. Let’s delve into a possible request flow using this method:
- Strong Security Capabilities: By generating a single usage token and isolating the service scope from the outside world, the flow provides strong security capabilities.
- Granular Access Control: The use of scopes allows for fine-grained access control, enhancing the security of the application.
- Isolation of Microfrontends: Each microfrontend can operate independently and securely, as they don’t need to share authentication state with each other.
- Performance Impact: The flow can introduce increased latency due to the number of requests made to the token issuer, which may negatively impact the performance of the application.
- Cost Implications: Depending on the token issuer and the usage plans, there may be cost implications associated with the increased number of requests made to the issuer.
- Complexity: The flow involves multiple steps and interactions, making it more complex to implement and maintain.
To address the performance and cost implications, one strategy is to implement a caching layer for the generated tokens for each scoped service. This approach can help reduce the number of requests made to the token issuer service by checking against the ID token. By serving the tokens directly from the cache, you can improve latency and reduce the load on the issuer service. However, it’s crucial to have a cache invalidation strategy, apply security measures, and monitor the cache performance to ensure its effectiveness and address any potential issues that may arise.
For the frontend silent login strategy, the token is generated once the microfrontend bootstraps and is used as any other token, added to the request headers. It is, by design, simpler. But again, we have some drawbacks to deal with. Let's deep dive into the details of using this method:
In this flow, the token is generated once for the microfrontend, which can help reduce the number of requests to the token issuer, thus potentially improving performance and reducing costs. However, this approach also introduces complexity that needs to be managed. The security of the token storage and the handling of the cliend id are crucial considerations in this strategy.
Now the frontend needs to know the client id of the scope to generate the access token, this opens up some backdoors for the application:
If the client id is exposed in the frontend code, it can be exploited by attackers. To mitigate this, avoid embedding secrets in the frontend code. If necessary, use a middleware service to store and secure your secrets, then proxy the API calls to your frontend.
XSS is a common attack on web applications that can lead to the exposure of the token to attackers. Use Content Security Policy (CSP) to restrict the sources of scripts and other resources, and use auto-escaping template engines to prevent the injection of malicious scripts.
The bootstrap request sent to the token issuer can easily be intercepted, copied and extended to add unwanted scopes, generating tokens with uncontrolled access to services. This can lead to data leaks or data theft.
Some ways to reduce the impact or prevent issues related to abuse are using the least privilege principle and to monitor for any unusual activity, such as a sudden increase in the scope of issued tokens, which could indicate an attack.
This option may be reasonable if time to production, less complexity and performance are more important than security, but is certainly not recommended for systems that deal with highly sensitive data.
To create a balanced architecture, we must identify the needs of our solution. We aim to minimize calls to the token issuer to simplify the overall system and reduce costs. Additionally, we want to ensure that details of token generation are not shared with the client.
One method to meet these requirements is Server-Side Rendering (SSR). SSR allows the entire token generation process to occur on the server side, sharing only the final result with the client page.
While SSR can help to mitigate some of the challenges associated with authentication in a microfrontend environment, it is not a silver bullet. It is important to carefully consider the specific requirements and constraints of your application and to implement additional security measures as necessary:
In a microfrontend architecture, different components of the application may be developed, deployed, and run independently. Therefore, managing and synchronizing the user’s authentication state across all these microfrontends can be a challenge. This is particularly true if different microfrontends are developed by different teams, possibly using different technologies.
While SSR can help to improve security by handling the initial authentication process server-side, it also introduces new security considerations. For example, the server must securely store and manage the user’s authentication token. Additionally, the server must ensure that this token is securely transmitted to the client and that it is not exposed to potential attackers.
While SSR can help to reduce the number of calls to the token issuer, it can also introduce additional processing overhead on the server. This could potentially impact the performance of your application, particularly if you have a large number of users or if your server is under heavy load.
Implementing SSR for authentication in a microfrontend environment can be complex, particularly if you’re not already familiar with SSR or if your application has complex authentication requirements. This complexity can increase the development time, make the application more difficult to maintain, and introduce additional potential points of failure.
In a microfrontend application, the application state is actually a combination of states, one for each microfrontend. Therefore, managing the routing and URL structure can be challenging. This might require specific logic in each microfrontend to receive messages from the main application router and update its state.
Navigating the intricacies of authentication in the world of microfrontends demands a thoughtful and strategic approach. We’ve explored two key strategies — Backend for Frontend (BFF) Authorization and Frontend Silent Login — each with its advantages and disadvantages. While BFF Authorization offers strong security and fine-grained access control, it may introduce performance and complexity issues. On the other hand, Frontend Silent Login simplifies token management but raises concerns about security and scope control.
To strike a balance, Server-Side Rendering (SSR) emerges as a potential solution, allowing for a more centralized token generation process and reducing the number of calls to the token issuer. However, it’s essential to recognize that SSR isn’t a one-size-fits-all solution and comes with its own set of challenges, including state management, security considerations, performance overhead, and increased complexity.
Ultimately, the choice of authentication strategy in a microfrontend environment should align with the specific needs, constraints, and security requirements of your application. It’s crucial to weigh the trade-offs carefully, implement robust security measures, and monitor for any unusual activities to ensure the safety and performance of your microfrontend architecture. By doing so, you can harness the benefits of modularity and independence while safeguarding your system against potential authentication challenges.
Server-side rendering micro-frontends — the architecture https://aws.amazon.com/pt/blogs/compute/server-side-rendering-micro-frontends-the-architecture/
Best Practices for Building Secure Micro Frontends