keycloak-angular icon indicating copy to clipboard operation
keycloak-angular copied to clipboard

Question: Keycloak with Module Federation

Open fraschizzato opened this issue 2 years ago • 19 comments

Hi, at this time I'm running keycloak-angular in a standalone app with the (working) following configuration/initialization:

Keycloak.init function:

import {KeycloakService} from "keycloak-angular";
import {environment} from "../../environments/environment";
import { KeycloakOnLoad } from "keycloak-js";

export function initializeKeycloak(keycloak: KeycloakService) {
    const onLoad =  environment['keycloak']['onLoad'] as KeycloakOnLoad;
    return () =>
        keycloak.init({
            config: {
                realm: environment['keycloak']['realm'],
                url: environment['keycloak']['url'],
                clientId: environment['keycloak']['clientId']
            },
            initOptions: {
                onLoad: onLoad,
                checkLoginIframe: environment['keycloak']['checkLoginIframe'],
                silentCheckSsoRedirectUri:
                    window.location.origin + '/assets/silent-check-sso.html'
            },
            bearerExcludedUrls: ['']
        })
}

AuthModule (first loaded):

import { APP_INITIALIZER, NgModule } from '@angular/core';
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
import { AuthComponent } from './auth.component';
import {CommonModule} from "@angular/common";
import {MatIconModule} from "@angular/material/icon";
import {AuthGuard} from "./auth.guard";
import {initializeKeycloak} from "./initKeycloak";


@NgModule({
  declarations: [AuthComponent],
  imports: [CommonModule, KeycloakAngularModule,MatIconModule],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService]
    },
      AuthGuard
  ],
  exports: [
      AuthComponent
  ],
  bootstrap: [AuthComponent]
})
export class AuthModule {}

If I try to share the keycloak-js in webpack (to federate the module) like this:

const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({

  shared: {

    "@angular/core": { singleton: true, strictVersion: true, requiredVersion: '14.2.5', eager: true },
    "@angular/common": { singleton: true, strictVersion: true, requiredVersion: '14.2.5' , eager: true},
    "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: '14.2.5' , eager: true},
    "@angular/router": { singleton: true, strictVersion: true, requiredVersion: '14.2.5' , eager: true},
    "@angular/material": { singleton: true, strictVersion: true, requiredVersion: '14.2.5' , eager: true},
    'keycloak-js': { singleton: true, strictVersion: true, requiredVersion: '19.0.2', eager: true },
    'keycloak-angular': { singleton: true , strictVersion: true, requiredVersion: '12.1.0' },
    //...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
  },

});

I'll get the following error (The share that causes the issue is keycloak-js):

src_bootstrap_ts.js:1 ERROR TypeError: keycloak_js__WEBPACK_IMPORTED_MODULE_3__ is not a function
    at keycloak-angular.mjs:114:26
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at asyncToGenerator.js:32:1
    at new ZoneAwarePromise (zone.js:1387:29)
    at asyncToGenerator.js:21:1
    at KeycloakService.init (keycloak-angular.mjs:111:30)
    at Array.<anonymous> (initKeycloak.ts:8:18)
    at ApplicationInitStatus.runInitializers (core.mjs:25353:36)

This is in the "shell" frontend, the one that is over all the other frontends. As I know the only way to share the same instance is by sharing the module. At this time I'm passing the token to the mfe via an external service library or through localStorage , the single frontend have an independent keycloak.init in the app.module (as in federation I'm passing the child module).

Thanks

Versions.

keycloak-angular 12.1.0 keycloak-js 19.0.2

Repro steps.

Add the keycloak-js to the shares of module federation

Desired functionality.

I'd like to know if there's a way to share the keycloak instance to use widely the keycloak-angular and get all the features on the single microfrontends, firstly the AuthGuard.

fraschizzato avatar Oct 13 '22 19:10 fraschizzato

@fraschizzato Did you find a solution for this issue? Have you been able to share the Keycloak Instance (KeycloakService) between the shell and the micro frontend?

michaelhunziker avatar Jan 03 '23 13:01 michaelhunziker

@fraschizzato Did you find a solution for this issue? Have you been able to share the Keycloak Instance (KeycloakService) between the shell and the micro frontend?

No, I'm passing the token and other data with a shared service. In standalone mode every microfrontend has a separate keykloak init, in federated mode the first module is after the initial init module.

fraschizzato avatar Jan 09 '23 23:01 fraschizzato

Hey @fraschizzato did you try to use an AuthGuard? Did you get it to find the token Values from the Injected KeycloakService?

Galileon-venta avatar Mar 08 '23 12:03 Galileon-venta

Hi @fraschizzato, sorry for the late reply. I'm catching up on the issues and this one seems quite interesting and important to solve. Is it possible to create a simple example of the issue? Are you using something like Nx or similar?  Thanks!

mauriciovigolo avatar May 08 '23 01:05 mauriciovigolo

@Galileon-venta Not used AuthGuard, all the routes are catched and secured by keycloak auth.

@mauriciovigolo NX not used or similiar not used. I think the simpliest example is this: https://github.com/manfredsteyer/module-federation-plugin-example/tree/static It's the code of the example provided for module federation here: https://www.angulararchitects.io/en/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/

Anyway now I'm sharing token with a service in a custom system wide library. With that mode I can get keycloak security both in "Portal" mode and Standalone. The "Portal" is the main app, it starts from Main Module, inits the keycloak and store the token non the library. The Other Apps usually starts from a child module (called by the Portal) and get the token from the library. But, the other apps can also run indipently from their Main Module, that module also contain the keycloak init.

So: In standalone the sigle app starts itself and loads keycloak, in "Portal Mode" is loaded the single requested (child) module.

Thanks

fraschizzato avatar May 14 '23 00:05 fraschizzato

Hi @mauriciovigolo, I'm using NX and a similiar issue (observe that reported error mention "is not a function" and in my case "is not a constructor") happend to me: ERROR TypeError: keycloak_js__WEBPACK_IMPORTED_MODULE_3__ is not a Constructor (refered line is where "keycloack.init()" is called.

In my case I observed a weird behaviour: error appear and then gone, I don't know how, but here is my history:

Enviroment

  • Angular 16.0.0
  • Microfrontend arquitecture managed with NX
  • keycloak-js: 21.1.1
  • keycloak-angular: 14.0.0
  • Auth library created with NX: contain keycloak stuff
  • Shell app imports Auth library to check authentication status

History

  1. After setting all the stuff (see code) in Auth lib and Shell, first try it worked OK: when open the shell app, comunication with keycloak work and it redirect to base url with "error" parameter because user not authenticated.
  2. After some changes in code (not related to the keycloak config stuff in the Auth lib), mentioned issue started to appear. Really I can't said wich changes had influence in the behaviour, was completelly unexpexted.
  3. As I had no solution, I created the project again installing all library from cero. This time, the issue appeared since the first try
  4. Then I tried to solve some compilation warnings (apparently not related to the issue):
  • related to CommonJS dependency related to "base64-js" and "js-sha256" and
  • TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and "false" respectively by the Angular CLI...
  1. I done following sequence:
  • In app.modules.ts in Shell App I removed AuthModule (defined in Auth lib) from imports (no relation with the warnings, just to feel the app works, :-)
  • Recompiled the application and everything work fine without using keycloak
  • To remove 1st warning I made changes Shell App in tsconfig.json, tsconfig.app.json and tsconfig.spec.json: "compilerOptions":
{
...
"target": "ES2022",
...
}
  • To remove 2nd warning, in project.json in Sheel App, I added
"allowedCommonJsDependencies": [
  "base64-js",
  "js-sha256"
] 
  • I restored the imports of AuthModule (defined in Auth lib) in the Shell App (app.modules.ts)

After that, everything started to work OK, issue has gone, really I don't know how and why.

Now (as always was) I have the keycloak stuff in the AuthModule defined in Auth lib generated with NX. In the Shell App I imported the AuthModule, the keycloak authentication checking is done perfectly, the user is redirected to the keycloak login page if necessary (I'm using onLoad: 'login-required') and after successfull login, user is returned back to shell app.

My scaffold and code Auth library imagen

keycloak-initilizer.ts

import { KeycloakOptions, KeycloakService } from 'keycloak-angular';
import { environment } from '../../environments/environment';

export function initializer(keycloak: KeycloakService): () => Promise<boolean> {

    const options: KeycloakOptions = {
      config: environment.keycloak,
      initOptions: {
        //onLoad: 'check-sso',
        onLoad: 'login-required',
        checkLoginIframe: false
      },
    }

    return () => keycloak.init(options);
}

shared-auth.modules.ts

import { APP_INITIALIZER, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
import { initializer } from './keycloak-initializer';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';

@NgModule({
  imports: [CommonModule, KeycloakAngularModule],
  providers: [
    {
        provide: APP_INITIALIZER,
        useFactory: initializer,
        multi: true,
        deps: [KeycloakService]
    },
    AuthGuard,
    AuthService,
  ]
})
export class AuthModule { }

Shell app scaffold imagen

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';
import { NxWelcomeComponent } from './nx-welcome.component';
import { AuthModule } from '@auth-lib/shared/auth';

@NgModule({
  declarations: [AppComponent, NxWelcomeComponent],
  imports: [
    BrowserModule,
    RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
    **AuthModule**
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

cdonis avatar Jun 19 '23 20:06 cdonis

Hi @mauriciovigolo, as a follow up of the previous comment I would like to report that in my example, I'm stuck again after trying to run a micro-frontend different than the shell. The described error come back and no way to initialize connection with the keycloack. Look like a dependency injection problem. Please if you plan to solve this issue and need any specific information about the example, condition and enviroment, I'm available, I have an urgent need for this to work in the project I'm currently working.

cdonis avatar Jun 22 '23 14:06 cdonis

Hey @cdonis, could you please provide me a codesanbox example with the problem described, so I could better look into this problem? Thank you!

mauriciovigolo avatar Jun 26 '23 00:06 mauriciovigolo

Hi @mauriciovigolo, thank you very much for your attention. I already created a codesanbox with an example that trigger the error. image

Example features:

  • Monorepo with a micro-frontend architecture composed by a shell (main application, apps/shell), and two sub-applications (micro-frontends): users" (apps/users) and tenants (apps/tenants).
  • A shared library auth (libs/shared/auth) holding a module (AuthModule) with the authentication logic based on keycloak. The module contains services (auth.services.ts) to interact with keycloak and a guard (auth.guard.ts) to be used on routes protection. Also contains the function that initialize the connection with keycloak (in keycloak-initializer.ts)
  • The shell and each subapplication imports the AuthModule and call the keycloack initializer during its initialization, also have configured the guard on its routes.
  • Start running: npx nx run shell:serve
  • Codesandbox link
  • Alternatively you can clone the example from https://github.com/cdonis/nx-keycloak.git

This example never try to connect with keycloak server because during shell and sub-applications initialization, it trigger the mentioned error in line 19 of keycloak-initializer.ts (return () => keycloak.init(options).

As explained in my first post, during a trying where I was resolving following two compilation warnings,

  • keycloak-js\dist\keycloak.mjs depends on 'base64-js'. CommonJS or AMD dependencies can cause optimization bailouts.
  • keycloak-js\dist\keycloak.mjs depends on 'js-sha256'. CommonJS or AMD dependencies can cause optimization bailouts. both solved by adding, in project.json: targets.build.options, following key "allowedCommonJsDependencies": [ "base64-js", "js-sha256" ]
  • TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and "false" respectively by the Angular CLI. To control ECMA version and features use the Browerslist configuration. You can set the "target" to "ES2022" in the project's tsconfig to remove this warning.

the application runned OK with following behaviour:

  • shell application correctly checks authentication status with keycloack,
  • route protection from the shell application works correctly,

then, when I tryied to run one of the subapplication directly (nx run users:serve), application stop working, i.e., error come back

cdonis avatar Jun 27 '23 01:06 cdonis

Hi @cdonis, thank you so much for preparing an example! 🥇 I will dive into this issue today.

mauriciovigolo avatar Jun 27 '23 11:06 mauriciovigolo

Hi @cdonis

I also am trying to use the keycloak angular in micro frontend along with Nx build systems , Webpack v5 and Module Federation . I also faced exactly the same issue. It worked initially for me, then I changed something(not related to keycloak) and getting starting the constructor error.

kc-bug

It points to this file kc-1

Versions used

Angular : 16.1.0 keycloak-angular : 14.0.0 keycloak-js : 21.1.2

Any help or tips to mitigate this issue at the earliest would be really helpful.

shivamsoods avatar Aug 02 '23 17:08 shivamsoods

Hi @cdonis & @mauriciovigolo ! Any updates on this one? I'm facing exactly what @cdonis explained (and thanks again for the effort of making this reproductible). Did you find any workaround? I'm quite stuck here.

I didn't have the issue when using: Angular: 15.8 Nx: 15.8 keycloak-angular: 20.0.5 keycloak-js: 13.1.0

Now that I'm willing to upgrade to: Angular: 16.1.7 Nx: 16.6 keycloak-angular: 14.0.0 keycloak-js: 21.1.2

I'm facing this "keycloak_js__WEBPACK_IMPORTED_MODULE_5__ is not a constructor". Tried a lot of different things but I'm not able to make it work. As a last resort I'm thinking about skipping keycloak-angular and using directly keycloak-js even if it's quite a change as I have several angular remotes, I already have react remotes using it. If an investigation/fix/workaround is ongoing please let me know.

Thanks in advance and have a great day

Laboiite avatar Aug 09 '23 07:08 Laboiite

I simply added the keycloak-js to the skip array you pass to the mfe webpack config (assuming you use the arhitects shareAll()). Don't share keycloak-js.

Ketec avatar Aug 31 '23 11:08 Ketec

Hi @mauriciovigolo, any news about this issue?

antogarcia72 avatar Oct 19 '23 10:10 antogarcia72

Hello @mauriciovigolo any news/solutions? Or a workaround that will work? if you set it to skip, there are 2 different instances of keycloak in shell and mfe (doesn't solve the problem)

cristearares24 avatar Oct 30 '23 09:10 cristearares24

Finally I solved the error by just starting a new project from scratch, with the angular cli (ng new new-project) and copying the code to this new project. I don't know why

isaacgonzalezroman avatar Jan 15 '24 16:01 isaacgonzalezroman

I did the same as @Ketec and it worked. I believe there is some sharing feature, like a singleton (I think), that might be affecting it.

hersonls avatar Jan 29 '24 19:01 hersonls

i added this code as mentioned by @Ketec

shared: { ...shareAll( { singleton: true, strictVersion: true, requiredVersion: "auto", }, ["keycloak-js"] ), },

Unfortunately this does not work in my case. I get the following error

image

Why should we skip the keycloak-js library from being exported in the first place?

syedhannan avatar Mar 14 '24 07:03 syedhannan

Fixed it by making changes to the webpack.config.js file

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(path.join(__dirname, "../../tsconfig.json"), [
  /* mapped paths to share */
]);

module.exports = {
  output: {
    uniqueName: "log-analyzer",
  },
  
  plugins: [
    new ModuleFederationPlugin({
      shared: {
        "@angular/core": { singleton: true, strictVersion: true },
        "@angular/common": { singleton: true, strictVersion: true },
        "@angular/router": { singleton: true, strictVersion: true },
        ...sharedMappings.getDescriptors(),
      },
    }),
    sharedMappings.getPlugin(),
  ],
};

Not sure what it does, but it fixed the issue

syedhannan avatar Mar 14 '24 09:03 syedhannan

Closing this issue, since it will be part of the major library improvements in #549.

mauriciovigolo avatar Mar 19 '24 02:03 mauriciovigolo

None of the workarounds mentioned here works for me. Is there any known way for now to do this apart from waiting for an update to the keycloak library?

cmaart avatar Mar 21 '24 18:03 cmaart

None of the workarounds mentioned here works for me. Is there any known way for now to do this apart from waiting for an update to the keycloak library?

Sure, you can't share keycloak via webpack module federation, but you can share it's instances (service, guard). I mean to create a library that will be shared as singleton, that library defines a service. Any piece (shell or micro) could initialize keycloak and save it's instance on the service as a property, accessible by other federated modules (shell or micro).

Ricard avatar Apr 08 '24 10:04 Ricard