안녕하세요 트리플랩입니다.
👉 NextAuth.js?
개발자들이 복잡한 인증 로직을 구현하지 않고도 빠르고 안전하게 사용자 인증을 구현할 수 있도록 도와준다.
👉 src/app/api/auth/[...nextauth]/route.ts에 초기화 세팅
Options
- providers: 공급자에는 다음과 같은 기본 옵션 세트가 제공됩니다.
- pages: 사용자 정의 로그인, 로그아웃 및 오류 페이지를 생성하려는 경우 사용할 URL을 지정합니다.
지정된 페이지는 해당 내장 페이지를 재정의합니다.
- callbacks: 콜백은 작업이 수행될 때 발생하는 작업을 제어하는 데 사용할 수 있는 비동기 함수입니다.
콜백은 데이터베이스 없이 액세스 제어를 구현하고 외부 데이터베이스 또는 API와 통합할 수 있으므로 특히 JSON 웹 토큰과 관련된 시나리오에서 매우 강력합니다.
// src/app/api/auth/[...nextauth]/route.ts
import { addUser } from "@/service/user";
import NextAuth, { AuthOptions, NextAuthOptions } from "next-auth";
import GoogleProvider from 'next-auth/providers/google';
export const authOptions: AuthOptions = {
providers: [
GoogleProvider({ //👈 구글 개정을 미리 준비해서 이와 같이 작성합니다.
clientId: process.env.GOOGLE_OAUTH_ID!,
clientSecret: process.env.GOOGLE_OAUTH_SECRET!,
}),
],
pages: { //👈 사용자 URL을 지정
signIn: '/auth/signin',
},
callbacks: { //👈 작업이 수행될 때 발생하는 작업을 제어하는 데 사용
async signIn({ user: {id, name, image, email}}) {
if(!email){
return false;
}
addUser({ //👈 데이터 베이스와 통신을 하는 API를 여기서 호출하는것!(로그인을 성공하면 사용자 정보를 데이터 베이스에 추가하는것)
id,
name: name || '',
image,
email,
username: email.split('@')[0]
});
return true
},
async session({ session }) { //기존의 session에서 username 프로퍼티를 추가
const user = session?.user
if(user){
session.user = {
...user,
username: user.email?.split('@')[0] || ''
}
}
return session
}
}
};
const handler : NextAuthOptions = NextAuth(authOptions)
export { handler as GET, handler as POST }
👉 Middleware를 사용해서 로그인 구현하기
(참고로 Middleware라는것은 서버와 통신하는 여러 방법중 하나의 패턴을 뜻합니다.)
//middleware.ts
import type { NextRequest, NextFetchEvent } from "next/server";
import { NextResponse } from "next/server";
import { decode, getToken } from "next-auth/jwt";
export async function middleware(req: NextRequest, _: NextFetchEvent) {
const url = req.nextUrl.clone();
const redirectTo = (dest: string) => NextResponse.redirect(new URL(dest, url));
const token = await getToken({
req,
secret: process.env.NEXTAUTH_SECRET,
raw: true
});
try {
const decoded = await decode({ token, secret: process.env.NEXTAUTH_SECRET! });
if (url.pathname.startsWith('/auth/signin')) {
if (decoded) {
return redirectTo('/');
}
} else {
if (!decoded) {
return redirectTo('/auth/signin');
}
}
} catch (e) {
console.log("decoded:", e)
}
};
// See "Matching Paths" below to learn more
export const config = {
matcher: "/((?!api|static|.*\\..*|_next).*)",
};
👉Server Component에서 session를 받아올수 있습니다.
middleware에서 token을 가지고 와서 decoded까지해주면 서버컴포넌트에서 session를 받아올수 있습니다.
import FollowingBar from "@/components/FollowingBar/FollowingBar";
import PostList from "@/components/PostList/PostList";
import SideBar from "@/components/SideBar/SideBar";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import { getServerSession } from "next-auth";
import { HomePageStyled } from '@/styles/pageStyled/HomePageStyled';
export default async function HomePage() {
const session = await getServerSession(authOptions); //👈서버컴포넌트에서 로그인한 session정보를 가져오는것 성공!
console.log('session:', session)
const user = session?.user!;
return (
<HomePageStyled className="homePage">
<div>
<FollowingBar />
<PostList />
</div>
<SideBar user={user}/>
</HomePageStyled>
);
}
결과 확인)👇
👉Client Component에서 session를 받아올수 있습니다.
Client Component에서 session를 받아올수 있습니다. 그렇게 하기 위해서는 SessionProvider컴포넌트를
layout.tsx에서 전역적으로 감싸줘야 합니다.
//AuthProvider.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
import { ReactNode } from 'react';
export default function AuthProvider({ children }: { children: ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
// layout.tsx
import { ReactNode } from "react";
import Header from "@/components/Header/Header";
import AuthProvider from "@/provider/AuthProvider";
export const metadata: Metadata = {
title: "Instantgram",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: ReactNode;
}>) {
return (
<html lang="ko" className={pretendard.className}>
<body>
<AuthProvider> //👈 AuthProvider컴포넌트를 감싸줍니다.
<CustomThemeProvider>
<Header />
<main>
<SWRConfigProvider>
{children}
</SWRConfigProvider>
</main>
</CustomThemeProvider>
</AuthProvider>
</body>
</html>
);
}
// Header.tsx
import { signIn, signOut, useSession } from "next-auth/react";
export default function Header() {
const pathName = usePathname();
const { data: session } = useSession(); //👈useSession으로 세션을 받아올수 있습니다.
const user = session?.user;
return (
<HeaderStyled className={clsx('Header')}>
<Link href="/">
<h1>Instantgram</h1>
</Link>
<nav>
<ul className={clsx('item-inner')}>
{user && (
<li>
<Link href={`/user/${user.username}`}>
<Avatar image={user.image}/>
</Link>
</li>
)}
{session ? (
<ColorButton text='Sign out' onClick={() => signOut()} />
) : (
<ColorButton text='Sign in' onClick={() => signIn()} />
)}
</ul>
</nav>
</HeaderStyled>
);
};