가수면

NextAuth.js 본문

React/라이브러리

NextAuth.js

니비앙 2023. 6. 11. 20:30

npm install next-auth

배포 시 주의할 점

환경 변수인 NEXTAUTH_URL 추가할 필요 없음

사용법

next 이전 버전

/pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth"

export default NextAuth({
  ...
})

next 최신 버전

/app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"

const handler = NextAuth({
  ...
})

export { handler as GET, handler as POST }

공식문서에 따르면 Next 13.2 이상의 버전에 대응하기 위해 REST와 유사하게 요청을 처리하는 방법을 추가했다고 한다.

때문에 같은 handler 다른 형태로 내보내고 있는 이상한 형태를 사용하지만, NextAuth.js가 제대로 작동하려면 GET 및 POST 처리기가 필요하므로 이 두 가지를 내보내야 한다고 한다.

이슈

현재 최신 버전 방식으로 처리하게 되면 session으로 받아오는 것이 가공된 형태가 아닌 디폴트 형태로 넘어온다.

분명 username을 추가했음에도 name, email, image만 넘어옴

 

반면 이전 버전 방식으로 session을 불러오면 제대로 불러오는 것을 확인할 수 있다.

적용 예시

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID || "",
      clientSecret: process.env.GOOGLE_SECRET || "",
    }),
    // ...add more providers here
  ],
});

export { handler as GET, handler as POST };

구글 OAuth 예시

https://console.cloud.google.com/getting-started에서 프로젝트 생성

대시보드 선택

api 개요로 이동

배포 후 배포된 도메인 추가

사용자 인증 정보 만들기 -> 'OAuth 클라이언트 ID' 선택

이후 만들어진 '클라이언트 ID' 와 '클라이언트 보안 비밀번호' 환경 변수로 사용

 

승인된 자바스크립트 원본, 승인된 리디렉션 URI 설정

 

env파일에 다음 항목 추가

NEXTAUTH_URL = http://localhost:3000/  // 배포 url
NEXTAUTH_SECRET = 토큰값

토큰값은 아래와 같은 암호 생성기를 사용해서 얻은 값을 입력

https://delinea.com/resources/password-generator-it-tool

 

Strong Password Generator | Create Random Passwords

Use Delinea's secure password generator to quickly generate strong passwords online. Customize your preferences. Try it here.

delinea.com

 

로그인 페이지 설정

  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID || "",
      clientSecret: process.env.GOOGLE_SECRET || "",
    }),
  ],
  pages: {
    signIn: "/auth/signin",
  },
});

pages옵션에서 설정한 경로에 페이지 생성

UI의 경우 onClick 이벤트가 발생하므로 클라이언트 컴포넌트로 따로 분리해야 한다!

// src\app\auth\signin\page.tsx

import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { getProviders } from "next-auth/react";
import { GET } from "../../api/auth/[...nextauth]/route";
import Signin from "@/components/Signin";

export default async function signPage() {
  const session = await getServerSession(GET);	// 세션 정보를 가져옴 (로그인 유저의 데이터)
  if (session) {
    redirect("/");
  }
  const providers = (await getProviders()) ?? {};  // providers 정보를 가져옴 (구글, 깃허브 등 로그인 타입이 담긴 정보)

  return <Signin providers={providers} />;
}

세션 정보

{
  user: {
    name: '고야',
    email: 'yhhnnmm@gmail.com',
    image: 'https://lh3.googleusercontent.com/a/AAcHTtfCsjOcU9VEQywYwe6-Ntlplhsy3WmBKDgyDJ-faA=s96-c'
  }
}

providers 정보

{
  google: {
    id: 'google',
    name: 'Google',
    type: 'oauth',
    signinUrl: 'http://localhost:3000/api/auth/signin/google',
    callbackUrl: 'http://localhost:3000/api/auth/callback/google'
  }
}
"use client";

import { ClientSafeProvider, signIn } from "next-auth/react";
import React from "react";
import { ColorButton } from "./ui/button";

type SigninProps = {
  providers: Record<string, ClientSafeProvider>;
};

export default function Signin({ providers }: SigninProps) {
  return (
    <>
      {Object.values(providers).map(({ name, id }) => (
        <ColorButton key={id} text={`Sign In with ${name}`} onClick={() => signIn(id)} />
      ))}
    </>
  );
}

Callbacks

기본 형태

  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {	// 로그인할 때
      return true
    },
    async redirect({ url, baseUrl }) {
      return baseUrl
    },
    async session({ session, user, token }) {	// 세션이 만들어질 때
      return session
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      return token
    }
...
}

session

 

  callbacks: {
    async session({ session }) {
      const user = session.user;
      if (user) {
        session.user = {
          ...user,
          username: user.email?.split("@")[0] ?? "",
        };
      }
      return session;
    },
  },
  pages: {
    signIn: "/auth/signin",
  },

useSession을 통해 로그인 한 유저에 대한 데이터를 사용

const { data: session } = useSession();

signIn

옵션 안에 로그인 할 때 시행될 로직 작성

    async signIn({ user: { id, name, image, email } }) {
      if (!email) {
        return false;
      }
      addUser({		// 데이터 베이스에 유저 데이터 추가하는 함수
        id,
        name: name || "",
        image,
        email,
        username: email.split("@")[0],
      });
      return true;
    },

미들웨어

기본형태

export { default } from "next-auth/middleware"

export const config = {
  matcher: ['/new', '/']
};

api 요청까지 적용하기

import { getToken } from 'next-auth/jwt';
import { NextRequest, NextResponse } from 'next/server';

export async function middleware(req: NextRequest) {
  const token = await getToken({ req });

  if (!token) {
    if (req.nextUrl.pathname.startsWith('/api')) {
      return new NextResponse('Authentication Error', { status: 401 });
    }

    const { pathname, search, origin, basePath } = req.nextUrl;
    const signInUrl = new URL(`${basePath}/auth/signin`, origin);
    signInUrl.searchParams.append(
      'callbackUrl',
      `${basePath}${pathname}${search}`
    );
    return NextResponse.redirect(signInUrl);
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    '/new',
    '/',
    '/api/bookmarks',
    '/api/comments',
    '/api/likes',
    '/api/follow',
    '/api/me',
    '/api/posts/:path*',
  ],
};

 

'React > 라이브러리' 카테고리의 다른 글

SWR  (0) 2023.06.20
Mongo DB  (0) 2023.06.15
[Sanity] 기본 정리  (0) 2023.06.11
nodemailer  (0) 2023.06.11
react-markdown  (0) 2023.06.10
Comments