728x90
Next.auth를 통해 인증인가 설정하는 이유
→ 유저의 Role에 따라 페이지 접근을 제어하기 때문입니다.
회원 정보를 담아야 하기 때문에 샘플 DB와 ORM을 설정할 필요가 있었습니다. 따라서, Prisma ORM 과 Postgre를 통해 입력값을 담을 준비를 마쳤습니다.
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
enum UserType {
USER
ADMIN
}
model User {
id String @id @default(cuid())
name String?
hashedPassword String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
UserType UserType @default(USER)
}
먼저 Prisma ORM의 스키마를 설정해서 디비에 저장할 테이블을 생성할 준비를 합니다.
version: "3"
services:
db:
image: postgres:latest
restart: always
ports:
- "5432:5432"
environment:
POSTGRES_USER: "root"
POSTGRES_PASSWORD: "1234"
volumes:
- ./data:/var/lib/postgresql/data
그리고 DB는 따로 설치를 하지 않고 도커컴포즈 파일을 작성해서 띄워 사용하였습니다.
DATABASE_URL="postgresql://root:1234@localhost:5432/next2db"
NEXTAUTH_SECRET=nextAuthSecret
NEXTAUTH_URL=http://localhost:3000
JWT_SECRET=jwt-secret
마지막으로 .env파일을 작성해 Next.js 프로젝트를 DB와 연결하였습니다.
본격적으로 NextAuth를 통해 회원 가입 / 로그인 기능 개발
const prisma = new PrismaClient()
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
CredentialsProvider({
// The name to display on the sign in form (e.g. "Sign in with...")
name: "Credentials",
// `credentials` is used to generate a form on the sign in page.
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const user = { id: "1", name: "J Smith", email: "jsmith@example.com", role: "USER" }
if (user) {
// Any object returned will be saved in `user` property of the JWT
return user
} else {
// If you return null then an error will be displayed advising the user to check their details.
return null
// You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
}
}
})
],
https://next-auth.js.org/providers/credentials
NextAuth 공식 문서에는 NextAuth를 연결하기만 하면 기본적으로 회원가입과 로그인 기능이 제공되고 있습니다.
공식문서에서 제공해주는 구글 로그인과 자체 로그인 코드를 토대로 개발을 진행했습니다.
// 세션 관리는 jwt로 지정
session: {
strategy: 'jwt',
},
// jwt 설정 -> 시크릿은 env파일에 설정해둠 + 유효기간 30일
jwt: {
secret: process.env.JWT_SECRET,
maxAge: 30 * 24 * 60 * 60 // 30 days
},
//토큰 내에 사용자 정보가 포함되어, 이후 세션 생성 시 이용
callbacks: {
async jwt({token, user}) {
return {...token, ...user}
},
async session({session, token}) {
session.user = token
return session
}
}
}
제공되는 코드에 추가로 설정할 부분은 로그인 이후 세션을 어떻게 관리할 지 정의하는 것이었습니다.
JWT 토큰을 통해 세션 정보를 유지하기 위해 콜백함수로 관리했습니다.
JWT 콜백 : 사용자가 로그인할 때 또는 토큰이 갱신될 때 실행
→ 사용자가 로그인하는 순간, user 객체에 포함된 정보를 token 객체와 병합하여 반환
session 콜백 : 서버 측에서 생성된 세션 정보를 클라이언트 측에 맞게 조정하거나 추가 데이터를 세션에 포함
→ session 객체 내에 user 속성을 추가하고, 이를 jwt 콜백에서 반환된 token 객체로 설정
import { getToken } from 'next-auth/jwt';
import { NextRequest, NextResponse } from 'next/server';
export {default} from 'next-auth/middleware'
export async function middleware(req: NextRequest) {
const session = await getToken({req, secret: process.env.JWT_SECRET});
const pathName = req.nextUrl.pathname;
// 관리자 페이지 접근 권한이 없는 경우 메인 페이지로 리다이렉트
if (pathName.startsWith('/admin') && session?.role !== 'ADMIN') {
return NextResponse.redirect(new URL("/", req.url));
}
// 로그인되지 않은 상태에서 유저 페이지 접근시 로그인 페이지로 리다이렉트
if (pathName.startsWith('/user') && !session) {
return NextResponse.redirect(new URL("/auth/login", req.url));
}
// 로그인 상태에서 로그인 페이지, 회원가입 페이지 접근시 메인 페이지로 리다이렉트
if (pathName.startsWith('/auth') && session) {
return NextResponse.redirect(new URL("/", req.url));
}
return NextResponse.next();
}
미들웨어 파일을 두고 세션의 속성 값에 따라 접근 가능한 경로를 제어했습니다
/admin:path*와 같은 와일드카드 패턴을 사용해, 하위 모든 경로를 제어할 수 있었습니다.