ETC -

Middleware & NextAuth.js(로그인 구현)(next - v14.1.0)

  • -

안녕하세요 트리플랩입니다.

👉 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>
 );
};

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.