Next 14 + next-auth + Axios interceptor

김태규·2024년 12월 2일

Aper 프로젝트

목록 보기
1/4

Next client와 Next server, Spring server를 이용한 프로젝트입니다
Next 14 를 이용하여 구현했습니다.

서술

Next 14와 next-auth 를 이용하여 회원 관리를 구현하는 중 Reissue를 위해 axios instance를 구현하였다.

먼저, 클라이언트와 서버에서 사용할 Axios Instace를 구분해 주었고, 인증이 필요한 인스턴스와 필요없는 인스턴스를 구분해주었다.

// app/api/clientInstance.ts
'use client';
import { default as Axios } from 'axios';
import { getSession } from 'next-auth/react';
import clientReissue from './auth/reissue/client';

// 인증이 필요 없는 인스턴스
export const client = Axios.create({
  baseURL: process.env.BASE_URL,
});
// 인증이 필요한 인스턴스
export const clientAuth = Axios.create({
  baseURL: process.env.BASE_URL,
});
// 요청 인터셉터
clientAuth.interceptors.request.use(
  async config => {
    try {
      const session = await getSession();
      if (!session)
        throw new Error('Axios client-auth interceptor error: no session');
      const accessToken = session.accessToken;
      config.headers.Authorization = `Bearer ${accessToken}`;
      return config;
    } catch (error) {
      return Promise.reject(error);
    }
  },
  error => Promise.reject(error),
);
// 응답 인터셉터
clientAuth.interceptors.response.use(
  response => response,
  async error => {
    try {
      const originalRequest = error.config;
      // 재시도가 필요 없는 경우
      if (error.status !== 401 || originalRequest._retry)
        return Promise.reject(error);
      const session = await getSession();
      if (!session)
        throw new Error('Axios client-auth interceptor error: no session');
      // 재시도 여부 기록
      originalRequest._retry = true;
      // 토큰 갱신 후 재요청
      const accessToken = await clientReissue();
      originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
      return clientAuth(originalRequest);
    } catch (refreshError) {
      return Promise.reject(refreshError);
    }
  },
);

const instances = { client, clientAuth };

export default instances;
// app/api/serverInstance.ts
// ...(클라이언트와 동일한 부분)
import { getServerSession } from 'next-auth';
import { authOptions } from './auth/authOptions';
import { PostReissueResponse } from './auth/reissue/route';

// ...
serverAuth.interceptors.request.use(
  async config => {
    // 서버 세션 조회
    const session = await getServerSession(authOptions);
    // ...
  },
  error => {
    return Promise.reject(error);
  },
);

serverAuth.interceptors.response.use(
  response => response,
  async error => {
    // ...
    const session = await getServerSession(authOptions);
    if (!session)
      throw new Error('Axios sever-auth interceptor error: no server session');
    // ...
    // 리프레시 토큰 갱신 (미작동)
    try {
      const res: AxiosResponse<PostReissueResponse> =
        await axios.post('/api/auth/reissue');
      session.accessToken = res.data.accessToken;
    } catch (refreshError) {
      console.log('Refresh error: clear server session');
      session.accessToken = '';
      session.user.id = -1;
      session.user.email = '';
      session.user.penName = '';
      session.user.fieldImage = '';
      return Promise.reject(refreshError);
    }
  },
);

const instances = { server, serverAuth };

export default instances;

대부분 코드는 공식문서인 Axios Docs-인터셉터에서 명시한 부분과 유사하다. 그러나 NextJS와 next-auth를 적용하며 특징적인 부분이 몇군데 있다.

1. next-auth의 Session에서 AccessToken 관리

토큰 관리를 위해 next-auth의 session을 사용했다. 따라서 클라이언트에서는 getSession(), 서버에서는 getServerSession()을 이용하여 AccessToken을 불러오는 것을 볼 수 있다.

2. 클라이언트 세션 갱신

Client-side에서 세션을 업데이트 하는 방법useSession().update({...session}) 을 이용하는 방법이 있다. 그러나 useSession()react hook이기 때문에 같은 hook 이나 component 안에서만 호출이 가능하다. 따라서 대용으로 next-auth의 signIn('credentials', options) 메소드를 사용해 주었다.

next-auth의 Credentials Provider에서 credentials를 통해 reissue 변수를 받고, 여기에 갱신할 session의 정보를 담아 처리했다.

이를 통해 클라이언트와 서버 세션을 동시에 갱신할 수 있다.

// app/api/auth/[...nextAuth]/route.ts
// ...imports

export const authOptions: NextAuthOptions = {
  // 생략
  providers: [
    Credentials({
      // 로그인 시 받을 데이터 정의
      credentials: {
        ...loginData,
        reissue: {...CredentialInput},
      },
      authorize: async credentials => {
        // 리이슈인 경우 바로 인증 & 세션 갱신
        if (credentials?.reissue) {
          const newSession = JSON.parse(credentials.reissue) as User;
          return { ...newSession };
        }
        // ...로그인 관련 코드
      },
    }),
  ],
  // 생략
};

3. 서버 세션 갱신

next-auth v4 에서는 서버에서 세션을 갱신할 메소드가 없다. @auth0/next-auth0라이브러리의 updateSession 메소드를 사용하거나 아직 베타버전인 next-auth v5update를 이용하면 갱신이 가능하다고 한다.

// ...
export const { handlers: { GET, POST }, auth, signIn, signOut, update } = NextAuth(authConfig);

그러나 불안정한 메소드를 이용하고싶지 않아 프로젝트에서는 제외하고, 서버사이드에서는 인증이 필요한 메소드를 사용하지 않도록 하였다.

profile
Frontend, Mobile Developer

0개의 댓글