[Saida Lab] Next-auth 적용하기 (2)_callback

https://next-auth.js.org/

 

지난 번에는 Next-auth를 이용해서 간단하게 카카오 로그인을 구현해 봤다.

React를 사용 했을 때와 비교해 봤을때, 초기에 세팅하는 구조가 달라서 조금 헷갈릴 수 있겠지만

사용하는 방법은 (개인적으로) 월등하게 편하게 사용할 수 있지 않나 하는 생각이 든다.

거기다가 useSession을 사용하면 어떤 페이지에서도 가져와서 사용할 수 있다는 점도 좋은 점 중 하나인 것 같다.

 

그렇게 카카오 로그인을 해결한 개발자는 오래오래 행복하게 살ㅇ...

 


 

뭐가 문젠데?

 

현재까지 적용한 것만 보면 카카오에서 전달해준 유저의 기본적인 정보와

카카오에서 제공하는 토큰을 사용할 수 있게 됐다.

그러나 서버의 API는 카카오의 토큰을 사용할 수 없고, 서버에서 제공해 주는 토큰을 사용해야만 통신을 할 수 있다.

그래서 이번에는 next-auth에 있는 callback을 활용해 보고자 한다.

 

Callbacks

 

...
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      return true
    },
    async redirect({ url, baseUrl }) {
      return baseUrl
    },
    async session({ session, user, token }) {
      return session
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      return token
    }
...

 

next-auth에서 제공하는 callback의 종류는 다음과 같다.

내가 이번 프로젝트에 사용했던 callback은 signIn / jwt / session 이 3가지를 사용했다.

 

// app/api/auth/[...nextauth]/route.ts

import { NextAuthOptions } from "next-auth";
import kakaoProvider from "next-auth/providers/kakao";

export const authOptions: NextAuthOptions = {
  providers: [
    kakaoProvider({
      clientId: process.env.KAKAO_CLIENT_ID as string,
      clientSecret: process.env.KAKAO_CLIENT_SECRET as string,
    }),
  ],
  
  callbacks: {
  ...
};

 

callback의 사용 위치는 다음과 같은데, 공식문서는 page router 기준으로 작성이 되어있어서

아 app router는 뭔가 또 다르겠구나..! 했는데, 몇 시간의 삽질 후 적용된 것을 보니 그냥 똑같다..ㅎ

 

Signin

 

// data의 타입

export interface DefaultSession {
  user?: {
    name?: string | null
    email?: string | null
    image?: string | null
  }
  expires: ISODateString
}

 

저번 글의 끝 부분을 생각해 보면 next-auth에서 useSession을 사용해서 data라는 값을 가져와서 사용할 수 있었는데,

data를 들여다보면 위와 같은 구조를 가지고 있는 것을 볼 수 있다.

여기서 user라는 키에 name, email, image 값을 지닌 객체가 들어가 있는 것을 볼 수 있는데,

이 값은 기본적으로 카카오에서 보내주는 값이지만, 서비스에 맞게 세팅을 해 줄 수도 있다.

 

...
    async signIn({ user, account }) {
      const data = await postLogin(account?.access_token || ""); // 서버와의 통신

      if (account?.provider) {
        user.name = data.user.name;
        user.id = data.user.userSeq;
        account.access_token = data.token.accessToken;
        account.issuedAt = data.token.issuedAt;
      }

      return true;
    },
...

 

나는 그 세팅을 우선 signIn callback에서 진행해 줬는데,

해당 callback에서 서버에 카카오에서 제공해 준 토큰을 담아 전달하면

유저의 이름, 번호, 서버 토큰을 받아오게 되고 해당 값을 각각 user 와 account의 데이터에 맞게

값을 다시 할당해 주었다.

 

여기서 하나 참고할만한 점은, Signin callback은 다른 callback과는 다르게 유일하게 boolean 값을 반환한다.

이게 만약 false라면 로그인이 거부되는 형태가 될 것이다.

그래서 그런 것도 생각해서 만약 카카오에서 토큰은 받았는데 어떠한 분기처리가 필요하다면

여기서 진행해 줘도 될 것 같다.

 

 

jwt

 

...
async jwt({ token, account, user }) {
  if (account) {
     token.accessToken = account.access_token;
     token.issuedAt = account.issuedAt;
  }

  return token;
},
...

 

jwt callback은 토큰과 관련된 데이터를 다룰때 사용한다.

공식 문서에서는 로그인 혹은 클라이언트에서 세션에 접근 할 때 마다 호출이 되는 callback이라고 설명한다.

 

이 callback에서 나는 signin callback에서 세팅했던 토큰을 재할당 해 주었다.

이렇게 재할당 해준 이유는 결국 여기에서 세팅한 값들을 클라이언트 사이드에서 사용해야 하는데

이 callback은 서버 사이드에서 이뤄지는 형태라서 내가 필요한 데이터를 바로 클라이언트에 저장할 수 없기 때문이다.

 

그래서 세팅한 token이라는 데이터를 다시 반환해준다.

 

session

 

...
    async session({ session, token }) {
      if (token.accessToken) {
        session.user.id = token.sub || "";
        session.user.accessToken = token.accessToken as string;
        session.user.issuedAt = token.issuedAt;
      }
      
      return session;
    },
...

 

공식 문서에서 session callback에 대한 내용을 들여다 보면

 jwt callback을 통해 토큰에 추가한 항목을 사용할 수 있게 하려면
여기에 명시적으로 전달하여 클라이언트가 사용할 수 있게 해야 합니다.

 

라고 적혀있다.

 

즉, 결과적으로 위의 여러 callback에서 세팅했던 내용들을 session이라는 callback에서 처리해줘야

실제적으로 클라이언트에서 사용할 수 있는 데이터가 만들어진다는 얘기가 되겠다.

그래서 필요한 값들을 session.user에 넣어줬는데, 여기서 약간의 오류가 생길 수 있다.

 

// data의 타입

export interface DefaultSession {
  user?: {
    name?: string | null
    email?: string | null
    image?: string | null
  }
  expires: ISODateString
}

 

다시 data의 타입을 살펴보면 user 객체 안에 name, email, image 만 데이터가 있는데

위의 session callback을 보면 여기에 없는 id, accessToken, issuedAt 이라는 키에 데이터를 할당하고 있다.

 

// types/next-auth.d.ts

import "next-auth";

declare module "next-auth" {
  interface User {
    id: string;
    name: string;
    email: string;
    image: string;
    accessToken: string;
    issuedAt: string;
  }
  
  interface Session extends DefaultSession {
    user: User;
    expires: string;
    error: string;
  }
}

 

이럴 때는 위와 같이 next-auth.d.ts 파일을 만들어서 사용해 주면 된다.

next-auth에서 사용하는 Session의 타입을 재정의 하는 과정이라고 생각하면 될 것 같다.

이렇게 파일을 만들어주면 원하는 값을 callback 과정에서 세팅해서 useSession을 통해

클라이언트에서 사용할 수 있게 된다.

 


 

참고 자료

 

- Next-auth 공식문서 [callbacks]

- next-auth.d.ts 관련 에러 해결할 때 참고한 글