{"id":2311,"date":"2025-09-23T13:15:02","date_gmt":"2025-09-23T13:15:02","guid":{"rendered":"https:\/\/www.cmarix.com\/qanda\/?p=2311"},"modified":"2026-02-05T11:59:10","modified_gmt":"2026-02-05T11:59:10","slug":"secure-micro-frontends-auth-authorization-in-angular","status":"publish","type":"post","link":"https:\/\/www.cmarix.com\/qanda\/secure-micro-frontends-auth-authorization-in-angular\/","title":{"rendered":"How do you Handle Authentication and Authorization in a Micro Frontend Architecture?"},"content":{"rendered":"\n<p>Handling security is one of the most critical and challenging aspects of a micro frontend architecture. The core principle of micro frontends is isolation, but authentication (AuthN &#8211; who you are) and authorization (AuthZ &#8211; what you&#8217;re allowed to do) are inherently cross-cutting concerns that must be managed centrally.<\/p>\n\n\n\n<p>The most robust, secure, and widely-adopted pattern is Centralized Authentication in the Shell Application. In this model, the host (or shell) application is solely responsible for the entire authentication lifecycle. The micro frontends are &#8220;dumb&#8221; in this regard; they do not manage tokens or login flows but simply consume the authentication state provided by the shell.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Core Principles of the Centralized Model<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Single Point of Login:<\/strong> The user always authenticates through the shell application. The login form, redirection to an IdP (Identity Provider like Auth0, Okta, or Azure AD), and token reception are all handled exclusively by the shell.<\/li>\n\n\n\n<li><strong>Shell as Token Custodian:<\/strong> The shell securely stores the authentication token (e.g., a JSON Web Token &#8211; JWT). It is responsible for storing it (e.g., in a secure, HttpOnly cookie or browser storage) and managing its lifecycle (e.g., refresh tokens). <strong>The raw token should never be passed directly to the micro frontends.<\/strong><\/li>\n\n\n\n<li><strong>Authentication State, Not Tokens:<\/strong> The shell shares the <em>state<\/em> of authentication (e.g., &#8220;user is logged in,&#8221; &#8220;user&#8217;s name is Alice,&#8221; &#8220;user has &#8216;admin&#8217; role&#8221;) with the micro frontends, not the token itself.<\/li>\n\n\n\n<li><strong>Centralized API Interception:<\/strong> The shell intercepts all outgoing HTTP requests made from <em>any<\/em> micro frontend. If a request is destined for a protected API, the shell attaches the authentication token before sending it. This is the most crucial piece of the puzzle.<\/li>\n\n\n\n<li><strong>Centralized Route Protection:<\/strong> The shell is responsible for guarding routes, including the routes that load entire micro frontends. It can prevent a user from even loading a micro frontend if they are not authenticated or authorized.<\/li>\n<\/ol>\n\n\n\n<p><strong>Practical Example: Implementing Centralized Auth<\/strong><\/p>\n\n\n\n<p>Let&#8217;s build a practical solution with a shell app and a dashboard-mfe.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Create the Central AuthService in the Shell<\/h3>\n\n\n\n<p>This service will manage everything related to authentication.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ shell\/src\/app\/auth\/auth.service.ts\nimport { Injectable } from '@angular\/core';\nimport { BehaviorSubject, Observable, of } from 'rxjs';\nimport { tap } from 'rxjs\/operators';\n\nexport interface UserProfile {\n  name: string;\n  roles: string&#91;];\n}\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthService {\n  \/\/ Use BehaviorSubject to hold and broadcast the current auth state\n  private isAuthenticatedSubject = new BehaviorSubject&lt;boolean>(false);\n  public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();\n\n  private userProfileSubject = new BehaviorSubject&lt;UserProfile | null>(null);\n  public userProfile$ = this.userProfileSubject.asObservable();\n\n  constructor() {\n    \/\/ In a real app, you'd check for an existing token here\n  }\n\n  \/\/ Simulate a login\n  login(username: string, password: string): Observable&lt;boolean> {\n    \/\/ In a real app, this would be an HttpClient call to your auth server\n    return of(true).pipe(\n      tap(() => {\n        const token = 'fake-jwt-token-from-server';\n        localStorage.setItem('authToken', token); \/\/ Use secure storage in a real app!\n\n        const user: UserProfile = {\n          name: 'Alice',\n          roles: &#91;'admin', 'editor'],\n        };\n\n        this.isAuthenticatedSubject.next(true);\n        this.userProfileSubject.next(user);\n      })\n    );\n  }\n\n  logout(): void {\n    localStorage.removeItem('authToken');\n    this.isAuthenticatedSubject.next(false);\n    this.userProfileSubject.next(null);\n  }\n\n  getToken(): string | null {\n    return localStorage.getItem('authToken');\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Create a Central HttpInterceptor in the Shell<\/h3>\n\n\n\n<p>This is the magic component that attaches the token to outgoing requests from <em>anywhere<\/em> in the application, including the loaded micro frontends.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ shell\/src\/app\/auth\/auth.interceptor.ts\nimport { Injectable } from '@angular\/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor\n} from '@angular\/common\/http';\nimport { Observable } from 'rxjs';\nimport { AuthService } from '.\/auth.service';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n\n  constructor(private authService: AuthService) {}\n\n  intercept(request: HttpRequest&lt;unknown>, next: HttpHandler): Observable&lt;HttpEvent&lt;unknown>> {\n    const token = this.authService.getToken();\n    const isApiUrl = request.url.startsWith('\/api\/'); \/\/ Only attach token to your API calls\n\n    if (token &amp;&amp; isApiUrl) {\n      request = request.clone({\n        setHeaders: {\n          Authorization: `Bearer ${token}`\n        }\n      });\n    }\n\n    return next.handle(request);\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Create a Central AuthGuard in the Shell<\/h3>\n\n\n\n<p>This guard will protect the route that loads our micro frontend.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ shell\/src\/app\/auth\/auth.guard.ts\nimport { Injectable } from '@angular\/core';\nimport { CanLoad, Router } from '@angular\/router';\nimport { Observable } from 'rxjs';\nimport { map, take } from 'rxjs\/operators';\nimport { AuthService } from '.\/auth.service';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthGuard implements CanLoad {\n  constructor(private authService: AuthService, private router: Router) {}\n\n  canLoad(): Observable&lt;boolean> {\n    return this.authService.isAuthenticated$.pipe(\n      take(1),\n      map(isAuthenticated => {\n        if (!isAuthenticated) {\n          this.router.navigate(&#91;'\/login']);\n          return false;\n        }\n        return true;\n      })\n    );\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Wire Everything Together in the Shell<\/h3>\n\n\n\n<p>Update the shell&#8217;s app.module.ts and app-routing.module.ts.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ shell\/src\/app\/app.module.ts\nimport { HTTP_INTERCEPTORS, HttpClientModule } from '@angular\/common\/http';\nimport { AuthInterceptor } from '.\/auth\/auth.interceptor';\n\/\/ ...other imports\n\n@NgModule({\n  imports: &#91;\n    BrowserModule,\n    HttpClientModule, \/\/ Ensure HttpClientModule is imported\n    \/\/ ...\n  ],\n  providers: &#91;\n    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n  ],\n  bootstrap: &#91;AppComponent],\n})\nexport class AppModule {}\n\n\/\/ shell\/src\/app\/app-routing.module.ts\nimport { Routes } from '@angular\/router';\nimport { AuthGuard } from '.\/auth\/auth.guard';\nimport { LoginComponent } from '.\/login\/login.component'; \/\/ A simple login component\n\nexport const routes: Routes = &#91;\n  { path: 'login', component: LoginComponent },\n  {\n    path: 'dashboard',\n    canLoad: &#91;AuthGuard], \/\/ Protect this route\n    loadChildren: () =>\n      import('dashboard-mfe\/Module').then((m) => m.DashboardModule),\n  },\n  \/\/ ... other routes\n];<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 5: Sharing the Auth State with the Micro Frontend<\/h3>\n\n\n\n<p>The micro frontend doesn&#8217;t need the login() method or the token. It just needs the isAuthenticated$ and userProfile$ observables. We&#8217;ll use a public, shared API for this.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1. Define a Shared Service Type<\/h4>\n\n\n\n<ol class=\"wp-block-list\"><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ shell\/src\/app\/auth\/shared-auth.service.ts\nimport { Observable } from 'rxjs';\nimport { UserProfile } from '.\/auth.service';\n\nexport abstract class SharedAuthService {\n  abstract isAuthenticated$: Observable&lt;boolean>;\n  abstract userProfile$: Observable&lt;UserProfile | null>;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">2. Update AuthService to Implement It<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ shell\/src\/app\/auth\/auth.service.ts\nimport { SharedAuthService } from '.\/shared-auth.service';\n\/\/ ...\n@Injectable({ providedIn: 'root' })\nexport class AuthService extends SharedAuthService {\n  \/\/ ... same content as before\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">3. Configure Webpack in the Shell<\/h4>\n\n\n\n<ol class=\"wp-block-list\"><\/ol>\n\n\n\n<p>Use @angular-architects\/module-federation&#8217;s share helper to share the running instance of the AuthService.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ shell\/webpack.config.js\nconst { share, withModuleFederationPlugin } = require('@angular-architects\/module-federation\/webpack');\n\nmodule.exports = withModuleFederationPlugin({\n  \/\/ ... remotes config\n\n  shared: share({\n    \/\/ ... share Angular dependencies\n\n    \/\/ Share the AuthService instance from the shell's injector\n    '@app\/auth': {\n      singleton: true,\n      strictVersion: true,\n      requiredVersion: 'auto',\n      \/\/ The key part: this tells remotes to get the instance\n      \/\/ from the shell's DI container instead of creating a new one.\n      provider: '.\/src\/app\/auth\/auth.service.ts',\n    },\n  }),\n});<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 6: Consume the Shared State in the Micro Frontend<\/h3>\n\n\n\n<p>Now, the dashboard-mfe can use this shared service to tailor its UI and make API calls.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ dashboard-mfe\/src\/app\/dashboard\/dashboard.component.ts\nimport { Component } from '@angular\/core';\nimport { HttpClient } from '@angular\/common\/http';\nimport { Observable } from 'rxjs';\n\/\/ This import works because of the webpack config. It imports the abstract class for type safety.\nimport { SharedAuthService } from '@app\/auth';\n\n@Component({\n\u00a0 selector: 'app-dashboard',\n\u00a0 template: `\n\u00a0 \u00a0 &lt;h2>Welcome to the Dashboard MFE&lt;\/h2>\n\u00a0 \u00a0 &lt;ng-container *ngIf=\"authService.isAuthenticated$ | async\">\n\u00a0 \u00a0 \u00a0 &lt;p *ngIf=\"authService.userProfile$ | async as user\">\n\u00a0 \u00a0 \u00a0 \u00a0 Hello, {{ user.name }}!\n\u00a0 \u00a0 \u00a0 \u00a0 &lt;!-- Fine-grained authorization inside the MFE -->\n\u00a0 \u00a0 \u00a0 \u00a0 &lt;button *ngIf=\"user.roles.includes('admin')\" (click)=\"loadAdminData()\">\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 Load Admin Data\n\u00a0 \u00a0 \u00a0 \u00a0 &lt;\/button>\n\u00a0 \u00a0 \u00a0 &lt;\/p>\n\u00a0 \u00a0 \u00a0 &lt;pre>{{ data$ | async | json }}&lt;\/pre>\n\u00a0 \u00a0 &lt;\/ng-container>\n\u00a0 `,\n})\nexport class DashboardComponent {\n\u00a0 data$: Observable&lt;any> | undefined;\n\n\u00a0 \/\/ Inject the shared service using the abstract class as the token\n\u00a0 constructor(public authService: SharedAuthService, private http: HttpClient) {}\n\n\u00a0 loadAdminData() {\n\u00a0 \u00a0 \/\/ When this call is made, the shell's interceptor will AUTOMATICALLY\n\u00a0 \u00a0 \/\/ attach the 'Authorization: Bearer ...' header. The MFE is completely\n\u00a0 \u00a0 \/\/ unaware of the token.\n\u00a0 \u00a0 this.data$ = this.http.get('\/api\/admin\/data');\n\u00a0 }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Authorization: Fine-Grained Control<\/h2>\n\n\n\n<p><strong>The pattern extends beautifully to authorization:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Coarse-Grained (Route-Level):<\/strong> The shell&#8217;s AuthGuard handles this. You can create a RoleGuard in the shell that checks authService.userProfile$.pipe(map(u => u.roles.includes(&#8216;admin&#8217;))) to protect entire MFE routes.<\/li>\n\n\n\n<li><strong>Fine-Grained (UI-Level):<\/strong> As shown in the dashboard.component.ts example, the MFE consumes the shared userProfile$ observable and uses the roles array to conditionally show or hide UI elements (like an &#8220;Admin&#8221; button) using *ngIf. This allows the MFE to manage its own internal authorization rules based on the identity provided by the shell.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Final Words<\/h2>\n\n\n\n<p>Centralized authentication in the shell app is the safest and cleanest way to handle security in micro frontends. It keeps tokens out of individual MFEs and simplifies state sharing. To get this right, <a href=\"https:\/\/www.cmarix.com\/hire-angular-developers.html\">hire Angular developers<\/a> who know how to architect secure, scalable systems from day one.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Handling security is one of the most critical and challenging aspects of a micro frontend architecture. The core principle of micro frontends is isolation, but authentication (AuthN &#8211; who you are) and authorization (AuthZ &#8211; what you&#8217;re allowed to do) are inherently cross-cutting concerns that must be managed centrally. The most robust, secure, and widely-adopted [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":2312,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[7,3],"tags":[],"class_list":["post-2311","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","category-web"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts\/2311","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/comments?post=2311"}],"version-history":[{"count":6,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts\/2311\/revisions"}],"predecessor-version":[{"id":2319,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/posts\/2311\/revisions\/2319"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/media\/2312"}],"wp:attachment":[{"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/media?parent=2311"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/categories?post=2311"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cmarix.com\/qanda\/wp-json\/wp\/v2\/tags?post=2311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}