3

I went through several questions like 1 , 2 but I don't know how to make my app work.

Problem: When I sign in 1st time, I do not get Bearer token and hence my SettingConfigService fails with 401, if I refresh the page, I get the token from this.oauth.getAccessToken() because now the token is in localstorage.

I am using oauth lib for login. Here is the modules and libs I have created.

App.module

 export function AppConfigurationFactory(config: SettingConfigService) { return async () => await config.ensureInit(APP_NAME); } export class AppConfig { baseUrl: string; production: boolean; } export const appConfig: AppConfig = { baseUrl: environment.baseUrl, production: environment.production, }; @NgModule({ exports: [], declarations: [ ], imports: [ .... CustomAuthModule.forRoot(environment.keycloak), CustomInfrastructureModule.forRoot({ appConfig }), SharedModule, ], providers: [ { provide: AppConfig, useValue: appConfig }, ... { provide: APP_INITIALIZER, useFactory: AppConfigurationFactory, deps: [ SettingConfigService, HttpClient, TranslateService, OAuthService], multi: true, }, ], }) export class AppModule {} 

CustomAuthModule.ts

import { NgModule, APP_INITIALIZER, Optional, SkipSelf, ModuleWithProviders, InjectionToken } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { OAuthModule, AuthConfig } from 'angular-oauth2-oidc'; import { OAuthModuleConfig,CustomAuthConfigParams } from './auth.config'; import { AuthConfigService } from './auth.service'; export function init_app(authConfigService: AuthConfigService) { return () => authConfigService.initAuth(); } @NgModule({ imports: [HttpClientModule, OAuthModule.forRoot()] }) export classCustomAuthModule { constructor(@Optional() @SkipSelf() parentModule:CustomAuthModule){ if(parentModule){ throw new Error('QontrolAuthModule is already loaded.'); } } static forRoot(keycloakParams): ModuleWithProviders<QontrolAuthModule> { return { ngModule:CustomAuthModule, providers: [ AuthConfigService, OAuthModuleConfig, { provide: AuthConfig, useValue: keycloakParams }, { provide: APP_INITIALIZER, useFactory: init_app, deps: [AuthConfigService], multi: true, }, ] } } } 

CustomInfrastrucutureModule.ts

@NgModule({ declarations: [], imports: [CommonModule], exports: [], providers: [], }) export class CustomInfrastructureModule { static forRoot(conf?: { appConfig: SharedInfrastructureAppConfig; }): ModuleWithProviders<CustomInfrastructureModule> { return { ngModule: CustomInfrastructureModule, providers: [ { provide: APP_CONFIG, useValue: conf.appConfig }, { provide: LOCALE_ID, deps: [SettingConfigService], // some service handling global settings useFactory: (config: SettingConfigService) => config.culture }, ], }; } } 

SettingConfigService

 @Injectable({providedIn: 'root'}) export class SettingConfigService { culture: string; config: any; constructor( private httpClient: HttpClient, @Inject(APP_CONFIG) protected appConfig: SharedInfrastructureAppConfig, private oauth: OAuthService ) { } async ensureInit(clientPrefix: string): Promise<void>{ console.log(this.oauth.getAccessToken()); //<-- comes as null when 1st login // putting a wait time of 1 sec as per https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep makes it work, // because by that time, we have the token in localStorage const response = await this.httpClient.get<any>(`${this.appConfig.baseUrl}/configs`).toPromise(); this.config = response; } } 

Here is my code which fetched the token using oauth2-oidc which is called from

AuthConfigService

 async initAuth(): Promise<any> { return new Promise((resolveFn, rejectFn) => { this.oauthService.configure(this.authConfig); // Redirect to path, if there is one if (window && window.location && window.location.pathname) { this.oauthService.redirectUri = window.location.origin + window.location.pathname; } this.oauthService.setStorage(localStorage); this.oauthService.tokenValidationHandler = new NullValidationHandler(); this.oauthService.events .pipe( filter((e: any) => { return e.type === 'token_received'; }) ) .subscribe(() => this.handleNewToken() // <-- this takes time to get triggered and meanwhile // the call to SettingConfigService is made ); this.oauthService.loadDiscoveryDocumentAndLogin().then((isLoggedIn) => { if (isLoggedIn) { this.oauthService.setupAutomaticSilentRefresh(); resolveFn(() => {}); } else { console.log(this.oauthService.getAccessToken()) this.oauthService.initImplicitFlow(); console.log(this.oauthService.getAccessToken()) rejectFn(); } }); }); } 

In short,

I need to synchronize APP_INITIALIZER of app.module.ts to wait for token of APP_INITIALIZER of CustomAuthModule, and then it'll have a bearer token (which gets added by interceptor). Is my understanding correct? Please help

1 Answer 1

2
+300

You need to follow the proper sequence to load config with the token. try:

app.module

@NgModule({ exports: [], declarations: [ ], imports: [ .... CustomAuthModule.forRoot(environment.keycloak, clientPrefixConstant), CustomInfrastructureModule.forRoot({ appConfig }), SharedModule, ], providers: [ { provide: AppConfig, useValue: appConfig }, ... ], }) export class AppModule {} 

in AuthModule

 export const CLIENT_NAME = new InjectionToken<string>('CLIENT_PARAM'); export function init_app(authConfigService: AuthConfigService, settingSvc: SettingConfigService, clientPrefix: string ) { return () => authConfigService.initAuth().then(async () => { await settingSvc.ensureInit(clientName); }); } @NgModule({ imports: [HttpClientModule, OAuthModule.forRoot()] }) export classCustomAuthModule { .... static forRoot(keycloakParams, clientPrefix): ModuleWithProviders<QontrolAuthModule> { return { ngModule:CustomAuthModule, providers: [ AuthConfigService, OAuthModuleConfig, { provide: AuthConfig, useValue: keycloakParams }, { provide: CLIENT_NAME, useValue: clientPrefix }, { provide: APP_INITIALIZER, useFactory: init_app, deps: [AuthConfigService,SettingConfigService,CLIENT_NAME], multi: true, }, ] } } } 

What was happening in your code is that both the APP_INITIALIZER are called in parallel, which resulted in unavailability of token. With my suggested answer, you resolve the token 1st and then you call ensureInit. With InjectionToken,I accepted the string value which was required to call the function.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer. I'll try this out and let you know

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.