How to initialize msal configurations using APP_INITIALIZER
See original GitHub issueLibrary
-
msal@1.x.xor@azure/msal@1.x.x -
@azure/msal-browser@2.x.x -
@azure/msal-angular@0.x.x -
@azure/msal-angular@1.x.x -
@azure/msal-angularjs@1.x.x
Documentation location
- docs.microsoft.com
- MSAL.js Github Wiki
- README file
- Other (please fill in)
- Documentation does not exist
Description
I found that most of the developers are facing issues in loading msal configurations from a config service/ app settings service, Even i struggled for couple of days. But finally I was able to achieve this.
Here is my solution:
config.service.ts:
import { Injectable } from '@angular/core';
import { HttpClient, HttpBackend } from '@angular/common/http';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ConfigService {
private settings: any;
private http: HttpClient;
constructor(private readonly httpHandler: HttpBackend) {
this.http = new HttpClient(httpHandler);
}
init(endpoint: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
this.http.get(endpoint).pipe(map(res => res))
.subscribe(value => {
this.settings = value;
resolve(true);
},
(error) => {
reject(error);
});
});
}
getSettings(key?: string | Array<string>): any {
if (!key || (Array.isArray(key) && !key[0])) {
return this.settings;
}
if (!Array.isArray(key)) {
key = key.split('.');
}
let result = key.reduce((acc: any, current: string) => acc && acc[current], this.settings);
return result;
}
}
Note config.service.ts, constructor, in this we are not injecting HttpClient, because if you inject HttpClient then angular first resolve all the HTTP_INTERCEPTORS, and when you use MsalInterceptor in app module, this makes angular to load MsalService and other component used by Msalinterceptor load before APP_INITIALIZER. To resolve this issue we need to by pass HTTP_INTERCEPTORS, so for this we can use HttpBackend handler, and then create local instance of HttpClient in config service constructor. This will bypass the HTTP_INTERCEPTORS, while getting config file.
msal-application.module.ts
import { InjectionToken, NgModule, APP_INITIALIZER } from '@angular/core';
import {
MSAL_CONFIG,
MSAL_CONFIG_ANGULAR,
MsalAngularConfiguration
, MsalService, MsalModule, MsalInterceptor
} from '@azure/msal-angular';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ConfigService } from '../config.service';
import { Configuration } from 'msal';
const AUTH_CONFIG_URL_TOKEN = new InjectionToken<string>('AUTH_CONFIG_URL');
export function initializerFactory(env: ConfigService, configUrl: string): any {
// APP_INITIALIZER, except a function return which will return a promise
// APP_INITIALIZER, angular doesnt starts application untill it completes
const promise = env.init(configUrl).then((value) => {
console.log(env.getSettings('clientID'));
});
return () => promise;
}
export function msalConfigFactory(config: ConfigService): Configuration {
const auth = {
auth: {
clientId: config.getSettings('clientID'),
authority: config.getSettings('authority'),
redirectUri: config.getSettings('redirectUri')
},
cache: {
cacheLocation: config.getSettings('cacheLocation')
}
};
return (auth as Configuration);
}
export function msalAngularConfigFactory(config: ConfigService): MsalAngularConfiguration {
const auth = {
unprotectedResources: config.getSettings('unprotectedResources'),
protectedResourceMap: config.getSettings('protectedResourceMap'),
};
return (auth as MsalAngularConfiguration);
}
@NgModule({
providers: [
],
imports: [MsalModule]
})
export class MsalApplicationModule {
static forRoot(configFile: string) {
return {
ngModule: MsalApplicationModule,
providers: [
ConfigService,
{ provide: AUTH_CONFIG_URL_TOKEN, useValue: configFile },
{ provide: APP_INITIALIZER, useFactory: initializerFactory,
deps: [ConfigService, AUTH_CONFIG_URL_TOKEN], multi: true },
{
provide: MSAL_CONFIG,
useFactory: msalConfigFactory,
deps: [ConfigService]
},
{
provide: MSAL_CONFIG_ANGULAR,
useFactory: msalAngularConfigFactory,
deps: [ConfigService]
},
MsalService,
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
}
]
};
}
}
Create a config.json file:
{
"clientID": "xxxx",
"authority": "https://login.microsoftonline.com/xxxx",
"redirectUri": "http://localhost:4200/",
"cacheLocation": "localStorage",
"protectedResourceMap": [
["xxxxxx", ["xxxxxx/.default"]]
],
"extraQueryParameters": "xxxxx"
}
Now use this MsalApplicationModule in app.module.ts file, imports section as:
MsalApplicationModule.forRoot('config.json')
Now use MsalService in app.component.ts file as per the sample provided by the authors of this library.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:4
- Comments:9 (3 by maintainers)
Top Related StackOverflow Question
Thank you for suggestion, I handle it exactly the same as in settings loader, I have replace existing translation loader with my own.
Hello, I have spent few days trying to make it work, but all the time msal config factory methods are invoked before the config file is recived. Where is the crucial part which wait until promise from config factory is returned? I am using Angular 9 and “@azure/msal-angular”: “^1.0.0-beta.5” app.settings.service.ts
msal-application.module.ts