はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

AngularのRouterでuseHashをtrueにしているときにmsalで`Error: Cannot match any routes. URL Segment: 'access_token'`が出るときの対処

はじめに

2020年7月時点の記事となります。

msal.jsのv2が出たのですが、書きかけだった記事の供養となります。。ご留意ください。

Angular・msal.js・@azure/msal-angularは下記のバージョンとなっています。

  • Angular: 10.0.1
  • msal.js: 1.3.2
  • @azure/msal-angular: 1.0.0

検証を行うバージョンによっては動作が異なることがありますのでご留意ください。

何が起きているか

タイトルのとおりですが、AngularのRouterModuleを設定するときにuseHashtrueにすると

Angularのルーティングはhttp://localhost:4200/#/<path>のような#付きのパスとなります。

AzureADでimplicit flowでトークンが返却される場合、リダイレクトURLに#access_token=~~のようにトークン情報が付与されて返却されているようです。

参考-Implicit grant (暗黙的な付与)

  1. fragmentでトークンが送られる
  2. Angularのルーティングで#付きのパス=遷移と誤認し遷移を実行
  3. 実際のパスは当然存在しないのでエラー

この流れが原因のようです。

queryで返却されるようになれば良かろうかとも思うのですが

msal.jsのConfigをさらっと眺めた感じだと、responce_modeをいじる設定はないように見えるので別の方法を模索してみます。

参考-msal・Configuration

どのように解消するか

routerのeventをsubscribeするのも良いですがちょっと手間なのでシンプルな解決方法が望ましいですね。

解決方法1:BootstrapModuleの変更

window.parent かどうか判定し、IFrame内部であればRouterを持たないダミーのモジュールを流し込めば良いはずです。

MSAL.js を使用してトークンを自動的に取得、更新するときにページのリロードを回避する」が参考になるかと思います。

別の問題の解消方法ですが、これに似た方法で対応が可能となります。

ただ、上記のサイトに上げた方法だと、useHash=trueにしたRouterModuleをImportすることになるので、問題となっていた現象を解決できません。

そこで、window.parentによる分岐をAppModule下ではなく

もう少し上のレイヤーで実行し、BootstrapするModuleをRouterを持たないダミーのModuleに変更する。といった対応を行います。

処理の分岐を下記のようにmain.tsで実行します。

if (window !== window.parent && !window.opener) {
  // IFrame内で実行されるダミーモジュール
  platformBrowserDynamic().bootstrapModule(RedirectDummyModule)
    .catch(err => console.error(err));
} else {
  // 通常のモジュール
  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));
}

解決方法2:リダイレクト先の変更

そもそもAngularを使用していないプレーンなダミーサイトにリダイレクト先を変更しちゃおうという手です。

ADAL Wiki - FAQ - Q1. My app is re-loading every time adal renews a token.」が参考になります。

これはadalの記事ですがmsal、@azure/msal-angularでも同様の実装が可能となります。

リダイレクト先のHTMLとして、下記のようなHTMLを用意しておきます。

リダイレクト時点でmsalのUserAgentApplicationインスタンスが生成されていないとエラーとなるので

msal.jsの読み込みと、UserAgentApplicationのインスタンス生成を行う必要があります。

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Frame Redirect</title>
    <script type="text/javascript" src="https://alcdn.msftauth.net/lib/1.3.2/js/msal.min.js" integrity="sha384-4cH4i4aLXsdIJJz5DF27S+GxZXRSqBORS4NN7kc7NyZBBIugxFyt6hQt5FiR/DuZ" crossorigin="anonymous"></script>
</head>
<body>
    dummy
    <script>
        var msalConfig = {
            clientId: "<Azure AD App Client ID>"
        };
        var authContext = new Msal.UserAgentApplication(msalConfig);
        authContext.handleWindowCallback((err, res) => {
            console.log({ err, res });
        });

        if (window === window.parent) { window.location.replace(location.origin + location.hash); }
    </script>
</body>
</html>

仮に、このHTMLがredirect-page.htmlとすると

アクセストークン取得時などにリダイレクトURLとしてこのURLを指定します。

    // 👇msal-angularのMsalService.acquireTokenSilent()
    // 👇msal.jsのUserAgentApplication.acquireTokenSilent()
    const res = await this.msalService.acquireTokenSilent({
      scopes: [ 'スコープ'],
      redirectUri: 'http://localhost:4200/redirect-page.html' // 👈こんな感じ
    });

デプロイする時も、今回作成したredirect-page.htmlが必要になるのででangular.jsonでデプロイ対象に指定しておきましょう。

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
   ....
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "assets": [
              ...
              "src/redirect-page.html" 👈追加
            ],
            ...

まとめ

そもそも別問題の対応のために、解決方法1や2を行っていたので今更見つけた感じになります。

無限リダイレクト問題とかにぶつかって、解決方法1,2とかに行き当たる方は多いと思うので知らん間に対応している。といったことが多そうです。

useHash:trueで且つ、そこらの対応がされていないとかのすこしニッチな状況のものになりますね。

msalのv2でこの記事が無意味なものに変わってくれるといいなーと若干期待しています。

今回検証で使用したリポジトリは下記となります。

解決方法1

解決方法2