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

박지수 Jisu Park님의 프로필 이미지

작성한 질문수

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

refresh token rotation 질문

24.09.06 13:49 작성

·

51

·

수정됨

0

안녕하세요 강의를 듣고 next-auth를 활용하여 개발 중에 궁금한 점이 생겨 질문 드립니다.

프로젝트에서 일반 로그인과 소셜 로그인을 모두 사용하고 있어

next-auth의 signin callback에서 로그인일 경우 자체 서버에 요청하여 accessToken과 refreshToken을 발급 받고 jwt callback에서 엑세스 토큰의 만료 시간을 체크하여 만료 시에 재발급 요청이 되도록 구현하고 있습니다.

이렇게 구현했을 경우 간헐적으로 서버 시간과의 타이밍 이슈 때문인지 api 요청 시에 401 토큰 만료 에러가 발생하는 순간이 존재합니다.

따라서 401 상태코드일 경우에 refreshToken 토큰 재발급 요청을 하려고 하는데 refreshToken이 현재 next-auth에서 jwt 콜백에서 리턴되어 jwt토큰으로 생성되어 있습니다.

이 401일 경우 재발급 요청 처리를 클라이언트 단에서 할 경우 refreshToken이 노출되어 보안상 이슈가 있을 것으로 예상이 되는데 서버단에서 처리를 하려면 next에서 어디서 처리를 하면 좋을 지 문의드립니다.

 async signIn({ account, user, credentials }) {
      const cookieStore = cookies();

      const authorizationParams = JSON.parse(
        cookieStore.get("authorization-params")?.value ?? ""
      );

      const action = authorizationParams.action;
      user.platform = authorizationParams.platform;
   
      if (!account?.provider || !sns_type_map[account.provider]) {
        throw new Error("Unsupported provider");
      }
      user.snsType = sns_type_map[account.provider];
      if (action === auth_action_type.signin) {
        return handleSignIn(user); // getAccessToken
      }
      if (action === auth_action_type.signup) {
        return handleSignUp();
      }
      return false;
},
async jwt({ token, account, user }) {
      // Initial sign in
      if (account && user) {
        return {
          ...token,
          ...user,
          accessToken: user.refreshToken,
          expiresAt: new Date(
            Date.now() + (user?.expiresIn ?? 0)
          ).toISOString(),
          refreshToken: "refreshtoken",
        };
      }

      // Return previous token if the access token has not expired yet
      if (new Date() < new Date(token.expiresAt as string)) {
        console.log("@@@@@@valid");
        return token;
      } else {
        // Access token has expired, try to update it
        console.log("@@@@@@expired");
        const cookieStore = cookies();
        const authorizationParams = JSON.parse(
          cookieStore.get("authorization-params")?.value ?? ""
        );
        const body = {
          ....
        };

        const tokenData = await authApi.reissueAccessToken(body);

        // reissue token
        return {
          ...token,
          ...user,
          accessToken: tokenData.accessToken,
          expiresAt: new Date(
            Date.now() + (tokenData.expiresIn ?? 0)
          ).toISOString(),
          refreshToken: tokenData.refreshToken,
        };
      }
    },

미들웨어에서 401 상태 코드 처리하는 것도 알아보았으나 일반적인 방식인지 궁금합니다.

미들웨어에서 401일때 재발급 요청 시 새로운 엑세스 토큰으로 재요청이 정상적으로 되는 것은 확인했으나 다시 next-auth의 jwt토큰에 재발급된 값을 세팅해줘야 하는데 어떻게 해야할지도 궁금합니다.

import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';

export async function middleware(request) {
  const token = await getToken({ req: request, secret: process.env.AUTH_SECRET });

  if (request.nextUrl.pathname.startsWith('/gateway')) {
    const accessToken = token?.accessToken;

    const headers = new Headers(request.headers);
   


      if (response.status === 401) {
      const refreshToken = token?.refreshToken;
      const refreshResponse = await fetch("/reissueToken")
      const refreshData = await refreshResponse.json();

      if (refreshResponse.ok) {
        const newAccessToken = refreshData.accessToken;
        headers.set('Authorization', `Bearer ${newAccessToken}`);
      const newResponse = NextResponse.next({
        request: {
          headers: request.headers,
        },
      });
      }
      // 토큰 재발급 실패 시 처리
      return NextResponse.redirect('/login');
    }

    return NextResponse.next(response);
  }

  return NextResponse.next();
}

 

 

 

답변 1

0

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

2024. 09. 06. 17:37

현재 jwt 메서드에서 return에 엑세스토큰과 리프레시토큰을 다 넣고 계신데 이렇게 하면 프론트에 액세스토큰과 리프레시 토큰이 다 전송될 것 같고요(물론 JWE라서 암호화는 되어있겠지만)

리프레시 토큰은 그냥 DB에 저장하거나 Redis에 저장하시면 됩니다. 그리고 그걸 업데이트하거나 불러오는 건 api routes로 만들어두시면 되고요. middleware든 어디든 리프레시토큰을 수정/사용할 때 그 api routes를 호출하시면 됩니다. api routes 호출할 때는 fetch에 header로 cookie 넣어주셔야 하고요.

박지수 Jisu Park님의 프로필 이미지

2024. 09. 06. 17:45

넵 답변 감사합니다! Api routes 호출할때 헤더로 크키를 넣어야한다고 하셨는데 jwt로 암호화된 값을 말씀하시는걸까요??

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

2024. 09. 06. 18:10

아 jwt로 하시면서 쿠키는 안 쓰고계신가요? 뭐든간에 api routes에서 자신을 인증할 수만 있으면 됩니다.

박지수 Jisu Park님의 프로필 이미지

2024. 09. 06. 18:28

jwt 콜백으로 리턴되어 암호화되어 authjs.session-token 에 저장되는 쿠키값 사용하고 있습니다

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

2024. 09. 06. 18:32

네 그거 보내주셔야지 같은 유저로 인식될 겁니다.

박지수 Jisu Park님의 프로필 이미지

2024. 09. 06. 18:36

답변 감사합니다!! 한번 구현해보겠습니다

박지수 Jisu Park님의 프로필 이미지

2024. 09. 12. 08:55

사용자가 세션에 접근할 때마다 암호화된 authjs.session-token 쿠키 값이 바뀌고 있어 쿠키값을 업데이트해줘도 원복되는 현상이 발생하고 있는데 세션에 접근할때마다 암호화된 값이 바뀌는 것이 스펙일까요?? 클라이언트, 서버단에서 접근할때마다 쿠키값이 바뀌는 현상을 확인했고 jwt 콜백에서 토큰을 출력해봤을때 값이 바뀌어있지는 않더라구요.

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

2024. 09. 12. 10:29

토큰 만료시간이 수정되어서 토큰이 바뀌는 것으로 보입니다.