diff --git a/src/app/[locale]/auto-login/page.tsx b/src/app/[locale]/auto-login/page.tsx
new file mode 100644
index 00000000..c185e07e
--- /dev/null
+++ b/src/app/[locale]/auto-login/page.tsx
@@ -0,0 +1,112 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { transformApiMenusToMenuItems } from '@/lib/utils/menuTransform';
+import { performFullLogout } from '@/lib/auth/logout';
+
+/**
+ * MNG 관리자 패널 → SAM 자동 로그인 페이지
+ *
+ * 흐름:
+ * 1. MNG에서 "SAM 접속" 클릭 → /auto-login?token=xxx 로 새 창 열림
+ * 2. 기존 세션 로그아웃 (쿠키 + localStorage + Zustand 초기화)
+ * 3. One-Time Token으로 API 호출 → 새 세션 생성
+ * 4. 사용자 정보 저장 후 /dashboard로 이동
+ */
+export default function AutoLoginPage() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const [status, setStatus] = useState<'processing' | 'error'>('processing');
+ const [errorMessage, setErrorMessage] = useState('');
+
+ useEffect(() => {
+ const token = searchParams.get('token');
+
+ if (!token) {
+ setStatus('error');
+ setErrorMessage('로그인 토큰이 없습니다.');
+ return;
+ }
+
+ const performAutoLogin = async () => {
+ try {
+ // 1. 기존 세션 완전 로그아웃 (쿠키 삭제 + 스토어 초기화)
+ await performFullLogout({ skipServerLogout: false });
+
+ // 2. One-Time Token으로 로그인
+ const response = await fetch('/api/auth/token-login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ token }),
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.error || '자동 로그인에 실패했습니다.');
+ }
+
+ // 3. 사용자 정보 localStorage 저장 (LoginPage와 동일 패턴)
+ const transformedMenus = transformApiMenusToMenuItems(data.menus || []);
+
+ const userData = {
+ id: data.user?.id,
+ name: data.user?.name,
+ position: data.roles?.[0]?.description || '사용자',
+ userId: data.user?.user_id,
+ department: data.user?.department || null,
+ department_id: data.user?.department_id || null,
+ menu: transformedMenus,
+ roles: data.roles || [],
+ tenant: data.tenant || {},
+ };
+ localStorage.setItem('user', JSON.stringify(userData));
+
+ // 4. persist store rehydrate
+ const { useFavoritesStore } = await import('@/stores/favoritesStore');
+ const { useTableColumnStore } = await import('@/stores/useTableColumnStore');
+ useFavoritesStore.persist.rehydrate();
+ useTableColumnStore.persist.rehydrate();
+
+ // 5. 로그인 플래그 설정
+ sessionStorage.setItem('auth_just_logged_in', 'true');
+
+ // 6. 대시보드로 이동
+ router.push('/dashboard');
+ } catch (err) {
+ console.error('자동 로그인 실패:', err);
+ setStatus('error');
+ setErrorMessage(err instanceof Error ? err.message : '자동 로그인에 실패했습니다.');
+ }
+ };
+
+ performAutoLogin();
+ }, [searchParams, router]);
+
+ if (status === 'error') {
+ return (
+
+
+
자동 로그인 실패
+
{errorMessage}
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/api/auth/token-login/route.ts b/src/app/api/auth/token-login/route.ts
new file mode 100644
index 00000000..eaabfa73
--- /dev/null
+++ b/src/app/api/auth/token-login/route.ts
@@ -0,0 +1,107 @@
+import { NextResponse } from 'next/server';
+import type { NextRequest } from 'next/server';
+
+/**
+ * 🔵 Next.js 내부 API - 토큰 자동 로그인 프록시
+ *
+ * MNG 관리자 패널에서 "SAM 접속" 버튼 클릭 시 사용
+ * One-Time Token으로 사용자 인증 후 HttpOnly 쿠키 설정
+ *
+ * 🔄 동작 흐름:
+ * 1. 클라이언트 → Next.js /api/auth/token-login (token)
+ * 2. Next.js → PHP /api/v1/token-login (토큰 검증)
+ * 3. PHP → Next.js (access_token, refresh_token, 사용자 정보)
+ * 4. Next.js: 토큰을 HttpOnly 쿠키로 설정
+ * 5. Next.js → 클라이언트 (토큰 제외한 사용자 정보만 전달)
+ */
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+ const { token } = body;
+
+ if (!token) {
+ return NextResponse.json(
+ { error: '토큰이 필요합니다.' },
+ { status: 400 }
+ );
+ }
+
+ // PHP 백엔드 API 호출
+ const backendResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/token-login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'X-API-KEY': process.env.API_KEY || '',
+ },
+ body: JSON.stringify({ token }),
+ });
+
+ if (!backendResponse.ok) {
+ const errorData = await backendResponse.json().catch(() => ({}));
+ return NextResponse.json(
+ { error: errorData.error || '토큰 인증에 실패했습니다.' },
+ { status: backendResponse.status }
+ );
+ }
+
+ const data = await backendResponse.json();
+
+ // 클라이언트에 전달할 응답 (토큰 제외)
+ const responseData = {
+ message: data.message,
+ user: data.user,
+ tenant: data.tenant,
+ menus: data.menus,
+ roles: data.roles,
+ token_type: data.token_type,
+ expires_in: data.expires_in,
+ expires_at: data.expires_at,
+ };
+
+ // HttpOnly 쿠키 설정 (login/route.ts와 동일한 패턴)
+ const isProduction = process.env.NODE_ENV === 'production';
+
+ const accessTokenCookie = [
+ `access_token=${data.access_token}`,
+ 'HttpOnly',
+ ...(isProduction ? ['Secure'] : []),
+ 'SameSite=Lax',
+ 'Path=/',
+ `Max-Age=${data.expires_in || 7200}`,
+ ].join('; ');
+
+ const refreshTokenCookie = [
+ `refresh_token=${data.refresh_token}`,
+ 'HttpOnly',
+ ...(isProduction ? ['Secure'] : []),
+ 'SameSite=Lax',
+ 'Path=/',
+ 'Max-Age=604800',
+ ].join('; ');
+
+ const isAuthenticatedCookie = [
+ 'is_authenticated=true',
+ ...(isProduction ? ['Secure'] : []),
+ 'SameSite=Lax',
+ 'Path=/',
+ `Max-Age=${data.expires_in || 7200}`,
+ ].join('; ');
+
+ const response = NextResponse.json(responseData, { status: 200 });
+
+ response.headers.append('Set-Cookie', accessTokenCookie);
+ response.headers.append('Set-Cookie', refreshTokenCookie);
+ response.headers.append('Set-Cookie', isAuthenticatedCookie);
+
+ return response;
+
+ } catch (error) {
+ console.error('Token login proxy error:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/lib/api/auth/auth-config.ts b/src/lib/api/auth/auth-config.ts
index 6a142913..0a442041 100644
--- a/src/lib/api/auth/auth-config.ts
+++ b/src/lib/api/auth/auth-config.ts
@@ -14,7 +14,7 @@ export const AUTH_CONFIG = {
// 명시적으로 여기에 추가된 경로만 비로그인 접근 가능
// 기본 정책: 모든 페이지는 인증 필요
publicRoutes: [
- // 비어있음 - 필요시 추가 (예: '/about', '/terms', '/privacy')
+ '/auto-login', // MNG → SAM 자동 로그인 (토큰 기반)
],
// 🔒 보호된 라우트 (참고용, 실제로는 기본 정책으로 보호됨)