callbacks
옵션에서 session
함수의 파라미터인 token
의 sub
프로퍼티에 유저 ID가 저장되어 있다.token.sub
값을 session.user.id
에 추가 후 session
을 리턴해준다.getServerSession
에서 유저 ID를 성공적으로 추출 가능하다.초보 개발자의 필기장입니다. 오류 지적, 보충 설명 댓글을 언제든지 환영합니다.
NextJS가 제공하는 SSR 기능을 극대화하고자 했다. 클라이언트가 제공하는 인증 토큰을 통해 서버에서 직접 유저 정보를 추출할 수 있다면 유저와 관련된 정보를 서버에서 fetch하고, 나아가 허가된 페이지와 API만 호출할 수 있도록 하는 접근 제어도 NextJS api route로 구현할 수 있을 것이다.
NextAuth의 강력한 소셜 로그인 기능과 세션 기능 또한 NextAuth의 테두리 안에서 해결책을 찾아보고 싶은 이유이기도 했다. 내 실력으로 NextAuth가 없다면 인증 기능 개발로 개발 기간이 1주는 더 늘어났을 것 같다...
사실 getServerSession
의 리턴값으로 유저의 이메일 주소가 기본적으로 제공되기 때문에 대부분의 경우에는 커스터마이징이 필요없다(중복 이메일을 허용하지 않는 이상). 하지만, 내가 진행하는 프로젝트의 경우 LINE 로그인을 추가해야 하는데, 기본적으로 이메일 주소가 오지 않는다. 카카오 로그인처럼 다소 귀찮은 절차를 거치면 이메일도 scope에 포함시킬 수 있으나, 이 역시 이메일을 필수 항목으로 설정하는 것은 불가능하다.
그래서 이메일 이외에 유저를 unique하게 식별할 수 있는 유저 ID를 추출하기로 했다. 다행히도 NextAuth 구성 옵션 중 callbacks
옵션에서 해결방법을 찾았다.
callbacks
// app/api/auth/[...nextauth]/route.ts
export const authOptions: AuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
LineProvider({
clientId: process.env.LINE_CLIENT_ID as string,
clientSecret: process.env.LINE_CLIENT_SECRET as string,
}),
],
callbacks: {
session: ({ session, token }) => ({
...session,
user: {
...session.user,
id: token.sub,
},
}),
},
// ...other options
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
ORM으로 Prisma를 사용하기 때문에 adapter
옵션을 PrismaAdapter
로 설정하였고, LINE 로그인 구현을 위해 providers
옵션 array에 LineProvider
를 추가하였다.
중요한 건 callbacks
옵션에 있는 session
함수인데, 공식 문서에 따르면 session
함수는 세션이 확인될 때마다 호출된다. 서버에서 유저 정보를 추출할 때 사용할 getServerSession
함수도 결국에는 세션을 확인하는 과정이기에, session.user.id
에 토큰에서 제공되는 유저 ID인 token.sub
값을 설정해주면 getServerSession
함수에서 ID 추출이 가능해진다.
getCurrentUser
// actions/getCurrentUser.ts
export default async function getCurrentUser() {
try {
const session = await getServerSession(authOptions);
// 유저 ID가 없으면 로그인 x
if (!session?.user?.id) {
return null;
}
// DB에서 해당 ID를 가진 유저 객체 추출
const currentUser = await prisma.user.findUnique({
where: {
id: session.user.id,
},
});
// 잘못된 유저 ID인 경우 로그인 x
if (!currentUser) {
return null;
}
// 추출된 유저 객체 return
return currentUser;
} catch (error) {
// 에러 발생 시 로그인 x
return null;
}
}
이제 서버에서 getCurrentUser
함수를 통해 현재 사용자가 정상적으로 로그인이 되어있는지, 로그인하였다면 어떤 사용자인지 파악할 수 있게 되었다.
User
& Session
아직 마지막 문제가 남아있었는데, 바로 typescript 에러였다. 기본적으로 session.user
는 id
프로퍼티를 갖고 있지 않기 때문에, 아래 에러가 발생하여 컴파일이 되지 않는다.
Property 'id' does not exist on type '{ name?: string | null | undefined; email?: string | null | undefined; image?: string | null | undefined; }'.
해결방법으로 Module Augmentation을 이용해 User
타입과 Session
타입을 extend하였다.
// next-auth.d.ts
import { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface User {
id: string;
}
interface Session extends DefaultSession {
user?: User;
}
}
이렇게 타입스크립트 에러도 해결하면서 서버에서 세션으로부터 추출한 유저 ID를 이용해 DB로부터 유저 정보를 추출하는 기능을 성공적으로 구현하였다.