MSAL for Angular v2がGAしたので触ってみたお話
はじめに
この記事は下記の内容でお送りいたします。
- Angular: v12.1
- @azure/msal-angular: 2.0.1
参照時期によっては記述しているサンプルコードで動かないない可能性がありますので
その点ご留意ください。
@azure/msal-angularのv2がGAしました
@azure/msal-browserがGAしてから長らくプレビュー状態でしたが
Angularに対応したライブラリがGAされました。
MSAL for Angular v2 is now available - Microsoft 365 Developer Blog
なので、さっそく触ってみたお話になります。
@azure/msal-angular for Azure AD B2C
AADやMS Graphを使用したサンプルはDocsやGitHubのリポジトリにありますので
今回はAzure AD B2CとAzure AD B2Cで保護したASP.NET Core WebAPIへのアクセスを実装してみようと思います。
ライブラリのインストール
npm i @azure/msal-browser @azure/msal-angular
msalの設定情報
まずはコアとなるPublicClientApplication
に食わせるための設定情報の作成です。
サンプルでは直接PublicClientApplication
に渡してあるパターンが多いですが、僕はenvironment.ts
派です。
export const environment = { production: false, msalConfig: { auth: { clientId: '<AD B2C AppのClientId>', authority: 'https://<domain>.b2clogin.com/<domain>.onmicrosoft.com/<Signup/in Policy>', redirectUri: 'http://localhost:4200', knownAuthorities: [ '<domain>.b2clogin.com' ] } }, msalInterceptorConfig: [ { resource: '/base-api/', scopes: ['<WebAPIのAzure AD B2Cスコープ>'] } ] };
基本はmsal-browserの設定内容と同じです。Angularで特殊な箇所はmsalInterceptorConfig
部分でしょう。
AngularのHTTP_INTERCEPTORのuseClassで使用できるMsalInterceptor
が提供されています。
resourceで指定されたWebAPI宛の通信をHttpClientで行うときに、scopesで指定されたスコープのアクセストークンを取得/通信に付与してくれます。
まいどまいどアクセストークンを取得する処理なんかは書きたくないですし非常に重宝すると思います。
AppModuleの実装
ライブラリが提供するInjectionTokenに対して設定値やPublicClientApplication
インスタンスを設定してあげます。
(インジェクショントークンにこのインスタンスを渡してあげるというのはちょっと意外な構成でした)
// environment.tsの設定値を利用してPublicClientApplicationインスタンスを作成する const msalInstanceFactory = (): IPublicClientApplication => { return new PublicClientApplication(environment.msalConfig); } // environment.tsの設定値を利用してMsalInterceptorConfiguration を作成する const mealInterceptorConfigFactory = (): MsalInterceptorConfiguration => { const protectedResourceMap = new Map<string, Array<string>>(); const conf = environment.msalInterceptorConfig; conf.forEach(f => { protectedResourceMap.set(f.resource, f.scopes); }); return { interactionType: InteractionType.Popup, protectedResourceMap } } @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule ], providers: [ { provide: MSAL_INSTANCE, useFactory: msalInstanceFactory // インスタンスを設定 }, { provide: MSAL_INTERCEPTOR_CONFIG, useFactory: msalInterceptorConfigFactory // インターセプターの設定情報を渡す }, { provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, // インターセプターをProvideする multi: true }, MsalService, // MsalServiceをProvideする MsalBroadcastService // MsalBroadcastServiceをProvideする(ログとか出したい場合) ], bootstrap: [AppComponent] }) export class AppModule { }
AppComponentの実装
サンプルなんかではログインはボタン押下など明確なアクションをトリガーに実行されるのですが
僕がよくやる実装なんかは、ngOninit時点でログイン済みか検証→ログイン情報なかったらloginRedirect()
みたいな実装にすることが多いです。
なのでそのやり方で実装してみようと思います。
export class AppComponent implements OnInit { constructor( private httpClient: HttpClient, private authService: MsalService ) {} ngOnInit() { this.authSettingInit(); } private async authSettingInit() { await this.authService.instance.handleRedirectPromise(); // ↑がないとエラー吐く this.authService.handleRedirectObservable().subscribe(x => { if (x) { console.log({handleRedirectObservable: x}) } }); const ac = this.authService.instance.getAllAccounts(); if (ac && ac.length > 0) { return; } this.authService.loginRedirect(); } request(): void { this.httpClient.get('/base-api/WeatherForecast').subscribe(x => { console.log(x); }); } }
すこし特殊なのが諸々の実装が走る前にawait this.authService.instance.handleRedirectPromise();
をしている点ですね。
これがないとどうなるかというと下記のようなエラーがログイン後に発生します
BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
handleRedirectObservable
が諸々の準備が完了する前に呼ばれているからのようです。
対応としては、上のソースで記述している通りで、MsalServiceはPublicClientApplicationのインスタンスをinstance
に持っているようなので
それのhandleRedirectPromise
でawaitしてあげると安定して動作します。
というかhandleRedirectPromise
を実行している時点でhandleRedirectObservable
はする必要がなくなります。
なので、今回のパターンのようにロード時に即ログイン検証+ログイン処理を行う場合はhandleRedirectObservable
ではなくhandleRedirectPromise
を使用したほうが良さそうです。
WebAPIと通信
通信を行っている部分は上記ソースコードのrequest
メソッドです。
/base-api/
宛の通信のときに指定スコープのアクセストークンを取得し通信する処理が
HTTP_INTERCEPTOR
で指定したMsalInterceptor
で実行されます。
WeatherForecast
WebAPI宛の通信にAuthorizationヘッダが付与され、200で返却されることが確認できます。
おわりに
@azure/msal-angular
のAzure AD B2Cでの実装の基本をざっと見てみました。
今回はさわりませんでしたがGuardもあり、v1のときに提供されていたAngularライブラリと同じような使用感で使うことができる感触です。
認証周りはmsal-browserをちょこちょこラップするのもなにかと手間なのでAngularを使用している場合は積極的に活用するのが良いと思います。
今回検証で使用したコードは下記となります。サンプルごった煮の一部となっていますm(__)m