diff --git a/.env.sample b/.env.sample
index 9bbadab2..5734649e 100644
--- a/.env.sample
+++ b/.env.sample
@@ -110,7 +110,7 @@ HYDRA_PUBLIC_URL=${OATHKEEPER_PUBLIC_URL}/oidc
# OIDC 클라이언트 callback (콤마 구분)
ADMINFRONT_CALLBACK_URLS=http://localhost:5173/auth/callback,https://sso.hmac.kr/auth/callback
-DEVFRONT_CALLBACK_URLS=http://localhost:5174/callback,https://sso.hmac.kr/devfront/callback
+DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback
# Kratos allowed_return_urls 확장 목록 (콤마 구분, 선택)
# 기본값은 KRATOS_UI_URL, USERFRONT_URL, 각 callback URL을 자동 포함합니다.
@@ -134,9 +134,9 @@ CSRF_COOKIE_NAME=__HOST-baronSSO_csrf
CSRF_COOKIE_SECRET=localcsrf123
# AdminFront OIDC 설정
-ADMINFRONT_CALLBACK_URLS=http://localhost:5000/callback,https://sso.hmac.kr/devfront/callback
+ADMINFRONT_CALLBACK_URLS=http://localhost:5173/auth/callback,https://sso.hmac.kr/auth/callback
# DevFront OIDC 설정
VITE_OIDC_CLIENT_ID=devfront
VITE_OIDC_AUTHORITY=https://sso.hmac.kr/oidc
-DEVFRONT_CALLBACK_URLS=http://localhost:5174/callback,https://sso.hmac.kr/devfront/callback
\ No newline at end of file
+DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback
\ No newline at end of file
diff --git a/adminfront/package-lock.json b/adminfront/package-lock.json
index e26a6fad..f206ce9b 100644
--- a/adminfront/package-lock.json
+++ b/adminfront/package-lock.json
@@ -20,9 +20,11 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
+ "oidc-client-ts": "^3.4.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.71.1",
+ "react-oidc-context": "^3.3.0",
"react-router-dom": "^6.28.2",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
@@ -3163,6 +3165,15 @@
"node": ">=6"
}
},
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/lightningcss": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
@@ -3605,6 +3616,18 @@
"node": ">= 6"
}
},
+ "node_modules/oidc-client-ts": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz",
+ "integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jwt-decode": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -3926,6 +3949,19 @@
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
+ "node_modules/react-oidc-context": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.3.0.tgz",
+ "integrity": "sha512-302T/ma4AOVAxrHdYctDSKXjCq9KNHT564XEO2yOPxRfxEP58xa4nz+GQinNl8x7CnEXECSM5JEjQJk3Cr5BvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "oidc-client-ts": "^3.1.0",
+ "react": ">=16.14.0"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
diff --git a/adminfront/package.json b/adminfront/package.json
index d91f2c03..fa57ca92 100644
--- a/adminfront/package.json
+++ b/adminfront/package.json
@@ -26,9 +26,11 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
+ "oidc-client-ts": "^3.4.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.71.1",
+ "react-oidc-context": "^3.3.0",
"react-router-dom": "^6.28.2",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
diff --git a/adminfront/src/components/common/LanguageSelector.tsx b/adminfront/src/components/common/LanguageSelector.tsx
index fe77df72..7f905cd0 100644
--- a/adminfront/src/components/common/LanguageSelector.tsx
+++ b/adminfront/src/components/common/LanguageSelector.tsx
@@ -44,12 +44,8 @@ function LanguageSelector() {
className="rounded-full border border-border bg-transparent px-3 py-2 text-sm text-muted-foreground transition hover:bg-muted/20"
aria-label={t("ui.common.language", "언어")}
>
-
-
+
+
);
}
diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx
index 7baec948..6fda7c27 100644
--- a/adminfront/src/components/layout/AppLayout.tsx
+++ b/adminfront/src/components/layout/AppLayout.tsx
@@ -12,6 +12,7 @@ import {
Users,
} from "lucide-react";
import { useEffect, useState } from "react";
+import { useAuth } from "react-oidc-context";
import { NavLink, Outlet, useNavigate } from "react-router-dom";
import { t } from "../../lib/i18n";
import LanguageSelector from "../common/LanguageSelector";
@@ -40,6 +41,7 @@ const navItems = [
{ label: "ui.admin.nav.auth_guard", to: "/auth", icon: KeyRound },
];
function AppLayout() {
+ const auth = useAuth();
const navigate = useNavigate();
const [theme, setTheme] = useState<"light" | "dark">(() => {
const stored = window.localStorage.getItem("admin_theme");
@@ -51,16 +53,16 @@ function AppLayout() {
window.confirm(t("msg.admin.logout_confirm", "로그아웃 하시겠습니까?"))
) {
window.localStorage.removeItem("admin_session");
+ auth.removeUser();
navigate("/login");
}
};
useEffect(() => {
- const session = window.localStorage.getItem("admin_session");
- if (!session) {
+ if (!auth.isLoading && !auth.isAuthenticated) {
navigate("/login");
}
- }, [navigate]);
+ }, [auth.isLoading, auth.isAuthenticated, navigate]);
useEffect(() => {
const root = document.documentElement;
@@ -77,6 +79,14 @@ function AppLayout() {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
+ if (auth.isLoading) {
+ return (
+
+ );
+ }
+
return (