Bedrock Chat Azure AD 自動ログイン|1ファイル変更で実装する簡単な方法

AWS

はじめに

おつかされまです。tyamonです。

この記事では、Bedrock Chat Azure AD 自動ログインの実装方法について解説します。ページを開いた瞬間に自動で Azure AD の認証画面へリダイレクトする実装です。以下のような方を対象としています。

  • BrChat を Azure AD(OIDC)と連携済みの方
  • ログインボタンを押す手間を省きたい方

結論(または成果物)

本記事の手順を実施することで、BrChat のページを開くだけで自動的に Azure AD の認証画面へ遷移し、ログイン成功後にそのままアプリが表示されるようになります。認証に失敗した場合はエラー画面を表示します。


前提条件・環境

  • BrChat: v3.15.5
  • フロントエンド: React + TypeScript / AWS Amplify v6
  • 認証: Azure AD(カスタム OIDC プロバイダー)連携済み
  • デプロイ先: Amazon CloudFront + AWS Lambda

背景: デフォルト動作の課題

BrChat を Azure AD(カスタム OIDC)と連携した場合、デフォルトではページを開くと以下のような画面が表示されます。

ユーザーはこの「ログイン」ボタンを押して初めて Azure AD の認証画面へ遷移します。社内ツールとして使う場合、ページを開いたら即座にログインできる方が自然です。


Bedrock Chat Azure AD 自動ログインの実装手順

変更するファイルは frontend/src/components/AuthCustom.tsx の1ファイルのみです。

変更前のコード

import React, {
  ReactNode,
  useState,
  useEffect,
  cloneElement,
  ReactElement,
} from 'react';
import Button from './Button';
import { BaseProps } from '../@types/common';
import { getCurrentUser, signInWithRedirect, signOut } from 'aws-amplify/auth';
import { useTranslation } from 'react-i18next';
import { PiCircleNotch } from 'react-icons/pi';

type Props = BaseProps & {
  children: ReactNode;
};

const AuthCustom: React.FC<Props> = ({ children }) => {
  const [authenticated, setAuthenticated] = useState(false);
  const [loading, setLoading] = useState(true);
  const { t } = useTranslation();

  useEffect(() => {
    getCurrentUser()
      .then(() => {
        setAuthenticated(true);
      })
      .catch(() => {
        setAuthenticated(false); // 未認証でも何もしない
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  const handleSignIn = () => {
    signInWithRedirect({
      provider: {
        custom: import.meta.env.VITE_APP_CUSTOM_PROVIDER_NAME,
      },
    });
  };

  const handleSignOut = () => {
    signOut();
  };

  return (
    <>
      {loading ? (
        <div className="flex flex-col items-center p-4">
          <div className="mb-3 text-4xl">Loading...</div>
          <div className="animate-spin">
            <PiCircleNotch size={100} />
          </div>
        </div>
      ) : !authenticated ? (
        // 未認証: ログインボタンを表示
        <div className="flex flex-col items-center gap-4">
          <div className="mb-5 mt-10 text-4xl text-aws-sea-blue-light">
            {t('app.name')}
          </div>
          <Button onClick={() => handleSignIn()} className="px-20 text-xl">
            {t('signIn.button.login')}
          </Button>
        </div>
      ) : (
        <>
          {cloneElement(children as ReactElement, { signOut: handleSignOut })}
        </>
      )}
    </>
  );
};

export default AuthCustom;

変更後のコード

import React, {
  ReactNode,
  useState,
  useEffect,
  cloneElement,
  ReactElement,
} from 'react';
import { BaseProps } from '../@types/common';
import { getCurrentUser, signInWithRedirect, signOut } from 'aws-amplify/auth';
import { PiCircleNotch } from 'react-icons/pi';

type Props = BaseProps & {
  children: ReactNode;
};

const AuthCustom: React.FC<Props> = ({ children }) => {
  const [authenticated, setAuthenticated] = useState(false);
  const [loading, setLoading] = useState(true);
  const [authError, setAuthError] = useState<string | null>(null); // 追加

  useEffect(() => {
    // Azure ADからエラー付きでリダイレクトされた場合はエラー表示してリダイレクトしない
    const urlParams = new URLSearchParams(window.location.search);
    const error = urlParams.get('error');
    if (error) {
      setAuthError(error);
      setLoading(false);
      return;
    }

    getCurrentUser()
      .then(() => {
        setAuthenticated(true);
        setLoading(false);
      })
      .catch(() => {
        // 未認証の場合は自動的にAzure ADへリダイレクト
        signInWithRedirect({
          provider: {
            custom: import.meta.env.VITE_APP_CUSTOM_PROVIDER_NAME,
          },
        });
      });
  }, []);

  const handleSignOut = () => {
    signOut();
  };

  // 認証エラー時の表示
  if (authError) {
    return (
      <div className="flex flex-col items-center gap-4 p-8">
        <div className="text-2xl text-red-600">認証エラー</div>
        <div className="text-gray-600">{authError}</div>
      </div>
    );
  }

  return (
    <>
      {loading || !authenticated ? (
        // セッション確認中またはAzure ADへのリダイレクト待ち
        <div className="flex flex-col items-center p-4">
          <div className="mb-3 text-4xl">Loading...</div>
          <div className="animate-spin">
            <PiCircleNotch size={100} />
          </div>
        </div>
      ) : (
        // 認証済み: signOut関数を子コンポーネントに渡す
        <>
          {cloneElement(children as ReactElement, { signOut: handleSignOut })}
        </>
      )}
    </>
  );
};

export default AuthCustom;

変更点の解説

変更のポイントは3つです。

1. URLの ?error= チェック(リダイレクトループ防止)

Azure AD での認証が失敗すると、Cognito は ?error=access_denied のようなパラメータを付けてアプリへリダイレクトします。このチェックがないと、「未認証 → 自動リダイレクト → 失敗 → 戻る → 未認証 → …」という無限ループが発生します。

const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');
if (error) {
  setAuthError(error); // エラー表示して止める
  return;
}

2. 未認証時の自動リダイレクト

変更前は catch ブロックで setAuthenticated(false) としてボタン表示に留まっていました。変更後は即座に signInWithRedirect() を呼び出します。

.catch(() => {
  // ボタン表示の代わりに自動リダイレクト
  signInWithRedirect({
    provider: {
      custom: import.meta.env.VITE_APP_CUSTOM_PROVIDER_NAME,
    },
  });
});

3. エラー画面の追加

認証失敗時にエラー内容を表示する画面を追加しました。

if (authError) {
  return (
    <div className="flex flex-col items-center gap-4 p-8">
      <div className="text-2xl text-red-600">認証エラー</div>
      <div className="text-gray-600">{authError}</div>
    </div>
  );
}

動作フロー比較

変更前

ページを開く
  └─ セッション確認
       ├─ 認証済み → アプリ表示
       └─ 未認証  → ログインボタンを表示
                        └─ ボタンをクリック → Azure AD へリダイレクト

変更後

ページを開く
  ├─ URLに ?error= がある  → エラー画面表示
  └─ セッション確認
       ├─ 認証済み → アプリ表示
       └─ 未認証  → 自動で Azure AD へリダイレクト
                        ├─ ログイン成功 → アプリ表示
                        └─ ログイン失敗 → ?error= 付きで戻る → エラー画面表示

[!NOTE]
ページを開いてから Azure AD へリダイレクトするまでの間、「Loading…」スピナーが一瞬表示されます。これは getCurrentUser() の非同期処理が完了するまでの待機時間です。


まとめ

今回は BrChat の AuthCustom.tsx を修正して、ページを開いたときに自動で Azure AD 認証へリダイレクトする実装を紹介しました。ポイントは以下の通りです。

  • 変更ファイルは AuthCustom.tsx の1ファイルのみ
  • getCurrentUser() が未認証を返したら即座に signInWithRedirect() を呼び出す
  • ?error= パラメータのチェックでリダイレクトループを防止する
  • 認証失敗時はエラー内容を画面に表示する

参考資料

コメント

タイトルとURLをコピーしました