Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/service/auth-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
export const authServiceRemote = () => ({
// 로그인
login: async (payload: LoginRequest) => {
const data = await api.post<LoginResponse>('/auth/login', payload);
const data = await api.post<LoginResponse>('/auth/login', payload, { withCredentials: true });

setAccessToken(data.accessToken, data.expiresIn);
return data;
Expand Down
6 changes: 5 additions & 1 deletion src/lib/auth/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ const ACCESS_TOKEN_KEY = 'accessToken';
export const setAccessToken = (token: string, maxAgeSeconds?: number) => {
if (typeof document === 'undefined') return;

const parts = [`${ACCESS_TOKEN_KEY}=${encodeURIComponent(token)}`, 'path=/'];
const parts = [
`${ACCESS_TOKEN_KEY}=${encodeURIComponent(token)}`,
'path=/',
'domain=.wego.monster',
];

if (typeof maxAgeSeconds === 'number' && maxAgeSeconds > 0) {
parts.push(`Max-Age=${maxAgeSeconds}`);
Expand Down
29 changes: 29 additions & 0 deletions src/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from 'next/server';

export default async function proxy(request: NextRequest) {
const accessToken = request.cookies.get('accessToken');
const refreshToken = request.cookies.get('refreshToken');

const protectedPaths = ['/mypage', '/post-meetup', '/message', '/schedule'];
const isProtected = protectedPaths.some((path) => request.nextUrl.pathname.startsWith(path));

// 보호되지 않은 경로는 그냥 통과
if (!isProtected) {
return NextResponse.next();
}

// 둘 다 없으면 로그인
if (!accessToken && !refreshToken) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('error', 'unauthorized');
loginUrl.searchParams.set('path', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}

// accessToken 있으면 통과
return NextResponse.next();
Comment on lines 16 to 23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

인증 로직에 불일치와 토큰 갱신 로직이 누락되었습니다.

현재 로직의 문제점:

  1. 16번 줄의 조건 !accessToken && !refreshToken은 둘 중 하나라도 존재하면 24번 줄로 진행합니다. 하지만 23번 줄의 주석은 "accessToken 있으면 통과"라고 명시하고 있어 의도가 불명확합니다.
  2. accessToken이 없고 refreshToken만 있는 경우, 토큰 갱신을 시도하지 않고 그냥 통과시킵니다. 이 경우 보호된 페이지에서 API 호출 시 401 에러가 발생할 수 있습니다.

미들웨어에서 토큰 갱신을 처리하거나, 최소한 accessToken이 있을 때만 통과하도록 로직을 명확히 해야 합니다.

🔎 제안하는 수정 (옵션 1: accessToken 필수)
-  // 둘 다 없으면 로그인
+  // 토큰이 없거나 accessToken이 없으면 로그인
-  if (!accessToken && !refreshToken) {
+  if (!accessToken) {
     const loginUrl = new URL('/login', request.url);
     loginUrl.searchParams.set('error', 'unauthorized');
     loginUrl.searchParams.set('path', request.nextUrl.pathname);
     return NextResponse.redirect(loginUrl);
   }
 
-  // accessToken 있으면 통과
+  // accessToken이 있으면 통과
   return NextResponse.next();
🔎 제안하는 수정 (옵션 2: refreshToken으로 갱신 시도)

refreshToken이 있지만 accessToken이 없는 경우 토큰 갱신을 시도하는 로직을 추가할 수 있습니다. 다만 미들웨어에서 API 호출을 수행하면 복잡도가 증가하므로, 클라이언트 측에서 처리하는 것이 더 나을 수 있습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!accessToken && !refreshToken) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('error', 'unauthorized');
loginUrl.searchParams.set('path', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
// accessToken 있으면 통과
return NextResponse.next();
if (!accessToken) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('error', 'unauthorized');
loginUrl.searchParams.set('path', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
// accessToken이 있으면 통과
return NextResponse.next();

}

export const config = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 경로들은 위의 프록시에서 제외되는 건가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 맞아용
내장 next api,정적 자원 요청(이미지 등), 로그인, 회원가입 페이지는 proxy 동작이 필요없기 때문에 제외했습니다~

matcher: ['/((?!api|_next/static|_next/image|favicon.ico|login|signup).*)'],
};