전체 소스코드
https://github.com/kimfame/next-login
사전 작업
mongodb DB 생성 (https://www.mongodb.com/)
패키지 설치
$ yarn add next-auth mongoose bcrypt
- next-auth : Next.js 로그인 인증 관련 라이브러리
- mongoose : mongodb ODM (RDBMS 상의 ORM 같은 거)
- bcrypt : 암호화 라이브버리 (비밀번호 단방향 암호화 때 사용)
.env 설정
MONGODB_URI
NEXTAUTH_URL
NEXTAUTH_SECRET
User 모델 (mongoose)
/src/models/User.js
import { model, models, Schema } from 'mongoose'
const UserSchema = new Schema(
{
name: { type: String },
email: { type: String, required: true, unique: true },
password: { type: String },
image: { type: String },
},
{ timestamps: true },
)
const User = models?.User || model('User', UserSchema)
export default User
- mongoose 이용한 User 모델
회원가입 폼
/src/app/register/page.jsx
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export default function RegisterPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [errorMsg, setErrorMsg] = useState('')
const router = useRouter()
async function handleFormSubmit(ev) {
ev.preventDefault()
const res = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify({ email, password }),
headers: { 'Content-Type': 'application/json' },
})
if (res.ok) {
router.refresh()
router.push('/login')
} else {
const { message } = await res.json()
setErrorMsg(message)
}
}
return (
<section>
<h1>Register</h1>
<form onSubmit={handleFormSubmit}>
<input
type="email"
placeholder="email"
value={email}
onChange={(ev) => setEmail(ev.target.value)}
/>
<input
type="password"
placeholder="password"
value={password}
onChange={(ev) => setPassword(ev.target.value)}
/>
<button type="submit">Register</button>
</form>
<div>{errorMsg}</div>
</section>
)
}
- 간단히 이메일과 비밀번호를 입력하면 회원가입이 완료되는 폼
- 회원가입 완료 시, 로그인 페이지로 이동
회원가입 API
/src/api/register/route.js
import bcrypt from 'bcrypt'
import mongoose from 'mongoose'
import User from '@/models/User'
import { NextResponse } from 'next/server'
export async function POST(req) {
const body = await req.json()
mongoose.connect(process.env.MONGODB_URI)
const { password } = body
if (!password?.length || password.length < 5) {
return NextResponse.json(
{ message: 'password must be at least 5 characters' },
{ status: 400 },
)
}
const notHashedPassword = password
const salt = bcrypt.genSaltSync(10)
body.password = bcrypt.hashSync(notHashedPassword, salt)
const newUser = await User.create(body)
return NextResponse.json(newUser)
}
- 5글자 미만인 경우, 등록 거절
- 비밀번호 단방향 해시 기능 추가
로그인 폼
/src/app/login/page.jsx
'use client'
import { signIn } from 'next-auth/react'
import { useState } from 'react'
export default function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
async function handleFormSubmit(ev) {
ev.preventDefault()
await signIn('credentials', { email, password, callbackUrl: '/' })
}
return (
<section>
<h1>Login</h1>
<form onSubmit={handleFormSubmit}>
<input
type="email"
name="email"
placeholder="email"
value={email}
onChange={(ev) => setEmail(ev.target.value)}
/>
<input
type="password"
name="password"
placeholder="password"
value={password}
onChange={(ev) => setPassword(ev.target.value)}
/>
<button type="submit">Login</button>
</form>
</section>
)
}
- 이메일과 비밀번호 입력 시, 로그인 동작 (next-auth의 signIn 사용)
로그인 API (AuthOptions 설정)
/src/app/api/auth/[...nextauth]/route.js
import bcrypt from 'bcrypt'
import mongoose from 'mongoose'
import CredentialsProvider from 'next-auth/providers/credentials'
import NextAuth from 'next-auth'
import User from '@/models/User'
export const authOptions = {
secret: process.env.SECRET,
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: {
label: 'Email',
type: 'email',
placeholder: 'test@example.com',
},
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const email = credentials?.email
const password = credentials?.password
mongoose.connect(process.env.MONGODB_URI)
const user = await User.findOne({ email })
const passwordOk = user && bcrypt.compareSync(password, user.password)
if (passwordOk) {
return user
}
return null
},
}),
],
session: {
strategy: 'jwt',
},
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
- next-auth의 Credentials Provider를 이용한 로그인 기능
- next-auth Provider 여러 개 존재 (Google, GitHub, Kakao, Naver 등등)
- providers 안에 여러 Provider들을 넣어 다양한 업체를 통한 로그인 가능
- Credentials Provider의 경우, authorize 직접 작성 필요
- credentials 필드 안에 있는 email, password 폼 정보는 next-auth에서 기본으로 제공해주는 로그인폼을 만들어주기 위한 필드임 (http://localhost:3000/api/auth/signin 안에서 접근 가능)
- session.strategy 필드
'database', 'jwt' 두 종류가 존재한다.
기본적으로 adapter를 추가하면 자동적으로 database로 구성되고, 'jwt'로 명시할 경우 adapter가 있어도 강제로 jwt로 동작한다.
지금 듣고 있는 유튜브 강의 상에서는 MongoDBAdapter를 추가해서 DB Session을 아무 문제없이 동작하는데 내가 시도했을 때는 로그인 인증 및 유지가 되지 않았다. 댓글을 보아하니 다른 외쿡인 친구들도 안되는듯...
Stackoverflow에서도 그렇고 일단은 연습용이니 'jwt'로 설정하면 된다는 글이 있어서 해봤더니 일단 동작은 한다. 근데 왜 DB Session은 안되는지 잘 모르겠다. 공식문서 예제 그대로 가져와서 사용했는데도 이게 안되네... 나중에 진도 다 빼고 다시 봐야겠다.
Layout
/src/app/layout.jsx
import { Inter } from 'next/font/google'
import './globals.css'
import Header from '@/components/layout/Header'
import SessionProvider from '@/components/SessionProvider'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<SessionProvider>
<Header />
{children}
</SessionProvider>
</body>
</html>
)
}
- Header 안에서 로그인 되었는지 확인하는 영역 포함
- SessionProvider
Client component인 useSesssion을 쓰려면 SessionProvider를 상위 component에서 감싸야한다.
Next-auth에서 Session을 가지고 오는 방법이 총 3가지가 있는 것 같다.
useSession - Client component
getSession - Client component
getServerSession - Server component
getSession은 테스트를 안해봤으니 useSession을 사용하려면 SessionProvider로 감싸야 사용할 수 있다.
getServerSession을 사용할 경우, SessionProvider 필요없음.
SessionProvider
/src/components/SessionProvider.js
'use client'
import { SessionProvider } from 'next-auth/react'
export default SessionProvider
- next-auth의 SessionProvider wrapper
Header
/src/components/layout/Header.jsx
'use client'
import { SessionProvider } from 'next-auth/react'
export default SessionProvider
- useSession을 통해 로그인 정상적으로 되었는지 확인
- status 상태 값으로 로그인 상태 구분
- status는 총 3가지 상태를 가짐
"authenticated" | "loading" | "unauthenticated"
[출처]
'React > Next.js' 카테고리의 다른 글
[Next.js] AWS S3 ACL Private 특정 유저만 접근 허용 (0) | 2024.03.28 |
---|---|
[Next.js] AWS S3 이미지 업로드 예제 (2) - presigned URL (0) | 2024.03.25 |
[Next.js] AWS S3 이미지 업로드 예제 (1) (0) | 2024.03.22 |
[Next.js] 프로젝트 생성 / 개발 서버 실행 (0) | 2024.03.11 |
[Next.js] 개발 공부 (0) | 2024.03.11 |