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

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

メモ書き-msal.jsでSSOしたらiOSやChromeでエラーが発生したので対応した話

はじめに

下記のライブラリのバージョンで発生したエラーに対する対応となります。

参照される時期によっては対応方法や対応不要になったりと状況が変わっている可能性がありますのでご留意ください。

  • @azure/msal-browser: 2.13.1

(状況変わっているのが一番うれしい

iOSChromeでSSOしたときにエラーが発生する

すでにadal.jsを使用しているWebアプリケーションやほかシステムでログインが行われている前提であった場合msal.jsの ssoSilent はよく使用する機能かもしれません。

SSO SilentはセッションCookieを利用しているのですが

iOSChromeのシークレットブラウザを使用した場合サードパーティ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:ChromeiOSの設定を変更する

てっとりばやいのはChromeの下記設定をOffにする

f:id:TakasDev:20210404153527p:plain

iOSの下記設定をOffにすることです。

f:id:TakasDev:20210404154042p:plain

しかし、あえてセキュリティ上守られているものを解除する方に寄せていくのもよろしくありません。

ここの設定を変更するのは原因の切り分けを行うときくらいにとどめておいたほうが良いと思っています。

対応策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状態を記録しておき

それによって動作を変える方法で逃げることにしました。

さいごに

実験に使ったコードは以下に格納しています。

github.com