./knowledge-base/frontend/src/pages/LoginCallbackPage.tsx
/**
* Okta ログインコールバックページ
*
* Okta が認証後にリダイレクトしてくる /login/callback を処理する。
* 処理フロー:
* 1. handleLoginRedirect() でトークンを取得・保存
* 2. accessToken を Cookie に即座に設定(CloudFront Function の /era-assist/* チェック用)
* 3. login() 実行時に保存した originalUri から redirect_to を取出
* 4. redirect_to があればそこへフルページ遷移、なければ originalUri に遷移
*
* このページのURLは Okta Application の "Sign-in redirect URIs" に
* 登録されている必要がある。
*/
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Box, CircularProgress, Typography } from "@mui/material";
import type { AccessToken } from "@okta/okta-auth-js";
import { oktaAuth } from "../auth/oktaConfig";
export default function LoginCallbackPage() {
const navigate = useNavigate();
useEffect(() => {
oktaAuth
.handleLoginRedirect()
.then(async () => {
// ── Cookie 即座設定 ────────────────────────────────────────────────
// CF Function (/era-assist/*) がフルページ遷移前に Cookie を読むため、
// window.location.href で遷移する前にトークンを Cookie に書き込む。
try {
const tokenObj = (await oktaAuth.tokenManager.get(
"accessToken",
)) as AccessToken | undefined;
if (tokenObj?.accessToken) {
const maxAge =
tokenObj.expiresAt - Math.floor(Date.now() / 1000);
if (maxAge > 0) {
document.cookie = `okta_access_token=${tokenObj.accessToken}; path=/; max-age=${maxAge}; SameSite=Lax; Secure`;
}
}
} catch {
// Cookie 設定失敗は無視(AuthContext の useEffect が後ほど設定する)
}
// ── redirect_to の取得 (認証前の URL から) ──────────────────────
// login() 時に signInWithRedirect({ originalUri }) で保存した URL を復元
const originalUri = oktaAuth.getOriginalUri() || "/";
try {
const url = new URL(originalUri, window.location.origin);
const redirectTo = url.searchParams.get("redirect_to");
if (redirectTo && redirectTo.startsWith("/")) {
// セキュリティ: 相対パスのみ許可
window.location.href = redirectTo; // Cookie 設定後にフルページ遷移
return;
}
} catch {
// URLパース失敗は無視
}
navigate(originalUri, { replace: true });
})
.catch((err: unknown) => {
console.error("Okta callback error:", err);
navigate("/", { replace: true });
});
}, [navigate]);
return (
<Box
sx={{
minHeight: "100vh",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 2,
}}
>
<CircularProgress />
<Typography color="text.secondary">ログイン処理中...</Typography>
</Box>
);
}