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
AADSTS501051 <ClientId> is not assigned to a role for the application への対処
出るたびに対応を忘れて調べているのでメモ。
Microsoft.Identity.Client
のConfidentialClientApplicationBuilder
でシークレットを使用したアクセストークンの取得を行いたいときに発生する場合の対応です。
はじめに
2021年7月時点の情報です。
参照時期によっては記事内の画面キャプチャや設定内容が異なっている可能性があるのでご留意ください。
何が起きているのか
エラーは下記のような内容です。
MsalUiRequiredException: AADSTS501051: Application 'Client Id'(AAD App name) is not assigned to a role for the application 'Scopes'(AAD App name).
僕がよくやる構成の話です。
Entiprise Applications上で認証に使用しているAzure ADアプリケーションの設定で
ユーザー割当必須にしている場合があります。
雑にサインインの制御を行いたい場合(ゲストユーザーのみとかその逆とか)の設定で
シークレット認証の場合ユーザーの情報を伴わない状態なのでエラーになる。というわけです。
対処
App Roleの作成
認証を行いたいAzure ADアプリケーションのApp roles
でアプリケーションのRole作成を行います。
項目 | 設定内容 |
---|---|
Display name | 任意な名前 |
Allowed member types | Applications |
Value | 任意な値 |
Description | Roleの説明文 |
APIのアクセス許可の設定
次にシークレット認証用のAzure ADアプリケーションを用意します。
このアプリケーションはユーザー割当必須の設定は行いません。
API permissions
から認証を行いたいAzureADアプリケーションへのアクセス許可の設定を行います。
先程設定を行った認証を行いたいAzure ADアプリケーションを指定します。
Appliaction permissions
で先程作成したApp Roleを指定します。
シークレットによる認証が可能となる
シークレット認証用のAzure ADアプリケーションでシークレットを生成し確認を行います。
モザイクだらけでアレですが、audが適切な形となっていること
rolesは指定したアプリケーションロールが設定されていることが確認できます。
おわりに
ちょいちょい発生しては対処方法を忘れているAADSTS501051
のメモでした。
Azure Web PubSub ServiceのイベントをトリガーにAzure Funcrtionsを実行する
はじめに
2021年7月時点の内容です。
また、AzureWebPubSubServiceはプレビュー版ということもあり
記載している画面やソースコードや設定内容は参照時点によっては異なっている可能性が高いので
参照される際はご留意ください。
今回は.NETラボのセッション資料中で時間の関係上セッションに落とせなかった部分のメモ書きをこちらに落としておきます。
基本などはイベントでお話しました。
まず基本的なところから抑えたいという方は、資料などをご覧いただければと思います。
PubSubイベントをトリガーにFunctionsを実行したい
Azure Web PubSub Serviceの設定
Azure Web PubSub Serviceで起きたイベントをFunctionsで処理したいと思います。
ドキュメントはこちらから確認できます。
Azure Web PubSub ServiceのSettingsから接続先のURLを指定することで
イベントをトリガーにFunctionsを実行できるようになります。
URLはFunctionsのExtensionを使う場合(エンドポイント + /runtime/webhooks/webpubsub
)となります。
設定内容は大まかに下図のとおりです。
とりあえず動きを確かめたい程度なら、SystemEventsは何も設定しなくてOKです。
Azure Functionsの実装
NuGetでMicrosoft.Azure.WebJobs.Extensions.WebPubSub
を取得します。
Functionsでは下記を実装することでメッセージの送信をトリガーにした処理が可能になります。
Hub=…
で指定された箇所が上図のHub nameと対応する箇所です。
[FunctionName("<Function名>")] public static async Task<MessageResponse> Broadcast( [WebPubSubTrigger(WebPubSubEventType.User, "message")] BinaryData message, [WebPubSub(Hub = "SampleHub")] IAsyncCollector<WebPubSubOperation> operations ) { // なんやかんや }
PubSubServiceで使用されるwssがwss://<resource name>/client/hubs/<hub name>?access_token=...
の形式で
↑のWSSで指定されるhub nameに対する通信のイベントがトリガーとなる。といった流れです。
System Eventsをトリガーにする
設定のSystem Eventsの各項目にチェックを入れた場合は下図のコードでトリガー実行可能となります。
WebPubSubTrigger
の第3引数がそれぞれ、connect/connectedのいずれかになります。
チェックを入れた方(あるいは両方)を実装します。
[FunctionName("<Function名>")] public static ServiceResponse Connect( [WebPubSubTrigger("SampleHub", WebPubSubEventType.System, "connect")] ConnectionContext connectionContext, ILogger log ) { // なんやかんや } [FunctionName("<Function名>")] public static async Task Connected( [WebPubSubTrigger(WebPubSubEventType.System, "connected")] ConnectionContext connectionContext, [WebPubSub] IAsyncCollector<WebPubSubOperation> operations ) { // なんやかんや }
気をつけなければならないこと
当然といえば当然なのですが、message
含めSettingsでURLを指定した場合
トリガーで実行される関数がない場合エラーとなり、Connectやメッセージの送信が失敗するようになります。
さいごに
Azure Web PubSub Serviceで発生する各イベントをトリガーにFunctionsで実行することができ
Connect前後にフローを追加したり、Messageに情報を付加して返却したりなどが簡単にできるようになっていることが確認できました。
発表や今回の記事で使用したサンプルソースは下記となります。
メモ書き-msal.jsでSSOしたらiOSやChromeでエラーが発生したので対応した話
はじめに
下記のライブラリのバージョンで発生したエラーに対する対応となります。
参照される時期によっては対応方法や対応不要になったりと状況が変わっている可能性がありますのでご留意ください。
- @azure/msal-browser: 2.13.1
(状況変わっているのが一番うれしい
iOSやChromeでSSOしたときにエラーが発生する
すでにadal.jsを使用しているWebアプリケーションやほかシステムでログインが行われている前提であった場合msal.jsの ssoSilent
はよく使用する機能かもしれません。
SSO SilentはセッションCookieを利用しているのですが
iOSやChromeのシークレットブラウザを使用した場合サードパーティCookieを参照できないため
ssoSilent
で下記のようなエラーとなってしまいます。
InteractionRequiredAuthError: login_required: AADSTS50058: A silent sign-in request was sent but no user is signed in. The cookies used to represent the user's session were not sent in the request to Azure AD. This can happen if the user is using Internet Explorer or Edge, and the web app sending the silent sign-in request is in different IE security zone than the Azure AD endpoint (login.microsoftonline.com).
シングル サインオン (MSAL.js) - Microsoft identity platform | Microsoft Docs
この問題に対する対応
対応策1:ChromeやiOSの設定を変更する
てっとりばやいのはChromeの下記設定をOffにする
iOSの下記設定をOffにすることです。
しかし、あえてセキュリティ上守られているものを解除する方に寄せていくのもよろしくありません。
ここの設定を変更するのは原因の切り分けを行うときくらいにとどめておいたほうが良いと思っています。
対応策2:エラー発生時にLoginRedirect/LoginPopupを行うように対応する
エラーの内容としては「ちゃんとログインしてくださいね」ということなので、別途ログインするだけで良さそうです。
UserAgentなどで特定ブラウザだけloginRedirect
することも考えましたが
対応しなければいけないブラウザが増える可能性もありますし
ssoSilent
したときでAADSTS50058
が出たときに対応したい。と状況は限定的なので
ssoSilent
でエラーをキャッチしそこでloginRedirect
なりloiginPopup
を行うように対応してみました。
Popupを使用する場合は↓な感じです。
async login(): Promise<AuthenticationResult> { const res = await this.client.ssoSilent({ loginHint: loginhint }).catch((err: AuthError) => { if(err.errorMessage.includes('AADSTS50058')) { return this.client.loginPopup(); } throw err; }); this.account = res.account; return res; }
AADSTS50058
発生しているときだけloginPopup
するようにしています。
Redirectを使用する場合は少し手間です。
RedirectされたときにキャッチするhandleRedirectPromise
をエラー発生時にだけ使用したいからです。
async login(): Promise<AuthenticationResult | null> { const errFlow = sessionStorage.getItem('errflowaction'); if (errFlow) { const res = await this.client.handleRedirectPromise(); if (res) { this.account = res.account; } sessionStorage.removeItem('errflowaction'); return res; } const res = await this.client.ssoSilent({ loginHint: loginhint }).catch((err: AuthError) => { if(err.errorMessage.includes('AADSTS50058')) { sessionStorage.setItem('errflowaction', 'dummy'); this.client.loginRedirect(); return null; } throw err; }); if (res) { this.account = res.account; return res; } return null; }
少し不格好ですが、Redirect前にsessionStorageにFlow状態を記録しておき
それによって動作を変える方法で逃げることにしました。
さいごに
実験に使ったコードは以下に格納しています。
メモ書き - AzureDevOpsで別プロジェクトのCIをトリガーにパイプラインを実行する
はじめに
このメモは2021年1月17日時点のAzure DevOpsを使用したメモになっています。
参照されるタイミングによっては、記事内で称しているキャプチャ・設定内容が変更されている可能性がありますのでご注意ください。
やりたいこと
Docsみててこんがらがったので自分用メモ・・・
Azure DevOpsで複数のプロジェクトを構築。
各プロジェクトの構成は下な感じで。
- 統合プロジェクト
- サブプロジェクト1
- サブプロジェクト2
- ...
サブプロジェクトでライブラリ作成→統合プロジェクトでサブプロジェクトのライブラリを使用してクライアントアプリの開発。といった構成。
サブプロジェクトxのCIが成功→統合プロジェクトのCIを実行 な感じで動作させたい。
統合プロジェクトのパイプラインの構築
サブプロジェクト側のパイプラインは今まで通りの構成で問題なし。
統合プロジェクト側のyamlの頭に👇の構成。
trigger: none resources: pipelines: - pipeline: subProjectPipeline # パイプラインの名称。このCIからアクセスするときの識別子 source: 'integration-pipeline-two (1)' # トリガーするパイプライン project: integration-pipeline-two # 別プロジェクトにあるCIの場合はこれを指定 trigger: branches: - master
サブプロジェクトでPublisしたものをDownloadしたいときは👇でDLできる。
- download: subProjectPipeline # pipelineで指定した識別子 artifact: drop # Artifact名
補足
Releasesを使用する場合、👇の2つをOffにしないと権限なしでサブプロジェクトでPublishされたリソースにアクセスできないとかあったけど、Pipelinesではそういうものはなさげ。
(まぁReleases Pipeline特有の設定っぽいからそらそうなんだけど、PipelineのほうはこういうのOffにする必要ないんだ。楽だなぁ。と。)
参考にしたサイト等
- Trigger one pipeline after another
- Resources in YAML - Resources: pipelines
- Trigger one pipeline AFTER another in Azure Pipelines
実験で使用したAzure DevOpsプロジェクト
Azure DevOpsのPipelineでOWASP ZAPを実行してみる
はじめに
2020年12月時点の情報で記事を作成しています。
参照される時期によっては、記事内で使用されているコマンド、画面キャプチャが使用できなくなっている可能性がありますのでご留意ください。
Azure PipelineでOWASP ZAPを実行したい
だいぶ前にGitHubActionsでOWASP ZAPのScanができるようになりました。
例のごとくAzurePipelineでは使用できません。
Azure Pipeline上でOWASP ZAPのスキャンを使用してみましょう。
ZAP Dockerを使用する
Pipeline上でDockerコマンドが実行できるのでそれを使用してPipeline上で脆弱性チェックを行います。
と、いってもZAP Dockerのコマンド等の類の説明は山程あると思いますので省きます。
今回はDocker内で提供されているFullScanを使用しますが
細かい機能を使用したい場合はPowerShell経由でZAP APIを叩いて
Context作成→ContextにURL追加→Spider実行ないつもの流れを行えばいいと思います。
OWASP ZAP2.7でzap-API を使ってSpiderの実行 - 備忘録/にわかエンジニアが好きなように書く
WebAPIをPowerShellからテストする - Qiita
Azure Pipeline上で実行する
すでにPipeline上で実行する記事を書かれている方がいるのでそれを参考にしてみます。
How to run OWASP ZAP Security Tests Part of Azure DevOps CI/CD Pipeline
この記事ではReleaseパイプライン上で実行されているので、MultiStagePipeline上で実行できるようにいじってみます。
また、結果レポートはAzure Artifactsに格納されていますが、すこしアクセスしづらいのでBLOB上に格納してみます。
Pipeline構成
下記な構成のymlとなります
- SPAのWebApplicationをビルド
- Angularアプリケーションをビルドします
- WebAppsにデプロイ 1, デプロイ先のWebAppsの脆弱性調査+Report出力
trigger: branches: include: - master stages: - stage: build jobs: - job: build_job displayName: Build Angular pool: vmImage: ubuntu-latest steps: - task: npm@1 displayName: npm ci inputs: command: custom customCommand: 'ci' - task: npm@1 displayName: npm build inputs: command: custom customCommand: 'run build:ci' - task: ArchiveFiles@2 displayName: 'Archive dist/pipeline-learn-front' inputs: rootFolderOrFile: 'dist/pipeline-learn-front' includeRootFolder: false archiveFile: '$(Build.ArtifactStagingDirectory)/drop.zip' - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact: drop' - stage: deploy dependsOn: build jobs: - deployment: deploy_webapp displayName: Deploy WebApp environment: deploy strategy: runOnce: preDeploy: steps: - download: current artifact: drop deploy: steps: - task: AzureRmWebAppDeployment@4 inputs: ConnectionType: 'AzureRM' azureSubscription: '***' appType: 'webApp' WebAppName: '***' packageForLinux: '$(Pipeline.Workspace)/**/*.zip' - stage: security_test dependsOn: deploy jobs: - job: security_test displayName: SecurityTest pool: vmImage: ubuntu-latest steps: - task: DockerInstaller@0 inputs: dockerVersion: '17.09.0-ce' - task: Bash@3 inputs: targetType: 'inline' script: | chmod -R 777 ./ docker run --rm -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable zap-full-scan.py -t https://okawa-test-webapp.azurewebsites.net/ -j -g gen.conf -x OWASP-ZAP-Report.xml -r scan-report.html true - task: PowerShell@2 inputs: targetType: 'inline' script: | $XslPath = "$($Env:SYSTEM_DEFAULTWORKINGDIRECTORY)/OWASPToNUnit3.xslt" $XslPath $XmlInputPath = "$($Env:SYSTEM_DEFAULTWORKINGDIRECTORY)/OWASP-ZAP-Report.xml" $XmlInputPath $XmlOutputPath = "$($Env:SYSTEM_DEFAULTWORKINGDIRECTORY)/Converted-OWASP-ZAP-Report.xml" $XmlOutputPath $XslTransform = New-Object System.Xml.Xsl.XslCompiledTransform $XslTransform.Load($XslPath) $XslTransform.Transform($XmlInputPath, $XmlOutputPath) - task: PublishTestResults@2 inputs: testResultsFormat: 'NUnit' testResultsFiles: 'Converted-OWASP-ZAP-Report.xml' searchFolder: '$(System.DefaultWorkingDirectory)' - task: AzurePowerShell@5 inputs: azureSubscription: '***' ScriptType: 'InlineScript' azurePowerShellVersion: latestVersion Inline: | $storage = Get-AzStorageAccount -ResourceGroupName "vse-sandbox" -Name "***" $ctx = $storage.Context $containerName = "zap-result" Set-AzStorageBlobContent -File "$($Env:SYSTEM_DEFAULTWORKINGDIRECTORY)/scan-report.html" -Container $containerName -Blob "scan-report.html" -Context $ctx
結果
AzureのCIレポートでテスト結果を確認できるように、レポート出力されたXMLファイルをNUnit形式に変換しています。
結果、下図のようにCIのレポートで発見された脆弱性のレポートを確認できるようになっている感じです。
HTMLで出力されたレポートを見たい場合はBLOBからですね。
Azure Artifactsに上げる場合のハマりどころ
今回はBLOBにあげてみましたが、参考サイトにある通りAzure Artifactsにあげようとした場合にハマったポイントがありました。
準備段階で、AzureArtifactsにaz artifacts universal
を使用してArtifactsを作っているのですが
azコマンドからは403が出てしまいPublishできないといった現象がおきました。
vsts CLIを使用した場合にはエラーは発生しなかったので、azコマンドでエラーが発生した場合はvsts CLIを使用してみるといいかもしれません。
まとめ
今回は雑にCIに組み込めるか程度のレベルで試してみました。
AngularアプリのようなSPA構成のアプリの場合は単純なSpiderではなくAjaxスパイダーを使用したり
試行時間を決定しないと永遠に終わらなかったり…と考慮することは多そうなので
実際にしっかり運用するとなったら直接APIを叩いてガリガリ組んでいくしかないかなと思います。
と言っても、知らん間にガチ脆弱性チェックを行ってデータが壊れるのも色々嫌な感じですし
サーバーの設定レベルだけチェックするような現在の構成のほうが、自動実行するレベルとしては扱いやすいのかもしれないですね。
SubscriptionのLifecycleNotificationUrlをいじってみた
Subsriptionのライフサイクル通知が存在するようです。
Docsの履歴を見る感じだと機能自体は2020年の7月くらいに使用できるようになってたみたいなのですが
Microsoft Graphの変更ログに上がってきていなかったため気づくことができなかったようです。無念。
さて、その機能のおかげでSubscriptionの通知のライフサイクルの管理問題が解決することができるかも…?と思ったので試してみました。
(通知のライフサイクルが切れたり…な管理を楽にできれば良いなー。。。的な
詳細な使い方を見ていこうと思います。
ライフサイクル管理を行う
Subsriptionを作成する際にライフサイクル通知の通知先としてlifecycleNotificationUrl
を指定。
この際、通常の変更通知でやるのと同様に、エンドポイントの検証を実装。
加えて、通常の通知先とライフサイクル通知の通知先は同じホスト名を指定する必要があるようです。
既存のSubscriptionにPATCHメソッドを使用してライフサイクル通知先を指定することはできないようなので
既存の変更通知がある場合は作成し直すしかなさそうです。
さて、例のごとくFunctionsに通知先を作成し、動作を確認してみようと思います。
ホスト名が違う状態を指定してみる
どんなエラー出るのか試したかったんですが…できちゃったんですよねぇ…
Functionsのログを見てみます。
ResourceNotifications
(通常の通知先)は問題なく通知が来ているようですので別エンドポイントでもワンチャン動くかもしれません。
ひとまず以降の処理でおかしなことになるかもなので、同一のエンドポイントにしてから検証を続行していきます。
どんなときにLifecycle通知が発生するのか色々試してみる
有効期限後のログを見てみる
"expirationDateTime": "2020-11-03T03:30:00Z",
を指定しているので12:30以降にどのようなログが出力されるか確認してみます。
- 変更通知作成
- 通知時間切れまでまつ
- 予定情報変更
有効期限後についてはLifecycle通知は行われませんでした。
有効期限切れも通知してくれたらexpirationDateTime
の管理も楽になると思ったのですが残念ですね。
手動でSubscriptionを削除してみる
手動で作成した変更通知を削除したあと、Lifecycle通知が捕捉できるか試してみます。
- 変更通知作成
- 1.で作成した変更通知を削除
- 予定情報変更
手動削除もLifecycle変更通知は捕捉できませんでした。
ユーザーのパスワードを変更/削除してみる
Documentには下記の条件のときのみに発生すると記載されています。
- ユーザーのパスワードがリセットされた場合
- ユーザーのデバイスが準拠しなくなった場合
- ユーザーのアカウントが取り消された場合
意図しないSubscriptionの削除を補足する。といった機能のようですね。
そこでユーザーのパスワードを変更する方法で動作を検証してみます。
変更通知は下記の通りの内容で設定します。特定ユーザーの予定作成/変更時です。
このユーザーのパスワードをリセットを行うことでLifecycleNotificationが呼び出されるか検証します。
順序としては下記のとおりです。
- 変更通知作成
- 変更通知を作成したユーザーのパスワードリセット
- 予定情報変更
結果、下記のようなJSONがLifecycleNotification側に通知されました。
{ "value": [ { "subscriptionId": "6482c2c2-96ba-4505-b375-4329c1aad3d4", "subscriptionExpirationDateTime": "2020-11-24T16:00:00-08:00", "clientState": "<state>", "tenantId": "<tenantId>", "lifecycleEvent": "subscriptionRemoved" } ] }
通常のNotification側への変更通知は発生せず、Lifecycleの通知だけ発生していることも確認できました。
Microsoft365側の都合による変更通知の削除によってLifecycle通知が発生することが確認できました。
通常通知先を潰してみる
通知先を潰してmissed
を捕捉できるか試してみました。
AzureFunctionsに公開しているリソースエンドポイントをリネームして公開しなおしただけです。
ただ、これは捕捉できませんでした。
Document的には、Microsoft365側で配信されなかった変更通知があった場合に呼び出されるようです。
ただ、こちらは先のように具体的な例がなかったのでどのような状況の時に実行されるかはわかりませんでした。
としたら通知されてない2のデータがでてくる?と思ったけど出てきませんでした。
Microsoft365内で何かしら起きて変更通知の整合性が合わなくなったときに通知されるものかもですね。
まとめ
サブスクリプションと変更通知の消失を減らすで紹介されていた動作を見てきました。
動作させてみて分かった通り、Microsoft365側からのアクションで、動作しなくなった変更通知の通知ということがわかりました。
Microsoft365側からのアクションによる変更通知未実行というのは、補足しにくいものではあるので一定の効果はあると思います。
ただ、通知の有効期限切れや通知先の消失なんかの問題は変わらず自分で管理しないといけない。という状況です。
まだまだApplication Insightなどを使用したり、定期的に変更通知の状況を監視するといった工夫は必要そうです。