가수면
NextAuth.js 본문
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
로그인 페이지 설정
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 |