인프런 커뮤니티 질문&답변

작성자 없음

작성자 정보가 삭제된 글입니다.

Next + React Query로 SNS 서비스 만들기

Next.js 14 배포 후 액세스토큰 업데이트 안되는 문제

작성

·

596

0

안녕하세요 제로초님

강의 잘 듣고 있습니다.

 

사실 이 문제가 몇개월동안 계속 해결이 안되어서 강의를 수강한거나 마찬가지인데요.

 

아무리 스택오버플로우나, next.js issue에 직접 글을 올려도 해결이 안되어서 이렇게 질문 글 올립니다.

일단 Next.js 14 + Express 조합으로 배포를 한 상황이고 jwt 토큰을 쿠키에 담아서 로그인 기능을 구현했습니다.

또한 next.js Middleware 내부에서 액세스토큰을 재발급 받아주고 있습니다.

순서는 이렇습니다.

 

  1. 로그인 시에 사용자가 아이디 + 비밀번호를 입력해서 서버에게 요청한다.

  2. 서버에서 해당 아이디 값을 받아서 조회 후, 새로운 리프레시 토큰 + 액세스 토큰을 클라이언트로 보내준다.

백엔드 내부 코드는 다음과 같습니다.

 

 const newAccessToken = createNewAccessToken(existingUser);

 const newRefreshToken = createNewRefreshToken(existingUser);

 const hashedToken = await hashValue(newRefreshToken);

 await deleteRefreshTokenData(existingUser.id);

 await createRefreshTokenData(hashedToken, existingUser.id);

 return res.json({
      success: true,
      accessTokenValue: newAccessToken,
      refreshTokenValue: newRefreshToken,
  });
  1. Next.js 측에서 액세스 토큰과 리프레시 토큰을 받아서 API ROUTE로 POST 요청을 날려서 set cookie를 해준다.

const sendTokenCookieToHandler = async (accessTokenValue: string, refreshTokenValue: string) => {
  await fetch('/api/token', {
    method: 'POST',
    body: JSON.stringify({ accessTokenValue, refreshTokenValue }),
    headers: new Headers({
      'Content-Type': 'application/json',
    }),
    cache: 'no-store',
  });
};

export default sendTokenCookieToHandler;
```
  1. 그 후 액세스 토큰이 만료 되면, Next.js 미들웨어 단에서 리프레시 토큰을 다시 백엔드로 보내서 새로운 액세스 토큰을 받아온다.

Next.js 미들웨어 코드는 이렇습니다.

 

export async function middleware(request: NextRequest) {
  const { accessToken, refreshToken } = getTokenValues(request);

  if (!accessToken && refreshToken) {
    const res = await getNewAccessToken(refreshToken);

    if (res && res.newAccessToken) {
      const response = NextResponse.next();
      const { newAccessToken } = res;

      response.cookies.set('accessToken', newAccessToken, {
        expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
      });
      return response;
    }
  }

  return NextResponse.next();
}
```

 

  1. 받아온 액세스 토큰 값을 아예 middleware 단에서 set cookie 를 통해 쿠키로 설정해준다.

근데 여기서 문제는 초기에는 정상적으로 잘 작동합니다.

액세스 토큰이 만료 된 후에도 정상적으로 새로운 액세스토큰을 받아오기도 하고요

근데 문제는 일정 시간 후에(제가 생각했을때 리프레시토큰이 만료 된 후, 다시 로그인 요청을 보냈을때 같습니다.) 아예 새로운 리프레시 토큰과 액세스 토큰을 제대로 받아오지 못하는거 같습니다.

근데 F12 를 켜서 확인해보면 백엔드로 api 콜은 정상적으로 가고 있습니다..

 

그래서 현재 다시 재배포를 할때마다 초기 상태로 돌아가서 로그인이 정상적으로 작동 하게 되는데요.

어디가 문제인걸까요 ?

 

여러 오픈톡이나 커뮤니티에 물어봤지만, 어떤분은 Next.js 내부에서 같은 라우트 그룹이면 미들웨어를 거치지 않아서 발생하는 문제라고도 하시는데

 

초기 배포 당시에, 리프레시 토큰을 처음에 잘 받았을때 일부러 액세스 토큰을 삭제하고 다시 새로고침하면 액세스 토큰을 제대로 잘 받아오고 있습니다..

 

도저히 아무리 이리저리 수정해봐도 몇개월 동안 고치지 못해서 이렇게 글 남깁니다.

 

읽어주셔서 감사합니다.

답변 3

0

근데 개발 환경에서는 정상적으로 꾸준히 재발급이 되는데 배포를 하니까 발생하네요

 

오픈 카톡이나 이런곳에서 물어보니까 뭐 미들웨어는 같은 라우트 그룹일땐 거치지 않는다 라는 말씀도 있었고

 

nextjs 의 헤더 자체가 일반 헤더와는 다르다라는 의견 있었는데요

 

혹시 저 말고 이런 에러 겪으셨던 수강생은 따로 안계신걸까요?

 

제가 구현한 흐름이 뭔가 잘못 된건지, 제가 모르는 nextjs 의 내부 흐름이 있는건지..

 

토큰 관련해서 이런 에러가 발생하는게 흔한건지.. 첫 배포인데 이런 에러가 발생하다보니 좀 감이 안잡혀서 여쭙습니다.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

네 다른 분 케이스도 본 적이 거의 없고, 저 자체는 액세스토큰을 안 씁니다. 저는 쿠키/세션으로 로그인을 해서요.

전체적인 흐름은 문제가 없어 보이는데, 개별 과정에서 잘못되고 있는 것이 아닌지 궁금하네요.

반대로 리프레시토큰/액세스 토큰을 정상적으로 발급하고 있다면 정상적으로 발급하는 로그는 백엔드에 있지 않나요?

네 백엔드 로그에서는

POST /api/users/new-access-token 200 6.438 ms - 524
POST /api/users/new-access-token 200 6.438 ms - 524

이런식으로 잘 요청이 들어옵니다.

next도 말씀하신 대로 수정해보고 미들웨어 코드를 함수 분리 안하고 다 작성해보면

export async function middleware(request: NextRequest) {
  const { pathname, searchParams } = request.nextUrl;
  const { accessToken, refreshToken } = getTokenValues(request);
  let response = null;

  if (!accessToken && refreshToken) {
    const res = await getNewAccessToken(refreshToken);
    if (res && res.newAccessToken) {
      response = NextResponse.next();

      response.cookies.set('accessToken', res.newAccessToken, COOKIE_CONFIG.access_token);

      response.headers.set('X-NewAccessToken', res.newAccessToken);
    }
  }

  return response || NextResponse.next(); 


이런식으로 초기에 null 값을 줬다가 후에 액세스 토큰 값이 없으면 그때 next()를 호출하는 식으로 바꾸어보았습니다.

현재 이래도 작동은 하는 상황인데, 이런식의 구현을 말씀하신것이 맞으실까요 ?

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

아뇨 middleware의 구현은 크게 문제는 아닐 것 같습니다. 지금 제일 문제는 문제가 정확하게 뭔지를 모르는 건데요. 액세스 토큰이 재발급되고 있는게 아니라는 걸 어떻게 파악하시는 건가요?

액세스 토큰은 재발급해서 값은 제대로 가져오는 상태입니다.

미들웨어 단에서 콘솔로 확인해보고 백엔드 로그도 확인해보면 정상적으로 값은 보내줍니다.

 

  if (!accessToken && refreshToken) {
    const res = await getNewAccessToken(refreshToken);
    if (res && res.newAccessToken) {
      response = NextResponse.next();

      console.log(res.newAccessToken) // 값 제대로 나옴

      response.cookies.set('accessToken', res.newAccessToken, TOKEN_COOKIE_OPTION.access_token);

      response.headers.set('X-NewAccessToken', res.newAccessToken);
    }
  }

근데 이제 맨 처음 유저 로그인 후에, 즉 맨 처음 로그인때 리프레시토큰과 액세스 토큰을 제대로 받아오고 그 후에 액세스 토큰이 만료 된후에도 꾸준히 재발급이 됩니다.

 

하지만 리프레시 토큰과 액세스 토큰이 다 만료되어서 재로그인을 하면 저 콘솔 값은 제대로 나오지만 F12로 확인해보면 액세스 토큰이 쿠키에 제대로 심어지지 않고 있는 상황입니다.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

혼자서 이것저것 또 해보다가 아직도 안되어서 이렇게 글 남깁니다..

네 14.2 버전 사용중입니다.

해당 글은 미들웨어에서 쿠키를 set해주고 layout.tsx에서 헤더를 통해 쿠키 값을 가져온다는 내용 같은데..

저 같은 경우 아예 미들웨어 단에서 set.cookie가 안되는 문제 같아서요ㅠ

export async function middleware(request: NextRequest) {
  const { pathname, searchParams } = request.nextUrl;
  const { accessToken, refreshToken } = getTokenValues(request);
  let response = NextResponse.next();

  if (!accessToken && refreshToken) {
    const newAccessToken = await getNewAccessToken(refreshToken); // 콘솔 값 잘 찍힘
    setCookieOfToken(response, 'accessToken', newAccessToken);
  }

  return response;
}

export const config = {
  matcher: ['/', '/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

- setCookieOfToken.ts

const setCookieOfToken = (response: NextResponse, type: string, token: string) => {
  const cookieConfig =
    type === 'accessToken' ? TOKEN_COOKIE_OPTION.access_token : TOKEN_COOKIE_OPTION.refresh_token;

  response.cookies.set(type, token, cookieConfig);
};
화면 기록 2024-08-17 오후 12.54.05.gif

 

영상을 보시면 임의로 액세스 토큰을 삭제하고 새로고침을 하면, 액세스 토큰은 재발급이 되었지만 로그인한 회원 정보는 풀려있습니다.

또 다시 새로고침을 해야지만 회원정보를 받아오고 있구요..

전체 코드 다시 첨부해봅니다.

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

새로운 리프레시 토큰과 액세스 토큰을 제대로 받아오지 못하는거 같습니다.

라고 하셨는데, 이 때 백엔드에서 에러 로그같은 거 안 남나요?

리프레시토큰이 만료됐을 때는 사용자가 수동으로 프론트에서 다시 로그인하는 건가요?

네 에러 로그도 안남습니다.. 정상적으로 응답까지 받는 상황입니다.

리프레시토큰이 만료됐을 때는 사용자가 수동으로 프론트에서 다시 로그인하는 건가요?

네 맞습니다. 리프레시토큰까지 만료됐을때는 사용자가 수동으로 다시 로그인해주어야합니다.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

이건 일단 정확하게

  1. 리프레시토큰 만료 후, 재발급 이후부터 에러가 난다는 확증이 필요하고,

  2. 리프레시토큰 재발급이 제대로 되는 건지,

  3. 아니면 재발급 후 액세스토큰이 문제인건지

이런식으로 단계별로 좁혀가셔야할 것 같네요.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

근데 궁금한게 NextResponse.next() 호출 후 cookie set해도 쿠키가 적용되나요?

https://nextjs.org/docs/app/api-reference/functions/next-response#next

저는 항상 next 안에서 쿠키 세팅을 했어서요.

https://nextjs.org/docs/app/building-your-application/routing/middleware#using-cookies

이 미들웨어에서 보면, 아예 NextReponse.next()를 reponse 변수에 담아주고 set을 해주고 있는데 차이점이 있을까요?

0

안녕하세요, 인프런 AI 인턴입니다.

디건버님의 상황에서, 저희가 제공할 수 있는 답변은 다음과 같습니다. 소개드린 자료는 직접적인 해결책을 제시하지는 않지만 토큰 갱신과 관련된 문제를 해결하는 방법에 대해 비슷한 상황에서의 접근 방식을 제공합니다.

다음 글, “refresh token rotation 오류”에서 제로초님께서는 NextAuth.js를 이용해 토큰 갱신 로직을 구현한 상황에 대해 답변을 주셨습니다. 비록 NextAuth.js를 사용한 상황이지만, 이 답변에서는 토큰 갱신 로직에서 마주칠 수 있는 일반적인 문제점과 그에 대한 해결 방식을 제시합니다. 특히, 답변에서는 토큰 갱신이 예상대로 이루어지지 않을 때의 가능한 원인과 해결책을 탐색하는 방법에 대해 언급하고 있습니다.

여기에 언급된 “jwt callback과 session callback의 실행 순서와 관련하여 발생할 수 있는 문제”는 비록 NextAuth.js 내에서의 상황이지만, 디건버님의 상황에서도 비슷한 문제가 발생할 가능성이 있습니다. 즉, 토큰을 갱신하고 쿠키에 반영하는 로직에서 발생할 수 있는 시간적 차이나 실행 순서 때문에 기대한 대로 동작하지 않을 수 있다는 것입니다.

이 경우, 디건버님께서는 미들웨어에서 리프레시 토큰을 이용해 액세스 토큰을 갱신하는 과정에서 비슷한 문제에 직면해 있을 수 있습니다. 따라서 미들웨어 또는 액세스 토큰과 리프레시 토큰을 처리하는 로직에서 발생할 수 있는 실행 순서나 상태 관리의 문제를 검토해볼 필요가 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

작성자 없음

작성자 정보가 삭제된 글입니다.

질문하기