면접에서 소셜로그인 구현할 줄 아시나요? 라는 질문을 받아본 적이 있다.
"앗.. 아니요... 해본적이 없습니다..."
이번 프로젝트에서 소셜 로그인 구현하는 방법을 기록해보려고 한다.
다음 면접에서는 당당하게 대답할 수 있기를 바라며 :)
오늘의 기록 목표 2가지
1.NextAuth.js를 사용하여 구글 로그인을 구현해보기.
2.이 블로그를 따라 하면 소셜 로그인이 뚝딱 구현되고, 이해도 쉽게 되는 내용으로 글쓰기.
즉, NextAuth.js로 구글로그인을 쉽게 구현할 수 있다!
yarn add next-auth
위 공식문서 내용과 같이 13.2로 업데이트 된 후, pages가 없어지고 app디렉토리를 지원하고 있다.
공식문서
src/app/api/auth/[...nextauth]/route.ts 파일을 만들고 그 안에 공식문서 코드를 넣어준다.
import NextAuth from "next-auth"
const handler = NextAuth( //NextAuth.js를 사용하여 인증과 관련된 작업을 처리하는 핵심 함수이다.
{ ...})
export { handler as GET, handler as POST }
구글, 카카오, 깃헙 등 정말 많은 서비스로그인을 구현할 수 있다. 아래 공식문서에서 각 사용법에 대해 확인이 가능하다.
Overview | NextAuth.js
공식문서에서 소셜로그인 코드 확인하기
나는 구글 로그인을 원하기 때문에 아래와 같이 코드를 작성해주면 된다.
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google";
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET
}),
],
});
export { handler as GET, handler as POST };
GOOGLE_CLIENT_ID = 구글에서 받은 아이디 //3.에서 아이디 받아옴.
GOOGLE_CLIENT_SECRET = 구글에서 받은 키 //3.에서 아이디 받아옴.
해당 오류 메시지는 'clientId'의 예상 타입이 'string'이어야 한다고 오류를 보내고 있다.
만약 2-1.에 적힌 환경 변수에 GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET이 없다면 'undefined'일수도 있다. 이 타입 오류를 없애기 위해서는 'clientId'속성이 'undefined'가 아니라 항상 'string' 값으로 할당되도록 하면 된다. 결국! GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET가 없을 경우, 빈 문자열을 쓰도록 만들어주면 된다.
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID || "", // 수정!!!!
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", // 수정!!!!
}),
],
});
export { handler as GET, handler as POST };
승인된 자바스크립트 원본, 승인된 리디렉션 URL 현재 로컬 주소를 적어주었다. 배포 후 주소 바꿔줄 예정이다. 이 칸을 입력하지 않으면 오류가 난다.
로그인한 사용자 정보를 가지고 있는 context를 만든다.
context안에 SessionProvider가 실제 로그인한 데이터를 가지고 있다.
"use client"; //next에서 context를 사용하기 위해서는 csr 렌더링으로 명시해주어야 한다.context는 상태를 가지고 있기 때문에!!! 서버컴포넌트는 상태에 접근 할 수 없다.
import { SessionProvider } from "next-auth/react";
type Props = {
children:React.ReactNode;
}
export default function AuthContext({ children }: Props) {
return <SessionProvider>{children}</SessionProvider>;
}
Session Provider가 뭘까?
로그인에 대한 세션 정보를 얻어오기 위해서 Session Provider를 사용한다. 또한 세션 정보를 접근하기 위해서는 useSession이라는 리액트 훅을 사용해야 접근이 가능하다.
<AuthContext>
를 자식컴포넌트에 감싸주자.export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={openSans.className}>
<body className="w-full max-w-screen-xl overflow-auto mx-auto">
<AuthContext>
<header className="sticky top-0 bg-white z-10 border-b">
<Navbar />
</header>
<main className="px-6">{children}</main>
</AuthContext>
</body>
</html>
);
}
공식문서
const { data: session } = useSession() //세션 정보를 가져옴
export default function Navbar() {
const pathname = usePathname();
const { data: session } = useSession(); //세션 정보를 가져옴
return (
<nav className="flex items-center justify-between px-6">
<Link href="/">
<h1 className="text-3xl font-bold">unigram</h1>
</Link>
<ul className="flex gap-4 items-center p-4">
{menu.map((item) => (
<li key={item.href}>
<Link href={item.href}>
{pathname === item.href ? item.clickedIcon : item.icon}
</Link>
</li>
))}
{session ? ( //세션 정보가 있으면 signOut()호출
<ColorButton text="Sign out" onClick={() => signOut()} />
) : ( //세션 정보가 없으면 signIn()호출
<ColorButton text="Sign in" onClick={() => signIn()} />
)}
</ul>
</nav>
);
}