Skip to content

[Feat] access token 재갱신#142

Merged
aken-you merged 22 commits intodevelopfrom
feat/error-handling
Sep 5, 2025
Merged

[Feat] access token 재갱신#142
aken-you merged 22 commits intodevelopfrom
feat/error-handling

Conversation

@aken-you
Copy link
Contributor

@aken-you aken-you commented Sep 4, 2025

☘️ 작업 내용

1. middleware

nextjs로 들어오는 요청을 가로채는 middleware 기능을 이용해, 사용자가 페이지에 접근할 권한이 없다면 login 페이지로 리다이렉션 해줬습니다.

2. axios instance

유효하지 않은 access token으로 인해 실패한 요청들을 queue에 모아둔 다음, 갱신 성공했으면 queue를 순차적으로 돌면서 재요청합니다.
테스트하기 위해 경도님이 작성해주신 문서보고 로컬에서 백엔드 돌려봐서 테스트하였습니다.
근데, axiosServerInstance는 아직 재갱신이 원활하게 되지 않아, 퇴근 후 다시 확인해야 합니다. 우선 axiosInstance 부분 위주로 리뷰 부탁드립니다 🙇

  1. 경도님이 작성해주신 문서대로 가이드해주세요.
  2. 저희 repo의 .env 파일도 문서에 나와있는 클라이언트 id로 변경해주셔야 합니다.
  3. 아래 이미지의 파일에 있는 코드를 수정해주시면 됩니다. ttl은 엑세스 토큰 유효시간입니다.
스크린샷 2025-09-04 오전 9 18 00 스크린샷 2025-09-04 오전 9 18 19

Copy link
Contributor

@Mimiminz Mimiminz left a comment

Choose a reason for hiding this comment

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

에러처리랑 로그인 안했을 시 리다이렉션, 멤버 아이디 가져오는 걸 정리하니까 코드가 훨씬 깔끔해졌네요..!

Copy link
Contributor

Choose a reason for hiding this comment

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

이 파일은 ssr 전용 프로필 정보 가져오는 함수인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아뇨!

이 파일의 목적은 Matcher에 정의된 경로에 접근했을 때,
쿠키에 accessToken과 memberId가 없으면 login 페이지로 이동하는 것입니다.

예를 들어, / 페이지 달라고 nextjs 서버에 요청 들어가기 전에, cookie에 access token과 memberId가 없으면 로그인 페이지로 이동합니다.

Comment on lines +2 to +5
public name = 'ApiError';
public statusCode: number;
public errorCode: string;
public errorName: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 여기 name과 errorName이 다른 역할을 하는 건가요?
name은 어떤 곳에 쓰이는 건지 궁금합니당

Copy link
Contributor Author

Choose a reason for hiding this comment

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

name은 저희쪽에서 만든 Error 객체임을 표현하고 싶었고,
errorName은 서버에서 응답값으로 내려줍니다.

middleware.ts Outdated
isNumeric(request.cookies.get('memberId')?.value);

// sign-up 페이지에서 memberId는 null (사용자 이름을 등록해야 memberId 주어짐)
if (request.url.endsWith('/sign-up')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 이 부분 request.url.endsWith('/sign-up') 보다
const { pathname } = request.nextUrl; 이런 식처럼 request.nextUrl를 사용하는 건 어떻게 생각하시나용

Comment on lines +10 to +17
export const axiosServerInstance = axios.create({
baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/`,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

서버가 갱신되지 않는 이유....는 정확하지 않지만...
찾아보니 withCredentials: true로는 쿠키가 안 실린다는 것 같습니다..
원래 보통 withCredentials 세팅을 하면 프론트에서 자동으로 쿠키를 첨부해서 서버로 넘겨주지만,
next.js의 SSR의 경우에는 실행되는 위치가 프론트측(브라우저)이 아닌 서버측에서 실행되고 실행 결과를 이용한 완성된 페이지를 프론트로 넘겨주기 때문에 브라우저에 저장되어 있는 쿠키를 알지 못한다고 합니다.

따라서 이 경우에도 refreshToken이 쿠키에 실리지 않아서 백엔드가 인식하지 못해 갱신이 안되는 오류가 아닐까......하는 생각이 듭니다. 🤔

저도 next의 토큰은 잘 몰라서 어렵네요.. 좋은 방법이 있다면 바로 공유하도록 하겠습니다,,

@aken-you
Copy link
Contributor Author

aken-you commented Sep 4, 2025

영은님 조언대로, middleware에서 access token 유효성 검사를 하였습니다.

middleware에서 토큰 재갱신 로직을 짠 이유

서버 컴포넌트에서 사용할 axiosServerInstance에서 재갱신 로직을 짜게 된다면, 아래와 같은 flow로 진행됩니다.

  1. 유효하지 않은 토큰 error 응답
  2. interceptors.response.use의 두번째 에러 콜백에서 토큰 재갱신 요청
  3. 재갱신되면, cookies().set으로 쿠키에 저장된 토큰 값 업데이트

하지만, cookies().set은 서버 컴포넌트 측에서 사용할 수 없습니다. server action 또는 route handler에서 사용할 수 있습니다. (공식문서)

You can use the (await cookies()).set(name, value, options) method in a Server Action or Route Handler to set a cookie. The options object is optional.

axiosServerInstance로 재갱신하는 방법 대신, middleware로 페이지 진입하기 전에 access token이 유효한지 검증하는 방식으로 구현했습니다.

middleware에서 재갱신 로직

  1. 쿠키에 accessToken이 없거나, 회원가입 페이지가 아닌데 쿠키에 memberId가 없으면 로그인 페이지로 이동 (회원가입하지 않은 사람은 memberId가 null)
  2. /api/v1/auth/me api를 통해 토큰 유효한지 검증 (verifyAccessToken)
  3. 토큰이 유효하지 않을 경우, refresh token으로 재갱신 (refreshAccessToken)

axiosServerInstance로 요청을 보내기 전에, middleware 쪽에서 access token을 검증합니다.

/api/v1/auth/me api는 access token을 통해 memberId를 반환합니다.
만약 access token이 유효하지 않을 경우, 아래와 같은 에러 응답이 옵니다.

{
    "statusCode": 401,
    "timestamp": "2025-08-12T19:56:58.7278032",
    "errorCode": "AUTH001",
    "errorName": "AUTHENTICATION_FAILED",
    "message": "Authentication Failed"
}

이 api를 통해 재갱신 로직을 구현하였습니다.

refreshAccessToken

이 함수는 refresh token으로 access token을 발급하여 반환하는 함수입니다.
여기서 headers.cookie가 의아해 하실 것 같아요.

const refreshAccessToken = async () => {
  try {
    const refreshToken = await getServerCookie('refresh_token');

    const response = await fetch(
      `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/auth/access-token/refresh`,
      {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          cookie: `refresh_token=${refreshToken}`, // 쿠키를 수동으로 전송
        },
      },
    );

    const data: { content: { accessToken: string } } = await response.json();

    return data.content.accessToken;
  } catch (error) {
    return null;
  }
};

저렇게 수동으로 설정해둔 이유는
middleware가 Edge Runtime 환경에서 실행하기 때문입니다. 요청할 때 쿠키를 자동으로 포함하지 않으므로, 수동으로 넣어줘야 합니다.

@aken-you aken-you marked this pull request as ready for review September 4, 2025 18:19
@aken-you aken-you closed this Sep 4, 2025
@aken-you aken-you reopened this Sep 4, 2025
@aken-you
Copy link
Contributor Author

aken-you commented Sep 4, 2025

백엔드 서버 로컬로 띄워서 access token 유효시간을 2초로 두고 테스트 한 화면입니다.
미들웨어에서 access token이 갱신되어, 브라우저에 저장된 것을 확인할 수 있습니다.

2025-09-05.3.26.30.mov

middleware.ts Outdated
Comment on lines 11 to 18
const res = await axios.get<{ content: number }>(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/auth/me`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);
Copy link
Contributor

@Mimiminz Mimiminz Sep 5, 2025

Choose a reason for hiding this comment

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

공식문서에서 middleware의 경우 Edge Runtime에서 돌며, axios의 경우 내부적으로 환경에 따라 Node.js API를 사용하기 때문에 fetch를 사용하는 게 좋다고 알고 있습니다..!
아래 refreshtoken의 경우는 fetch를 사용한 것 같은데,, 혹시 이 부분은 axios로 한 이유가 있나요?!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

사실 fetch로 해도 되긴 하는데, 에러 응답도 json으로 파싱해야 해서 파싱 과정을 생략하는 axios로 처리했습니다.

공식문서에서 middleware의 경우 Edge Runtime에서 돌며, axios의 경우 내부적으로 환경에 따라 Node.js API를 사용하기 때문에 fetch를 사용하는 게 좋다

요부분은 저도 처음 봤는데요! axios의 경우 내부적으로 환경에 따라 Node.js API를 사용한다는 내용은 공식문서에서 못찾아서, 어디서 확인할 수 있는건지 가이드 부탁드려도 될까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

우선, 이 부분은 fetch로 바꿨습니다!

@aken-you aken-you merged commit 7234d61 into develop Sep 5, 2025
@aken-you aken-you mentioned this pull request Sep 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants