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.
Creating a New Angular Application
- 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.