From a2c5c436e10f5520b286aee4bea015a863faaf24 Mon Sep 17 00:00:00 2001 From: Kentaro Fukunaga Date: Thu, 22 Jun 2023 23:24:35 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20151:=20=E3=83=91=E3=82=B9?= =?UTF-8?q?=E3=83=AF=E3=83=BC=E3=83=89=E3=83=AA=E3=82=BB=E3=83=83=E3=83=88?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AB=E3=81=A6=E3=80=8CCancel=E3=80=8D?= =?UTF-8?q?=E3=82=92=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=A8=E3=80=81=E3=80=8Cloading=E3=80=8D=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=A7=E6=AD=A2=E3=81=BE=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E8=A7=A3=E6=B1=BA=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task1921: パスワードリセット画面にて「Cancel」をクリックすると、「loading」画面で止まる問題を解決する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1921) - 「補足」の「実装参考記事」を参考に、ログインリダイレクト時のハンドリング方法を修正 - ログインリダイレクト処理におけるAADB2Cからのエラーハンドリングを実装し、 エラーコードが「ユーザーによる操作キャンセル」だった場合、トップ画面に戻すよう修正 ## レビューポイント - ログインリダイレクト時のハンドリング方法は不適切ではないか - とくに、React hookの使い方でアンチパターンになっているようなことはないか ## UIの変更 - なし ## 動作確認状況 - ローカルにて、ログインできることを確認 - ローカルにて、ユーザーフローキャンセル時にトップ画面に戻ることを確認(二要素認証、パスワードリセットともに) ## 補足 - 実装参考記事 - [その1](https://learn.microsoft.com/ja-jp/azure/active-directory/develop/msal-js-initializing-client-applications#initialize-msaljs-2x-apps) - [その2](https://learn.microsoft.com/ja-jp/azure/active-directory/develop/msal-error-handling-js) - [AADB2Cエラーコードリファレンス](https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/error-codes) --- .../src/pages/LoginPage/index.tsx | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/dictation_client/src/pages/LoginPage/index.tsx b/dictation_client/src/pages/LoginPage/index.tsx index 58d6796..61e66d1 100644 --- a/dictation_client/src/pages/LoginPage/index.tsx +++ b/dictation_client/src/pages/LoginPage/index.tsx @@ -1,4 +1,5 @@ import { useMsal } from "@azure/msal-react"; +import { AuthError } from "@azure/msal-browser"; import { AppDispatch } from "app/store"; import { isIdToken } from "common/token"; import Footer from "components/footer"; @@ -11,10 +12,7 @@ import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; const LoginPage: React.FC = (): JSX.Element => { - /* XXX B2CのログインからIDトークンの取得までの挙動を整理する必要がある。 - 「プロダクト バックログ項目 1655: ログイン周りの挙動について調査・整理する」で調査・整理する。 - */ - const { accounts, instance } = useMsal(); + const { instance } = useMsal(); const dispatch: AppDispatch = useDispatch(); const navigate = useNavigate(); const [, i18n] = useTranslation(); @@ -34,7 +32,7 @@ const LoginPage: React.FC = (): JSX.Element => { if (meta.requestStatus === "fulfilled") { const accessToken = loadAccessToken(); const refreshToken = loadRefreshToken(); - /* TODO デスクトップアプリが無いためメモ帳を開くようにしている + /* TODO 1899:デスクトップアプリが無いためメモ帳を開くようにしている デスクトップアプリチームでスキーム名が決まり次第修正する */ const url = `note:login?accessToken=${accessToken}&refreshToken=${refreshToken}&language=${i18n.language}`; // カスタムURLスキーム @@ -51,18 +49,24 @@ const LoginPage: React.FC = (): JSX.Element => { // TODO 将来的にトークンの取得処理をoperations.ts側に移動させたい。useEffect内で非同期処理を行いたくない。 useEffect(() => { + if (status !== "none") { + // ログイン処理で、何回か本画面が描画される契機があるが、認証処理は一度だけ実施すればよいため認証処理実行済みであれば何もしない + return; + } + (async () => { - if (accounts.length >= 1) { - const { homeAccountId, idTokenClaims } = accounts[0]; - if (idTokenClaims) { - if (idTokenClaims.aud) { + try { + const loginResult = await instance.handleRedirectPromise(); + if (loginResult && loginResult.account) { + const { homeAccountId, idTokenClaims } = loginResult.account; + if (idTokenClaims && idTokenClaims.aud) { // IDトークンの取得 const idTokenString = localStorage.getItem( `${homeAccountId}-${ import.meta.env.VITE_B2C_KNOWNAUTHORITIES }-idtoken-${idTokenClaims.aud}----` ); - if (idTokenString && status === "none") { + if (idTokenString) { const idTokenObject = JSON.parse(idTokenString); if (isIdToken(idTokenObject)) { await login(idTokenObject.secret); @@ -70,9 +74,20 @@ const LoginPage: React.FC = (): JSX.Element => { } } } + } catch (e) { + // AAD B2Cの多要素認証画面やパスワードリセット画面で「cancel」をクリックすると、handleRedirectPromise()にてエラーが発生するため、 + // それをハンドリングして適切な画面遷移処理を行う。 + if (e instanceof AuthError) { + // エラーコードはerrorMessageの中の一部として埋め込まれており完全一致で取得するのは筋が悪いため、部分一致で取得する。 + // TODO 他にもAADB2Cのエラーコードを使用する箇所が出てきた場合、定数化すること + if (e.errorMessage.startsWith("AADB2C90091")) { + navigate("/"); + } + } } })(); - }, [accounts, login, status]); + }, [instance, login, navigate, status]); + return ( <>