Best Practices for Building Secure Micro Frontends
This is a guest post written by Grgur Grisogono, Director of Engineering at Modus Create.
Micro frontends have been rapidly gaining popularity in the development of modern web applications. However, while micro frontends offer several benefits, including improved scalability, agility, and faster development cycles, they also come with several security challenges. In this article, we will explore common micro frontend security issues for the web and hybrid mobile apps.
Inconsistent Dependency Management
One of the biggest benefits of micro frontends is democratized development, with teams getting to choose their own dependencies. As an application team’s digital footprint grows, naturally, two or more apps will eventually share dependencies but in different versions. While modern runtimes like the ones shipped with Module Federation will have no problem dealing with versioning discrepancies, mismatches can be a major flaw of micro frontends.
In a simpler scenario, two micro applications could use different major versions of the same library. In that event, most runtimes will choose to load both versions, respectively. While this will work fine in many cases, the system is clearly not optimized and may execute vulnerable code from the outdated library. Micro frontends don’t have a straightforward upgrade path mechanism, so teams need to figure out how to stay up to date collectively.
This third scenario has an interesting twist, making it far more complex. Suppose the shared library is a singleton, meaning only one version can be loaded simultaneously (e.g., React, Styled Components). In that case, we no longer have the luxury of loading different versions in independent sandboxes. Instead, the first version loaded stays active and reused across the stack. Most singletons will break, requiring all micro frontends to run the same version. Libraries with excellent backward compatibility will work fine, but these are rare.
Running the same version across the stack wouldn’t be a significant setback had it not been for the maintenance issues. Unfortunately, updating such a library can be incredibly difficult because all micro frontends must be updated simultaneously, which is sometimes impossible without causing problems for app users.
Unfortunately, there isn’t a universal technical solution to this. The best advice is to avoid singletons and resort to the standards. Leverage your client’s powerful browser to the greatest extent possible.
Another helpful tip is to implement semantic versioning for your micro frontends. Versioned apps can help teams understand the level of changes between two deployments.
A more complex approach to managing conflicting dependencies is to integrate a custom dependency manager (i.e., a global lock file). Your build step can record and compare dependencies with other apps in the system and suggest alternatives where applicable.
Authorization Token Sharing in Hybrid Mobile Apps
Authentication in hybrid mobile apps is usually a part of the native code since there are no callback URLs in SSO. As discussed at my Building Superapps in Mobile talk at Micro Frontends Summit 2023, it’s common for apps to create secure storage for the authorization token on the native side but then provide that token for the JavaScript code to communicate with a backend. This is already an improvement from the regular mobile web, as secure storage on the native side is superior to the browser capabilities.
However, a malicious attacker can tap into the JavaScript code to access shared tokens. A safer solution is to let the native side of the app communicate with the server. While implementing and maintaining such a bridge comes at a cost, it ensures that sensitive tokens are never accessible to interpreted code.
In other words, the native application shell serves as an asynchronous service to the UI, similar to microservices. Many organizations bank on that service to secure other sensitive data.
Feature Isolation
One of the most critical (yet awkward) recommendations for micro frontend architects and engineers is to avoid inter-app communication at all costs. It comes naturally to many developers to create service points for sharing data, making it sound like a ridiculous best practice. However, as your application scales, it will undoubtedly run into severe issues if you don’t isolate apps.
Since micro frontends sport at least some independent processes like the build step, deployment, test suites, or infrastructure, there are multiple points where potential attackers could sneak in. Implementing a zero-trust policy across the board will help mitigate such issues.
One way to improve trust policies is introducing Cross-Origin Resource Sharing (CORS) and Content Security Policy (CSP) headers in places such as:
- The application core – limiting allowed domains for feature loading
- Each frontend to its backend service – ensuring communication with trusted APIs only
If your micro frontends are loaded from different domains or subdomains, you will want to have at least some of the policies mentioned above in place. However, if you don’t care for such security hardening, then at least set the Access-Control-Allow-Origin
CORS header to “*
” because that will avoid opaque responses. Opaque responses misreport response size and some headers, which can create issues with caching and even crash mobile browsers when the browser cache size is exceeded.
For isolation to work, micro frontends should be deployed to predictable domains. CSPs work best when Frontends have respective subdomains at the cost of maintenance, especially when there are many apps to integrate.
In addition to isolating communication, it’s a good idea to enforce a zero-trust build step where any outbound communication is restricted. That helps create solid end-to-end isolation.
Conclusion
Micro frontends should generally implement the same security protocols as any web application. Even though micro frontend architecture is complex, it doesn’t bring significant security challenges by design. More so, they promote security by organizing business logic into independent, self-contained modules that can be secured autonomously.