일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 변수
- JS
- git
- 최적화
- github
- 햇소
- next.js
- developerlife
- Python
- es6
- CSS
- html5
- JavaScript
- array
- hatso
- object
- AI
- This
- 선택자
- react
- 함수
- ES5+
- DOM
- hooks
- dev
- learn next.js
- gitCLI
- API
- 우아한테코톡
- ES6+
- Today
- Total
codinghatso
Learn Next.js 제 15장 : Adding Authentication 본문
-ai를 사용해 개념정리 했음을 공지합니다.-
이번 장에서 배울 내용
* 인증이란 무엇인가요. |
* NextAuth.js를 사용하여 앱에 인증을 추가하는 방법. |
* 미들웨어를 사용하여 사용자를 리디렉션하고 경로를 보호하는 방법. |
* React's 사용 방법 useActionState 보류 중인 상태와 폼 오류를 처리합니다. |
인증이란 무엇인가? (Authentication)
인증은 오늘날 많은 웹 애플리케이션의 핵심 부분이고, 시스템이 사용자가 누구인지 확인하는 방식입니다.
보안 웹사이트는 종종 사용자의 신원을 확인하기 위해 여러 가지 방법을 사용합니다. 예를 들어, 사용자 이름과 비밀번호를 입력한 후 사이트에서 기기로 인증 코드를 보내거나 Google Authenticator와 같은 외부 앱을 사용할 수 있습니다. 이 2단계 인증(2FA)은 보안을 강화하는 데 도움이 됩니다. 누군가 당신의 비밀번호를 알게 되더라도, 당신의 고유한 토큰 없이는 계정에 접근할 수 없습니다.
인증 vs 권한 부여
웹 개발에서 인증과 권한 부여는 서로 다른 역할을 합니다:
- 인증(Authentication)은 사용자가 자신이 누구인지 확인하는 것입니다. 사용자 이름과 비밀번호 같은 것으로 신원을 증명하고 있습니다.
- 권한 부여(Authorization)는 다음 단계입니다. 사용자의 신원이 확인되면, 권한 부여가 그들이 사용할 수 있는 애플리케이션의 일부를 결정합니다.
따라서 인증은 사용자가 누구인지 확인하고, 인증은 애플리케이션에서 무엇을 하거나 접근할 수 있는지를 결정합니다.
setting up NextAuth.js
next-auth 설치(install)
pnpm i next-auth@beta
key 생성(create)
# macOS
openssl rand -base64 32
# Windows can use https://generate-secret.vercel.app/32
.env 파일에 key 추가(add)
///.env
AUTH_SECRET=your-secret-key
사용자 설정 파일
//auth.config.ts
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
pages: {
signIn: '/login',
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // Redirect unauthenticated users to login page
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
},
},
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
📌 authConfig 코드 해설 (NextAuth 설정)
이 코드는 NextAuth.js의 인증 설정(authConfig)을 정의하는 역할을 합니다.
사용자가 로그인 상태인지 확인하고, **대시보드 페이지(/dashboard 경로)**에 대한 접근을 제어합니다.
✅ 코드 분석
import type { NextAuthConfig } from 'next-auth';
- NextAuthConfig 타입을 가져와서 authConfig 객체의 타입을 검사할 수 있도록 함.
🔹 1. signIn 페이지 설정
pages: {
signIn: '/login',
},
- 사용자가 로그인되지 않은 상태에서 보호된 페이지에 접근하면,
/login 페이지로 리다이렉트 되도록 설정.
🔹 2. callbacks.authorized → 인증 상태 확인 및 리다이렉트
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
- auth?.user가 존재하는지 확인하여 로그인 상태(isLoggedIn)를 판별.
- 현재 요청된 URL이 /dashboard로 시작하는지 확인하여,
대시보드 페이지 접근 여부를 체크.
🔹 3. 보호된 경로(/dashboard) 접근 제어
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // 로그인 안 된 사용자는 접근 차단
}
- 사용자가 /dashboard에 접근하려 하면:
- 로그인 상태(isLoggedIn = true)면 접근 허용 (return true)
- 로그인하지 않은 경우 접근 차단 (return false) → /login 페이지로 이동됨
🔹 4. 로그인된 사용자가 /dashboard 외의 경로에 있을 경우 대시보드로 이동
else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
- 로그인한 사용자가 /dashboard가 아닌 페이지에 있을 경우,
→ 자동으로 /dashboard로 리디렉트 (예: 로그인 후 자동 이동).
🔹 5. 기본적으로 모든 페이지 접근 허용
return true;
- 로그인된 상태가 아닐 때 기본적으로 모든 페이지 접근 허용 (예: 로그인 페이지, 공개 페이지 등).
🔹 6. providers 설정
providers: [], // Add providers with an empty array for now
- 현재 로그인 제공자(Providers)가 없음.
- 나중에 Google, GitHub, Credentials(이메일/비밀번호) 로그인 등을 추가할 수 있음.
✅ 결론
✔ 로그인하지 않은 사용자는 /dashboard 접근 불가 → /login으로 이동
✔ 로그인한 사용자는 /dashboard 외의 경로에 있을 경우 자동 이동
✔ 기본적으로 모든 페이지 접근 가능하지만, dashboard는 로그인 필수
✔ 현재 로그인 제공자 없음 → 추후 Google, GitHub 로그인 등 추가 가능
🚀 즉, 이 코드는 "NextAuth를 활용해 인증 상태를 관리하고, 대시보드 페이지를 보호하는 역할"을 함! 😊
미들웨어 설정
authConfig 개체를 미들웨어(middleware) 파일로 가져와야 합니다.
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;
export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
이 작업을 위해 미들웨어를 사용하는 장점은 미들웨어가 인증을 검증할 때까지 보호된 경로가 렌더링을 시작하지 않아 애플리케이션의 보안과 성능이 모두 향상된다는 점입니다.
📌 NextAuth 미들웨어 코드 해설
이 코드는 NextAuth를 사용하여 Next.js 애플리케이션의 전역 미들웨어로 인증을 적용하는 역할을 합니다.
✅ 코드 분석
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
- next-auth에서 NextAuth를 가져와 인증 미들웨어를 설정.
- authConfig는 인증 설정 객체로, NextAuth의 구성을 포함.
🔹 1. NextAuth 미들웨어 적용
export default NextAuth(authConfig).auth;
- NextAuth(authConfig).auth는 NextAuth의 미들웨어 기능을 활성화.
- 모든 요청이 이 미들웨어를 거쳐 사용자가 인증되었는지 확인.
- 로그인하지 않은 사용자는 보호된 페이지에 접근할 수 없도록 설정.
🔹 2. config.matcher를 사용한 미들웨어 적용 범위 설정
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
- matcher는 미들웨어를 적용할 경로를 정의하는 역할.
- **정규식(/(?! ...) 사용)**을 사용하여 특정 경로를 미들웨어 적용 대상에서 제외.
📌 적용 제외되는 경로
제외 대상 설명/api | API 라우트는 미들웨어를 거치지 않음. |
/_next/static | 정적 파일(빌드된 JS, CSS 등)에는 인증 적용 안 함. |
/_next/image | Next.js 이미지 최적화 API 경로 제외. |
*.png | .png 이미지 파일 요청에는 인증 적용 안 함. |
✅ 즉, 이 설정은 Next.js에서 "정적 리소스나 API 요청을 제외하고, 나머지 모든 요청에 대해 인증 미들웨어를 적용"하는 역할을 함.
✅ 결론
✔ NextAuth를 미들웨어로 설정하여 모든 요청에서 인증 확인
✔ 정적 파일, API, 이미지 요청을 제외하고 나머지 모든 경로에 인증 적용
✔ 보호된 페이지는 로그인한 사용자만 접근 가능
🚀 즉, 이 코드는 Next.js 애플리케이션에서 "로그인이 필요한 페이지를 보호하는 역할"을 함! 😊
비밀번호 해싱(Password hashing)
무작위 문자열로 변환해 데이터 노출되더라도 안전을 보장받습니다.
DB를 seed할 때 bcrypt 패키지를 사용했는데. 이는 bcrypt가 Next.js의 middleware에서 사용할 수 없는 Node.js API에 의존하고 있기 때문입니다.
📌 NextAuth.js 인증 설정 코드 해설
이 코드는 NextAuth.js를 사용하여 이메일/비밀번호 기반 로그인 기능을 구현하는 코드예요.
특히 PostgreSQL에서 사용자 정보를 가져와서 인증을 처리하는 방식으로 되어 있습니다.
✅ 코드 분석
1️⃣ 필요한 모듈 및 설정 가져오기
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcryptjs';
import postgres from 'postgres';
- NextAuth → NextAuth.js 라이브러리를 가져와 인증 기능 설정.
- authConfig → 기존 인증 설정 (로그인 페이지 등) 가져오기.
- Credentials → 이메일 & 비밀번호 기반 인증을 처리하기 위한 credentials 프로바이더.
- zod → 입력값을 검증하기 위한 라이브러리.
- bcryptjs → 데이터베이스의 해싱된 비밀번호를 비교하는 데 사용.
- postgres → PostgreSQL과 연결하여 사용자 정보를 가져오는 데 사용.
2️⃣ PostgreSQL 연결 설정
const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' });
- 데이터베이스 연결을 설정 (process.env.POSTGRES_URL 사용).
- ssl: 'require' → 보안을 위해 SSL 연결 강제.
3️⃣ 데이터베이스에서 사용자 정보 가져오기 (getUser 함수)
async function getUser(email: string): Promise<User | undefined> {
try {
const user = await sql<User[]>`SELECT * FROM users WHERE email=${email}`;
return user[0];
} catch (error) {
console.error('Failed to fetch user:', error);
throw new Error('Failed to fetch user.');
}
}
- getUser(email) 함수는 PostgreSQL에서 특정 이메일의 사용자 정보를 가져오는 역할.
- sql<User[]> → SQL 쿼리를 실행하여 users 테이블에서 이메일이 일치하는 사용자 조회.
- 데이터가 있으면 user[0]을 반환, 없으면 undefined.
- 오류 발생 시 콘솔에 출력하고 예외 처리.
4️⃣ NextAuth 설정 (NextAuth({...}))
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
- authConfig를 확장하여 기존 인증 설정(예: 로그인 페이지)을 유지하면서 추가 설정 적용.
- auth, signIn, signOut 함수를 추출하여 외부에서 사용 가능하도록 설정.
5️⃣ 이메일/비밀번호 로그인 처리 (Credentials Provider)
providers: [
Credentials({
async authorize(credentials) {
- Credentials 프로바이더를 사용하여 이메일 & 비밀번호 로그인 기능을 구현.
- authorize(credentials) 함수는 사용자가 로그인할 때 호출됨.
🔹 1. 입력값 검증 (Zod 사용)
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
- 사용자가 입력한 이메일과 비밀번호를 검증.
- safeParse() → 값이 유효하면 parsedCredentials.success = true, 그렇지 않으면 false.
- 이메일 형식(.email())과 비밀번호 길이(.min(6))를 체크.
🔹 2. 사용자 조회 & 비밀번호 확인
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
if (!user) return null;
const passwordsMatch = await bcrypt.compare(password, user.password);
if (passwordsMatch) return user;
}
- 사용자가 입력한 이메일을 getUser(email) 함수로 DB에서 찾음.
- 사용자가 존재하지 않으면 null 반환 → 로그인 실패 처리.
- 입력한 비밀번호와 DB에 저장된 해싱된 비밀번호를 bcrypt.compare()로 비교.
- 비밀번호가 맞으면 user 객체 반환 → 로그인 성공.
- 비밀번호가 틀리면 null 반환 → 로그인 실패.
🔹 3. 로그인 실패 시 처리
console.log('Invalid credentials');
return null;
- 잘못된 로그인 정보일 경우 null을 반환하여 로그인 실패 처리.
- console.log()로 로그인 실패 원인 출력.
✅ 결론
✔ PostgreSQL에서 사용자 정보를 가져와 이메일 & 비밀번호 로그인 구현
✔ bcrypt.compare()를 사용해 입력한 비밀번호와 DB 저장 비밀번호 비교
✔ Zod를 활용하여 입력값(이메일 & 비밀번호) 검증
✔ 로그인 성공 시 사용자 정보를 반환하고, 실패 시 null 반환
🚀 즉, 이 코드는 Next.js + NextAuth.js를 활용하여 이메일 & 비밀번호 기반 인증을 구현하는 핵심 로직! 😊
action.ts 에 로그인 액션을 추가합니다.
'use server';
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';
// ...
export async function authenticate(
prevState: string | undefined,
formData: FormData,
) {
try {
await signIn('credentials', formData);
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return 'Invalid credentials.';
default:
return 'Something went wrong.';
}
}
throw error;
}
}
'use client';
//...//
import { useActionState } from 'react';
import { authenticate } from '@/app/lib/actions';
import { useSearchParams } from 'next/navigation';
export default function LoginForm() {
const searchParams = useSearchParams();
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
const [errorMessage, formAction, isPending] = useActionState(
authenticate,
undefined,
);
return (
<form action={formAction} className="space-y-3">
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
<div className="w-full">
//......//
</div>
<input type="hidden" name="redirectTo" value={callbackUrl} />
<Button className="mt-4 w-full" aria-disabled={isPending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
</Button>
<div
...
>
{errorMessage && (
<>
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
</>
)}
</div>
</div>
</form>
);
}
📌 Next.js 로그인 폼 (LoginForm) 코드 해설
이 코드는 Next.js에서 클라이언트 측에서 실행되는 로그인 폼을 구현한 것으로,
폼 제출, 오류 메시지 표시, 인증 처리, 페이지 리디렉션 기능을 포함하고 있어요.
✅ 코드 분석
1️⃣ 'use client'; → 클라이언트 컴포넌트 설정
'use client';
- Next.js에서 이 컴포넌트가 클라이언트 측에서 실행됨을 명시.
- useState, useEffect, useActionState 같은 훅을 사용할 수 있도록 설정.
2️⃣ 필요한 모듈 가져오기
import { useActionState } from 'react';
import { authenticate } from '@/app/lib/actions';
import { useSearchParams } from 'next/navigation';
- 인증 함수 (authenticate) 가져오기
- 사용자가 로그인하면 이 함수가 실행되어 인증을 처리함.
- URL의 쿼리 파라미터 가져오기 (useSearchParams())
- 로그인 후 리디렉션할 callbackUrl 값을 가져옴.
3️⃣ callbackUrl 설정 → 로그인 후 이동할 페이지 확인
const searchParams = useSearchParams();
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
- URL에서 callbackUrl을 가져옴.
- 예: /login?callbackUrl=/profile → 로그인 후 /profile로 이동하도록 설정.
- callbackUrl 값이 없으면 기본적으로 /dashboard로 이동하도록 설정.
4️⃣ useActionState() → 로그인 폼 상태 관리
const [errorMessage, formAction, isPending] = useActionState(
authenticate,
undefined,
);
- useActionState()는 폼의 상태(로딩, 오류 등)를 관리하는 Next.js 훅.
- authenticate → 로그인 액션 함수 (서버에서 실행).
- errorMessage → 로그인 실패 시 표시될 오류 메시지.
- formAction → 폼이 제출될 때 실행할 액션 (authenticate 호출).
- isPending → 로그인 요청이 처리 중인지 여부 (true면 버튼 비활성화).
5️⃣ 로그인 폼 렌더링 (<form> 태그)
<form action={formAction} className="space-y-3">
- <form>의 action={formAction} → 폼이 제출되면 authenticate 실행.
6️⃣ redirectTo 값(hidden input) 설정
<input type="hidden" name="redirectTo" value={callbackUrl} />
- 로그인 후 사용자를 어디로 보낼지 전달하기 위한 숨겨진 입력 필드.
- 예: callbackUrl=/profile이면 로그인 성공 후 /profile로 이동.
7️⃣ 로그인 버튼 (<Button>)
<Button className="mt-4 w-full" aria-disabled={isPending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
</Button>
- aria-disabled={isPending} → 로그인 요청 중이면 버튼 비활성화.
- ArrowRightIcon → 로그인 버튼 옆에 오른쪽 화살표 아이콘 추가.
8️⃣ 오류 메시지 표시
{errorMessage && (
<>
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
</>
)}
- errorMessage가 존재하면 오류 메시지를 표시.
- ExclamationCircleIcon → 빨간색 경고 아이콘 추가.
✅ 결론
✔ Next.js 클라이언트 컴포넌트에서 로그인 폼 구현
✔ NextAuth.js의 authenticate를 활용한 로그인 처리
✔ 사용자가 로그인하면 callbackUrl을 통해 원하는 페이지로 이동
✔ 로그인 진행 중 isPending 상태를 활용해 버튼 비활성화
✔ 로그인 실패 시 오류 메시지 표시
🚀 즉, 이 코드는 Next.js에서 인증 시스템을 구축할 때 사용되는 클라이언트 로그인 폼! 😊
'WEB > Next.js' 카테고리의 다른 글
Learn Next.js 제 16장 : Adding Metadata (0) | 2025.03.13 |
---|---|
Learn Next.js 제 14장 : Improving Accessibility (0) | 2025.03.13 |
Learn Next.js 제 13장 : Handling Errors (0) | 2025.03.12 |
Learn Next.js 제 12장 : Mutating Data (0) | 2025.03.12 |
Learn Next.js 제 11장 : Adding Search and Pagination (0) | 2025.03.11 |