처음에는 Firebase의 인증 시스템을 사용해보려고 했는데, 살펴보다보니 NextAuth.js
로 훨씬 더 간단하게 인증/인가 기능을 구현할 수 있어서 NextAuth.js
를 사용하기로 했다.
OAuth 기능을 사용해보고 싶었어서 이번에는 이메일/비밀번호 대신 OAuth를 통해 로그인/로그아웃하는 기능을 구현하기로 했다.
공식 문서에 나와있는 대로 구현을 했더니 아래와 같은 에러가 계속 발생했다.
○ Compiling /api/auth/[...nextauth]/route ...
✓ Compiled /api/auth/[...nextauth]/route in 606ms (1120 modules)
⨯ TypeError: r is not a function
Next 14 버전으로 업데이트된 지가 얼마 안 돼서 혹시나 하고 Next 13 버전으로 변경해보았는데, 그랬더니 문제 없이 돌아갔다. Next 14 버전에서 가능한 방법을 찾으려면 시간이 너무 오래 걸릴 듯 하여 결국 아래와 같이 버전을 낮췄다
"next": "^13",
"next-auth": "^4.24.4",
Google OAuth 기능을 사용하려면 우선 구글 클라이언트 아이디와 비밀번호가 필요한데, 이거는 Google Developer Console에서 발급 받을 수 있었다.
발급 받을 때 redirection url을 작성해줘야 하는데, NextAuth.js의 경우 /api/auth/callback/google
로 작성해주면 된다.
그리고 src/app/api/auth/[...nextauth]/route.ts
경로에 아래와 같이 작성해주면 OAuth 관련 설정은 간단히 끝난다.
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { AuthOptions } from "next-auth";
export const authOptions: AuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
}),
],
};
export const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
모든 routes에 대해서 로그인이 되어 있지 않은 상태면 로그인 화면으로 이동하는 기능을 구현하기로 했다.
처음에 생각한 방법은 jotai로 loginAtom이라는 state를 만들고, 이 값이 false이면 로그인 화면으로 이동하게 하려고 했다. 그런데 NextJS는 기본적으로 서버 컴포넌트를 가정하고 있고, 서버 컴포넌트에서는 state값을 사용하지 않기 때문에 이 방법은 적절하지 않았다.
다음으로 고려한 방법은 middleware.ts
파일에서 아래와 같이 getServerSession
함수를 사용해서 로그인 상태 값을 받아오고, 로그인이 되어 있지 않은 경우 로그인 화면으로 리다이렉션 하는 방법이었다. 하지만 아래의 코드로 하면 오류가 발생했는데 middleware.ts
는 Vercel 플랫폼의 Edge 상태에서 실행돼서 getServerSession
함수가 정상적으로 작동할 수 없기 때문이었다.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getServerSession } from "next-auth";
export async function middleware(request: NextRequest) {
const session = await getServerSession();
if (session) {
return NextResponse.next();
}
return NextResponse.redirect(new URL("/api/auth/login", request.url));
결국 성공한 방법은 layout.tsx
에서 로그인 여부를 체크하는 방법이었다. 아래와 같이 getServerSession()
함수로 로그인 상태를 받아오고 로그인이 되어 있지 않으면 redirect()
함수를 사용해서 로그인 화면으로 이동하도록 했다.
import { Layout } from "@/app/components/layout";
import ThemeClient from "@/app/components/themes/ThemeClient";
import { getServerSession } from "next-auth";
import SessionProvider from "@/app/components/auth/SessionProvider";
import { redirect } from "next/navigation";
/** 중략 */
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getServerSession();
if (!session || !session.user) {
redirect("api/auth/signin");
}
return (
<html lang="en">
<body className={inter.className}>
<SessionProvider session={session}>
<ThemeClient>
<Layout>{children}</Layout>
</ThemeClient>
</SessionProvider>
</body>
</html>
);
}
위 코드를 보면 SessionProvider
를 next-auth
가 아닌 프로젝트 내부 컴포넌트로 import하는 것을 발견할 수 있다. 이렇게 한 이유는 SessionProvider
는 클라이언트 컴포넌트여야 하기 때문에 아래와 같이 별도의 컴포넌트를 만들었다.
"use client";
import { SessionProvider } from "next-auth/react";
export default SessionProvider;
로그아웃 기능은 signOut()
함수를 사용하면 간단하게 구현할 수 있었다.
import { signOut } from "next-auth/react";
const Header = () => {
return (
{/* 중략 */}
<IconButton
size="large"
color="inherit"
sx={{
width: 35,
height: 35,
mt: 1,
...{
color: "grey.400",
},
}}
onClick={() => signOut()}
>
<LogoutIcon />
</IconButton>
);
};
export default Header;
NextAuth.js
를 사용해서 아래와 같이 OAuth 로그인 기능을 구현할 수 있었다. 시간이 된다면 NextAuth.js
에서 제공하는 기본 UI 말고 커스텀 UI를 사용하도록 수정해봐야겠다.