I am in the process of updating from okta/okta-angular 3.x to 5.x. It seems to have introduced an odd bug.
When the app first starts up, we have been using APP_INITIALIZER to execute appInitializerFactory(configService: ConfigService)
, which makes an http call to load configuration data.
The call looks like this:
public async loadConfig(): Promise<any> {
return this.httpClient.get('assets/config.json').pipe(settings => settings)
.toPromise()
.then(settings => {
this.config = settings as IAppConfig;
})
.catch(exception => {
console.log("Exception encountered while retreiving configuration");
});
}
Before updating to okta 5.x, the APP_INITIALIZER has been awaiting the promise. Now, it appears that other providers are being resolved before the promise in APP_INITILIZER has finished.
The resulting issue happens downstream at the oktaInitializerFactory
, where it runs the following code:
public oktaConfig() {
return Object.assign({
onAuthRequired: (oktaAuth: OktaAuth, injector: Injector) => {
const router = injector.get(Router);
router.navigate(['/login']);
}
}, this.config.oktaConfig);
}
On the last line, this.config.oktaConfig
is returning as undefined
because the APP_INITIALIZER has not finished awaiting.
Heres the complete app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { Routes, RouterModule, Router } from '@angular/router';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { MaterialModule } from './modules/material/material.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TablesComponent } from './components/tables/tables.component';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { LoginComponent } from './components/login/login.component';
import { FormsModule } from '@angular/forms';
import { RunLogComponent } from './components/run-log/run-log.component';
import { RunDataComponent } from './components/run-data/run-data.component';
import { ActionComponent } from './components/action/action.component';
import { ErrorsComponent } from './components/errors/errors.component';
import { OktaAuth } from '@okta/okta-auth-js';
import { OKTA_CONFIG, OktaAuthGuard, OktaAuthModule, OktaCallbackComponent } from '@okta/okta-angular';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { TabulatorUserTableComponent } from './components/tabulator-user-table/tabulator-user-table.component';
import { TabulatorTableComponent } from './components/tabulator-table/tabulator-table.component';
import { DataLakeComponent } from './components/data-lake/data-lake.component';
import { IntegrationHistoryComponent } from './components/integration-history/integration-history.component';
import { IntegrationStatusComponent } from './components/integration-status/integration-status.component';
import { MatSpinnerButtonComponent } from './components/mat-spinner-button/mat-spinner-button.component';
import { ConfigService } from './services/config.service';
import { DataStudioComponent } from './components/data-studio/data-studio.component';
import { IntegrationDashboardComponent } from './components/integration-dashboard/integration-dashboard.component';
import { IntegrationSelectorToolbarComponent } from './integration-selector-toolbar/integration-selector-toolbar.component';
import { PrimaryButtonsComponent } from './components/primary-buttons/primary-buttons.component';
import { UserCardComponent } from './components/user-card/user-card.component';
import { HttpOktaInterceptorService } from './services/http-okta-interceptor.service';
import { DebugInfoComponent } from './components/debug-info/debug-info.component';
import { ModalModule } from './modal';
import { DragNDrop } from './components/dropbox/drag-n-drop';
import { ProgressComponent } from './components/dropbox/progress/progress.component';
const appRoutes: Routes = [
{
path: '',
component: DashboardComponent,
canActivate: [OktaAuthGuard]
},
{
path: 'login/callback',
component: OktaCallbackComponent
},
{
path: 'login',
component: LoginComponent
}
];
@NgModule({
declarations: [
AppComponent,
TablesComponent,
PageNotFoundComponent,
DashboardComponent,
ToolbarComponent,
LoginComponent,
RunLogComponent,
RunDataComponent,
ActionComponent,
ErrorsComponent,
TabulatorUserTableComponent,
TabulatorTableComponent,
DataLakeComponent,
IntegrationHistoryComponent,
IntegrationStatusComponent,
MatSpinnerButtonComponent,
DataStudioComponent,
IntegrationDashboardComponent,
IntegrationSelectorToolbarComponent,
PrimaryButtonsComponent,
UserCardComponent,
DebugInfoComponent,
ProgressComponent,
DragNDrop
],
imports: [
BrowserModule,
OktaAuthModule,
RouterModule.forRoot(appRoutes, { relativeLinkResolution: 'legacy' }),
BrowserAnimationsModule,
MaterialModule,
FormsModule,
HttpClientModule,
ModalModule
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: appInitializerFactory,
deps:[ConfigService],
multi: true
},
{
provide: OKTA_CONFIG,
useFactory: oktaInitializerFactory,
deps:[ConfigService],
},
{
provide: HTTP_INTERCEPTORS,
useClass: HttpOktaInterceptorService,
multi: true,
deps: [OktaAuth]
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
export function appInitializerFactory(configService: ConfigService) {
return () => configService.loadConfig();
}
export function oktaInitializerFactory(configService: ConfigService) {
return configService.oktaConfig();
}
Is there a particular reason why my APP_INITIALIZER isn't finishing before other code executes? When I downgrade back to okta 3.x, this issue goes away.
As it turns out, upgrading from Okta 3
to 4+
or 5+
does introduce a change that makes loading auth server configuration in APP_INITIALIZER
a non-viable option.
In a nutshell, okta-angular will attempt to connect to the auth server before the APP_INITIALIZER
finishes. The workaround for this is to load the configuration data into the injector in main.ts
, which fires off before the APP_INITIALIZER
. This technically goes against the documentation in angular.io , but Okta support has verified this behavior [ Source ]
Another user does a similar implementation for Auth0, which encountered the same problem, linked here: stackoverflow.com/a/66957293/3202440
I have no idea how it was supposed to work in previous versions. you have appInitializerFactory
that depends on ConfigService
. ConfigService
depends on HttpClient
. HttpClient
=> HTTP_INTERCEPTORS
ie on HttpOktaInterceptorService
. HttpOktaInterceptorService
=> OktaAuth
which most likely relies on OKTA_CONFIG
.
It means that to construct a HttpClient
(which is implicitly requried for initializer) you need OKTA_CONFIG
already constructed. and it sounds very logical that this config initializer is being called earlier than APP_INITIALIZER
s.
Most likely this dependency was introduced during refactoring, not just the update.
In your place I would try to eliminate dependency on a HttpClient
in ConfigService
and just make this request with a native api
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.