Skip to Content
Suffering builds character
아카이브9.Next.js특징인증3. Next.js의 인증 프로세스

3. Next.js의 인증 프로세스

앞서 설명한 인증 프로세스를 Next.js에서 구현하기 위해서는 다음의 구조와 흐름으로 진행할 수 있음

💡
Tip

Next.js에서 인증 기능 구현에 필요한 API

Next.js에서 인증 기능을 구현하기 위해서는, <form>과 React의 Server Action, useActionState 훅을 활용할 수 있음

이를 통해 사용자 자격증명(ID, PW 등)을 기록하고, 입력 필드 값을 검증할 수 있으며, 인증 프로바이더 API 및 DB로 호출 작업을 수행할 수 있음

또한 Server Action은 항상 서버 측에서 실행되기 때문에 인증 로직 처리에 있어서 안전한 환경을 제공함
→ 코드가 클라이언트 측에서 실행되지 않기 때문에 브라우저에 key값이나 인증 처리 로직이 노출되지 않음

1. (Next.js)사용자 자격증명 정보 입력받기

Note

자격증명(Credential)
사용자의 아이디, 비밀번호와 같이 인증에 요구되는 정보

사용자가 자신의 인증 정보를 가지고 있는 서버에게 자신이 해당 서비스를 이용할 수 있는 자격을 증명하기 위한 정보

로그인 페이지에서 입력 폼을 통해 사용자가 입력한 자격증명을 기록하기 위해서는 Server Action으로 동작하는 <form> 태그를 활용

만약 사용자가 아이디/비밀번호를 입력 후 로그인(submit) 버튼을 클릭하였을 경우, <form> 태그에 작성된 action 어트리뷰트의 함수가 호출됨

action 어트리뷰트에 들어갈 함수는 Server Action으로 동작하는 함수를 활용함

username, email, password를 입력받는 회원가입 폼 예시

components/signup-form.jsx
// Server Action으로 동작할 회원가입 처리 함수, 별도의 파일에서 import import { signup } from '@/app/actions/auth' export function SignupForm() { return ( // form 태그의 action 어트리뷰트에 콜백 함수로 지정 <form action={signup}> <div> <label htmlFor="name">Name</label> <input id="name" name="name" placeholder="Name" /> </div> <div> <label htmlFor="email">Email</label> <input id="email" name="email" type="email" placeholder="Email" /> </div> <div> <label htmlFor="password">Password</label> <input id="password" name="password" type="password" /> </div> <button type="submit">Sign Up</button> </form> ) }

서버 액션 코드 위치 및 구조 예시

app/actions/auth.js
export async function signup(formData) { // 회원가입 처리 로직.. }

2. (Next.js) 사용자가 폼에 입력한 값 검증하기

Server Action을 활용하여 서버에서 입력폼 필드를 검증할 수 있으며,

외부 라이브러리인 zod를 통해 간단하게 입력 폼 필드를 검증할 수 있음

app/lib/definitions.js
import { z } from 'zod' // 입력값 검증용 스키마 규칙이 작성된 코드 export const SignupFormSchema = z.object({ name: z // 이름 필드 규칙 .string() .min(2, { message: 'Name must be at least 2 characters long.' }) .trim(), email: z.string().email({ message: 'Please enter a valid email.' }).trim(), password: z // 비밀번호 필드 규칙 .string() .min(8, { message: 'Be at least 8 characters long' }) .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' }) .regex(/[0-9]/, { message: 'Contain at least one number.' }) .regex(/[^a-zA-Z0-9]/, { message: 'Contain at least one special character.', }) .trim(), })

입력 폼 검증 결과, 만약 사용자가 입력한 값이 유효하지 않을 경우 return 처리 수행
→ 실제 인증을 수행하는 백엔드 서버로 불필요한 네트워크 요청을 방지

app/actions/auth.js
import { SignupFormSchema } from '@/app/lib/definitions' export async function signup(state, formData) { // 1. zod 스키마를 기반으로 사용자가 입력한 필드값 검증 const validatedFields = SignupFormSchema.safeParse({ name: formData.get('name'), email: formData.get('email'), password: formData.get('password'), }) // 2. 사용자의 입력값이 유효하지 않을 경우, 초기에 return if (!validatedFields.success) { return { errors: validatedFields.error.flatten().fieldErrors, } } // 3. 사용자 계정을 생성하기 위해 인증 백엔드 서버 or DB 서버 호출 로직 // ... }

다시 <SignupForm />에서는 useActionState 훅을 활용하여 사용자의 폼 제출(Submit) 과정에서 발생한 유효성 에러를 페이지에 출력, 적절한 에러 메시지를 표시할 수 있음

components/signup-form.js
'use client' import { signup } from '@/app/actions/auth' import { useActionState } from 'react' export default function SignupForm() { const [state, action, pending] = useActionState(signup, undefined) return ( <form action={action}> <div> <label htmlFor="name">Name</label> <input id="name" name="name" placeholder="Name" /> </div> {state?.errors?.name && <p>{state.errors.name}</p>} <div> <label htmlFor="email">Email</label> <input id="email" name="email" placeholder="Email" /> </div> {state?.errors?.email && <p>{state.errors.email}</p>} <div> <label htmlFor="password">Password</label> <input id="password" name="password" type="password" /> </div> {state?.errors?.password && ( <div> <p>Password must:</p> <ul> {state.errors.password.map((error) => ( <li key={error}>- {error}</li> ))} </ul> </div> )} <button disabled={pending} type="submit"> Sign Up </button> </form> ) }

3. (백엔드) 사용자 회원가입 처리, 기존 계정과의 중복 여부 확인하기

폼에 입력된 값에 대한 검증이 유효할 경우, 새로운 사용자 계정을 생성하는 처리가 수행됨

이후 사용자 계정 생성이 완료되었을 경우, 현재 인증에 대한 상태 정보를 가지고 있는 세션(Session)을 생성하여 사용자의 인증 정보를 관리할 수 있음

Note

기존에 생성된 계정 여부 확인
해당 사용자로 가입된 계정이 이미 존재하는지에 대한 확인은 Next.js가 아닌 인증 서버(주로 백엔드)에서 데이터베이스를 통해 확인 후 처리

백엔드에서 수행해야할 로직을 Next.js로 임시 구현한 코드 예시

app/actions/auth.js
export async function signup(state, formData) { // 1.사용자가 입력한 필드값 검증 // ... // 2. DB에 INSERT할 처리 로직 준비 const { name, email, password } = validatedFields.data // e.g. DB에 저장하기 전에 평문으로 작성된 비밀번호를 해시 형태로 암호화 const hashedPassword = await bcrypt.hash(password, 10) // 3. DB INSERT 처리 로직 수행 const data = await db .insert(users) .values({ name, email, password: hashedPassword, }) .returning({ id: users.id }) const user = data[0] if (!user) { return { message: 'An error occurred while creating your account.', } } // TODO: // 4. 현재 사용자의 세션 정보 생성 // 5. 사용자를 적절한 페이지로 이동시키기(redirect) }

현재까지 수행된 흐름

현재까지 수행된 흐름을 이미지로 보면 다음과 같음

Last updated on