Integrating Authentication with OpenID Connect in Module Federation
Introduction
Module Federation enhances the management of shared code and state across micro frontends. This guide will walk you through adding authentication to your project using OpenID Connect with Okta. By the end of this documentation, your application will be able to handle authenticated states both within the existing application and a newly integrated micro frontend.
Prerequisites
- A free Okta developer account. If you do not have one, you can sign up using the Okta CLI.
- Okta CLI installed on your machine.
Setting Up Okta Authentication
Create an Okta Application
Register or Log In to Okta:
- To register for a new account, execute
okta register
in your terminal.
- If you already have an account, log in by running
okta login
.
Create Your Application:
- Execute
okta apps create
.
- When prompted, accept the default application name or provide a new one.
- Select Single-Page App (SPA) and confirm by pressing Enter.
- For Redirect URI, use
http://localhost:4200/login/callback
and set the Logout Redirect URI to http://localhost:4200
.
Configure Application in Okta
The Okta CLI creates an OIDC SPA in your Okta Org, configures redirect URIs, grants access to the Everyone group, and adds http://localhost:4200
as a trusted origin.
NOTE
The Okta Admin Console can also be used for app creation. For Angular apps, refer to the Okta documentation on creating an Angular application.
Okta Application Configuration Example:
- Issuer:
https://dev-12345.okta.com/oauth2/default
- Client ID:
0oab12345CDEF
NOTE
Ensure you note down the Issuer and Client ID; these are crucial for your application's configuration.
Install Required Libraries
Add Okta Angular and Okta Auth JS libraries to your project:
npm install @okta/okta-angular@5.2 @okta/okta-auth-js@6.4
Configure Okta in Angular Module
Import and configure OktaAuthModule
and OktaAuth
in your shell project's AppModule
. Replace {yourOktaDomain}
and {yourClientID}
with your specific Okta domain and client ID.
import { NgModule } from '@angular/core';
import { OKTA_CONFIG, OktaAuthModule } from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';
const oktaAuth = new OktaAuth({
issuer: 'https://{yourOktaDomain}/oauth2/default',
clientId: '{yourClientID}',
redirectUri: window.location.origin + '/login/callback',
scopes: ['openid', 'profile', 'email']
});
@NgModule({
imports: [
OktaAuthModule,
// other imports
],
providers: [
{ provide: OKTA_CONFIG, useValue: { oktaAuth } }
],
// other module properties
})
Configure Routing for Authentication
Update the app-routing.module.ts
to include the login callback route.
import { Routes } from '@angular/router';
import { OktaCallbackComponent } from '@okta/okta-angular';
const routes: Routes = [
{ path: '', component: ProductsComponent },
{ path: 'basket', loadChildren: () => import('mfeBasket/Module').then(m => m.BasketModule) },
{ path: 'login/callback', component: OktaCallbackComponent }
];
Implementing Authentication Logic
Update Application Component
Modify app.component.ts
to include sign-in and sign-out logic, utilizing the Okta libraries. Update the authentication state variables accordingly.
import { Component, Inject } from '@angular/core';
import { Observable } from 'rxjs';
import { filter, map, shareReplay } from 'rxjs/operators';
import { OKTA_AUTH, OktaAuthStateService } from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
public isAuthenticated$: Observable<boolean>;
public name$: Observable<string>;
constructor(private oktaStateService: OktaAuthStateService, @Inject(OKTA_AUTH) private oktaAuth: OktaAuth) {
this.isAuthenticated$ = this.oktaStateService.authState$
.pipe(
filter(authState => !!authState),
map(authState => authState.isAuthenticated ?? false),
shareReplay()
);
this.name$ = this.oktaStateService.authState$
.pipe(
filter(authState => !!authState && !!authState.isAuthenticated),
map(authState => authState.idToken?.claims.name ?? '')
);
}
public async signIn(): Promise<void> {
await this.oktaAuth.signInWithRedirect();
}
public async signOut(): Promise<void> {
await this.oktaAuth.signOut();
}
}
Handle Sign-In and Sign-Out in the UI
In app.component.html
, add the UI logic for sign-in and sign-out buttons.
<li>
<button *ngIf="(isAuthenticated$ | async) === false; else logout" (click)="signIn()">
Sign In
</button>
<ng-template #logout>
<button (click)="signOut()">
Sign Out
</button>
</ng-template>
</li>
Testing the Application
Run the project using npm run start (or the appropriate command for your setup) to test authentication functionality. Successful implementation allows users to sign in and out, with the profile information being accessible upon signing in.
Adding User Profiles with Module Federation
This section expands on incorporating Module Federation to share authenticated state across the main application and the micro-frontend. We'll explore how to set up a new Angular application, configure routing, and update components to include profile details.
Generate a New Angular Application
Stop the current project execution and run the following command to create a new Angular application named mfe-profile
:
ng generate application mfe-profile --routing --style css --inline-style --skip-tests
This command accomplishes several tasks:
- Generates a new application with a module and component.
- Adds a separate routing module.
- Defines CSS styles to be inline within components.
- Skips the creation of test files for the initial component.
Generate HomeComponent and ProfileModule
Execute the following commands to create a HomeComponent
and a ProfileModule
within the mfe-profile
application:
ng generate component home --project mfe-profile
ng generate module profile --project mfe-profile --module app --routing --route profile
These commands create:
- A
HomeComponent
for the default route.
- A
ProfileModule
with routing and a default ProfileComponent
, added as a lazy-loaded route to the AppModule
.
Updating the Application Code
Configure Routing:
Update projects/mfe-profile/src/app/app-routing.module.ts
to include a route for HomeComponent
and a lazy-loaded route for ProfileModule
:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'profile', loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule) }
];
Update AppComponent and HomeComponent Templates
- For
app.component.html
, replace the content with a message of your choice and a router-outlet
for navigation.
- For
home.component.html
, provide a message guiding users to the Profile page with a router link to /profile
.
Profile Component Configuration
Implement Profile Logic
Update projects/mfe-profile/src/app/profile/profile.component.ts
to include properties for user profile information and authentication state, utilizing OktaAuthStateService
.
Update Profile Template
Modify the template to display user profile details, such as name and email, and the last sign-in time.
Integrating Module Federation
Add Module Federation to mfe-profile
Use the @angular-architects/module-federation
schematic to prepare mfe-profile
for Module Federation, specifying port 4202.
ng add @angular-architects/module-federation --project mfe-profile --port 4202
Configure mfe-profile
as a Remote
Update webpack.config.js
in mfe-profile
to expose ProfileModule
for the host application.
Update Host Application Configuration
Modify the shell application's webpack.config.js
to include mfe-profile
as a remote, enabling the host to access the Profile micro-frontend.
Share Authenticated State
- Update
webpack.config.js
in the shell application to share Okta libraries as singletons.
- Ensure
mfe-profile
also shares the Okta libraries to utilize the authenticated state.
Running the Integrated Application
After configuring Module Federation and updating both the shell and micro-frontend applications, you can run the project using npm run run:all
.
This setup allows you to log in, view your profile, log out, and interact with other parts of the application seamlessly across the main and micro-frontend parts.