0

I would like to call a webservice beforce initializing keycloak frontend component from keycloak-angular library to make keycloakconf dynamic. The backendService.keycloakConfig() webservice returne the correct value but the isInitialized variable is always false.

I think mixing the Promise and the Observable is messing with the initialization.

Here is the code :

import {KeycloakService} from 'keycloak-angular';

import {BackendService} from "./service/backend.service";

export function initializer(keycloak: KeycloakService, backendService: BackendService): () => Promise<any> {

  return (): Promise<any> => {
    return new Promise<void>(async (resolve, reject) => {
      backendService.keycloakConfig()
        .toPromise()
        .then(
          keycloakConfig => {
            try {
              keycloak.init({
                config: keycloakConfig,
                enableBearerInterceptor: true,
                loadUserProfileAtStartUp: false,
                initOptions: {
                  checkLoginIframe: false
                },
                bearerExcludedUrls: ['/assets']
              }).then((isInitialize) => {
                console.log("Initialized keycloak", isInitialize)
              }).catch(reason => {console.log(reason)})
              resolve();
            } catch (error) {
              console.log("error", error)
              reject(error);
            }
          })
    });
  };
}
batmaniac
  • 392
  • 4
  • 22
  • Did you find an answer to this? I'm trying to do something quite similar. Closest I've come is storing the configuration in session storage on an unauthed page, and then pulling it in when I do the initialization. However not only is this not really what I want, but it also leads to a bizarre infinite loop on login (page keeps redirecting to keycloak and back to itself thinking it isn't authorized) - but the actual config load and initialization does technically work. – DrTeeth Dec 10 '21 at 05:47

2 Answers2

0

I'm not great with Angular, so there's probably a better way to do exactly what you want, and I'd love to hear it because I'd like to do the same thing.

What I've settled on is on the un-authed pages (landing/splash page pre-login) is to fetch the configuration data and stick it in session storage:

  set keycloakConfig(val: string) {
    sessionStorage.setItem('kcc', val);
  }
  fetchKeycloakConfig() {
    console.log("Fetching keycloak config from service.");
    this._restapiService.getKeycloakConfig().then((data) => {
      this.keycloakConfig = JSON.stringify(data);
    });
  }

Then on the app initialization, I go ahead and pull from session storage which doesn't need any kind of promise/async/await/etc:

function initializeKeycloak(keycloak: KeycloakService) {
  return () => {
    let storedConfig: string = sessionStorage.getItem('kcc');
    if (storedConfig != null) {
      console.log("Initializing Keycloak client with config: " + storedConfig);
      let keycloakConfig = JSON.parse(storedConfig);
      return keycloak.init({
        config: {
          url: keycloakConfig.url,
          realm: keycloakConfig.realm,
          clientId: keycloakConfig.resource
        },
        initOptions: {
          onLoad: 'check-sso',
          silentCheckSsoRedirectUri:
            window.location.origin + '/assets/silent-check-sso.html',
          checkLoginIframe: false
        },
      });
    }
    else {
      console.log("Keycloak config not stored.  No client available.");
      return null;
    }
  }
}

...

@NgModule({
  ...
  providers: [
    CookieService,
    KeycloakService,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService],
    }
  ],
  ...
})

Like I said, I don't love this. I'd much rather pull from the api on demand, but I haven't figured out yet how to get all of the async calls working on init.

P.S. the checkLoginIframe option is necessary if you're running without SSL (in my case a local env). Some of the Keycloak cookies won't have the proper SameSite policy set otherwise.

DrTeeth
  • 927
  • 2
  • 10
  • 32
0

I answer my own question here :

I used this library to specify providers order : "ngx-ordered-initializer": "1.0.0"

in app.module.ts :

    providers: [
    KeycloakService,
    {provide: ORDERED_APP_INITIALIZER, useFactory: initializerConfig, deps: [CommonService], multi: true},
    {provide: ORDERED_APP_INITIALIZER, useFactory: initializer, deps: [KeycloakService, CommonService], multi: true},
    ORDERED_APP_PROVIDER
    },
  ]

    export function initializer(keycloak: KeycloakService, commonService: CommonService): () => Promise<any> {
  return (): Promise<any> => {
    return new Promise<void>(async (resolve, reject) => {
      try {
        await keycloak.init({
          config: commonService.customKeyCloakConfig,
          loadUserProfileAtStartUp: false,
          initOptions: {
            checkLoginIframe: false
          },
          enableBearerInterceptor: true,
          bearerExcludedUrls: ['']
        }).then((isInitialize) => {
          console.log("keycloak is initialized : ", isInitialize)
        })
        resolve();
      } catch (error) {
        console.log("error happened during Keycloak initialization : ", error)
        reject(error);
      }
    });
  };
}

in commonservice :

customKeyCloakConfig: any;

  constructor(private apiService: ApiService, private config: ConfigService) {
  }

  public keycloakConfig(){
    return new Promise((resolve, reject) => {
      this.apiService.get<any>(this.config.KEYCLOAK_CONFIG_CUSTOM).subscribe(res => {
        this.customKeyCloakConfig = res;
        resolve(true);
      })
    })
  }
batmaniac
  • 392
  • 4
  • 22