mauriciovigolo / keycloak-angular

Easy Keycloak setup for Angular applications.
MIT License
729 stars 280 forks source link

Question: Keycloak with Module Federation #432

Closed fraschizzato closed 8 months ago

fraschizzato commented 2 years ago

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.

michaelhunziker commented 1 year ago

@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?

fraschizzato commented 1 year ago

@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.

Galileon-venta commented 1 year ago

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

mauriciovigolo commented 1 year ago

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!

fraschizzato commented 1 year ago

@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

cdonis commented 1 year ago

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

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...
  5. I done following sequence:

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 commented 1 year ago

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.

mauriciovigolo commented 1 year ago

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

cdonis commented 1 year ago

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

Example features:

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,

the application runned OK with following behaviour:

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

mauriciovigolo commented 1 year ago

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

shivamsoods commented 1 year ago

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.

Laboiite commented 1 year ago

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

Ketec commented 1 year ago

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.

antogarcia72 commented 1 year ago

Hi @mauriciovigolo, any news about this issue?

cristearares24 commented 1 year ago

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)

isaacgonzalezroman commented 10 months ago

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

hersonls commented 9 months ago

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.

syedhannan commented 8 months ago

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 commented 8 months ago

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

mauriciovigolo commented 8 months ago

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

cmaart commented 7 months ago

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?

Ricard commented 7 months ago

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).

sifiso-thela commented 2 weeks ago

Hi @mauriciovigolo @cdonis @fraschizzato,

I see this issue has been closed. Was there a solution for this?