در این مقاله قصد داریم تا با مفهوم احراز هویت (authentication) آشنا شویم و نحوه افزودن آن به برنامه Next.js، با کمک NextAuth.js را با هم بررسی کنیم.
منظور از احراز هویت چیست؟
امروزه احراز هویت بخش کلیدی بسیاری از اپلیکیشنهای وب است. به زبان ساده، کارکرد احراز هویت به این صورت است که بررسی میکند آیا کاربر همان کسی است که میگوید یا خیر.
یک وب سایت امن اغلب از راههای متعددی برای بررسی هویت کاربر استفاده مینماید. به عنوان مثال، پس از وارد کردن نام کاربری و رمز عبور، سایت ممکن است یک کد تأیید به دستگاه ما ارسال کند، یا این که از یک برنامه خارجی مانند Google Authenticator استفاده نماید. این احراز هویت دو مرحلهای (۲FA) به افزایش امنیت کمک میکند. در این صورت، حتی اگر شخصی رمز عبور ما را یاد بگیرد، نمیتواند بدون داشتن رمز منحصر به فرد ما به حساب کاربریمان دسترسی داشته باشد.
مقایسه Authentication و Authorization
در توسعه وب، authentication و authorization نقشهای مختلفی را ایفا میکنند:
- authentication عبارت است از اطمینان از اینکه آیا کاربر همان کسی است که میگوید یا خیر. ما هویت خود را با مواردی مانند نام کاربری و رمز عبور ثابت میکنیم.
- authorization مرحله بعدی است. هنگامی که هویت کاربر تأیید شد، authorization تصمیم میگیرد که این کاربر مجاز است تا از چه بخشهایی از برنامه استفاده نماید.
بنابراین، authentication بررسی میکند که ما چه کسی هستیم، و authorization تعیین میکند که چه کاری میتوانیم انجام دهیم یا به چه بخشهایی دسترسی داشته باشیم.
ساخت مسیر login
برای این کار یک مسیر جدید در برنامه خود به نام
/login
/login
ایجاد میکنیم و کد زیر را در آن مینویسیم:
import AcmeLogo from '@/app/ui/acme-logo';
import LoginForm from '@/app/ui/login-form';
export default function LoginPage() {
<main className="flex items-center justify-center md:h-screen">
<div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32">
<div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36">
<div className="w-32 text-white md:w-36">
import AcmeLogo from '@/app/ui/acme-logo';
import LoginForm from '@/app/ui/login-form';
export default function LoginPage() {
return (
<main className="flex items-center justify-center md:h-screen">
<div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32">
<div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36">
<div className="w-32 text-white md:w-36">
<AcmeLogo />
</div>
</div>
<LoginForm />
</div>
</main>
);
}
import AcmeLogo from '@/app/ui/acme-logo';
import LoginForm from '@/app/ui/login-form';
export default function LoginPage() {
return (
<main className="flex items-center justify-center md:h-screen">
<div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32">
<div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36">
<div className="w-32 text-white md:w-36">
<AcmeLogo />
</div>
</div>
<LoginForm />
</div>
</main>
);
}
NextAuth.js
ما در این مقاله برای افزودن مبحث احراز هویت به برنامه Next.js از NextAuth.js استفاده خواهیم کرد. NextAuth.js بسیاری از پیچیدگیهای مربوط به مدیریت sessionها، sign-in و sign-out و سایر جنبههای احراز هویت را از بین میبرد. ما میتوانیم این ویژگیها را به صورت دستی هم پیادهسازی کنیم، اما این فرآیند میتواند زمانبر و مستعد خطا باشد. بنابراین NextAuth.js این فرآیند را سادهتر کرده و راه حلی یکپارچه برای احراز هویت در برنامههای Next.js ارائه میدهد.
راه اندازی NextAuth.js
NextAuth.js را با اجرای دستور زیر در ترمینال خود نصب میکنیم:
npm install next-auth@beta
npm install next-auth@beta
npm install next-auth@beta
در اینجا، ما نسخه بتا NextAuth.js را نصب میکنیم که با Next.js 14 سازگار است.
در مرحله بعد، یک secret key برای برنامه خود میسازیم. این کلید برای رمزگذاری کوکیها استفاده میشود و امنیت sessionهای کاربر را تضمین میکند. برای انجام این کار میتوانیم دستور زیر را در ترمینال اجرا کنیم:
openssl rand -base64 32
سپس در فایل
.env
.env
خود، کلید تولید شده را به متغیر
AUTH_SECRET
AUTH_SECRET
اضافه مینماییم:
AUTH_SECRET=your-secret-key
AUTH_SECRET=your-secret-key
AUTH_SECRET=your-secret-key
برای اینکه auth در پروژه نهایی که میسازیم کار کند، باید متغیرهای محیطی خود را در پروژه Vercel خود نیز به روز رسانی کنیم. مطالعه این راهنما در مورد نحوه اضافه کردن متغیرهای محیطی در Vercel میتواند مفید باشد.
افزودن گزینه pages
یک فایل
auth.config.ts
auth.config.ts
در root پروژه خود ایجاد میکنیم که یک آبجکت
authConfig
authConfig
را export میکند. این آبجکت شامل گزینههای پیکربندی NextAuth.js خواهد بود. در حال حاضر، فقط گزینه pages را داریم:
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
pages: {
signIn: '/login',
},
};
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
pages: {
signIn: '/login',
},
};
می توانیم از گزینه
pages
pages
برای تعیین مسیر سفارشی sign-in، sign-out و صفحات خطا استفاده نماییم. این مورد ضروری نیست، اما با افزودن گزینه
signIn: '/login'
signIn: '/login'
به
pages
pages
، کاربر به جای صفحه پیشفرض NextAuth.js به صفحه لاگین سفارشی مدنظر ما هست هدایت میشود.
محافظت از مسیرها با استفاده از Next.js Middleware
در مرحله بعد، منطق محافظت از مسیرهای خود را اضافه میکنیم. این کار باعث میشود تا کاربران به صفحات داشبورد دسترسی نداشته باشند مگر اینکه وارد سیستم شوند.
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isLoggedIn) return true;
return false; // Redirect unauthenticated users to login page
return Response.redirect(new URL('/dashboard', nextUrl));
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
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;
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;
callback
authorized
authorized
مورد استفاده قرار میگیرد تا اینکه بررسی کند که آیا درخواستی که برای دسترسی به صفحه از طریق
Next.js Middleware اعلام شده است مجاز است یا خیر. این تابع قبل از تکمیل درخواست فراخوانی میشود، و یک آبجکت با ویژگیهای
auth
auth
و
request
request
دریافت میکند. ویژگی
auth
auth
شامل session کاربر و ویژگی
request
request
شامل درخواست ورودی است.
گزینه
providers
providers
آرایهای است که در آن گزینههای مختلف برای لاگین را لیست میکنیم. در حال حاضر، این یک آرایه خالی است تا پیکربندی NextAuth را تکمیل کند. مطالعه
Adding the Credentials provider میتواند مفید باشد.
پس از آن، باید آبجکت
authConfig
authConfig
را در یک فایل Middleware وارد کنیم. در root پروژه خود یک فایل به نام
Middleware.ts
Middleware.ts
ایجاد کرده و کد زیر را در آن مینویسیم:
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
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$).*)'],
};
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.js را با آبجکت
authConfig
authConfig
مقداردهی اولیه کرده و ویژگی
auth
auth
را export میکنیم. همچنین برای این که مشخص کنیم که باید در مسیرهای خاصی اجرا شود، از گزینه
matcher
matcher
از Middleware استفاده میکنیم.
مزیت استفاده از Middleware برای این کار این است که تا زمانی که Middleware احراز هویت را تأیید نکند، مسیرهای محافظت شده حتی شروع به رندر نمیکنند. در نتیجه امنیت و عملکرد برنامه ما افزایش پیدا میکند.
Hash کردن رمز عبور
hash کردن رمزهای عبور قبل از ذخیره آنها در پایگاه داده، کار بسیار مفیدی است. hash کردن، رمز عبور را به یک رشته کاراکتر با طول ثابت تبدیل میکند که بهصورت تصادفی ظاهر میشود و حتی اگر دادههای کاربر در معرض دید دیگران قرار گیرد، لایهای از امنیت را فراهم میکند.
برای این کار ما باید یک فایل جداگانه برای پکیج
bcrypt
bcrypt
ایجاد کنیم. دلیل این کار این است که
bcrypt
bcrypt
به APIهای Node.js متکی است که در Next.js Middleware موجود نیستند.
یک فایل جدید به نام
auth.ts
auth.ts
ایجاد میکنیم که آبجکت
authConfig
authConfig
را گسترش میدهد:
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export const { auth, signIn, signOut } = NextAuth({
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
});
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
});
افزودن Credentials provider
در مرحله بعد، باید گزینه
providers
providers
را برای NextAuth.js اضافه نماییم.
providers
providers
آرایهای است که در آن گزینههای مختلف لاگین مانند Google یا GitHub را لیست میکنیم. برای این مقاله، ما فقط بر استفاده از
Credentials provider تمرکز خواهیم کرد.
Credentials provider به کاربران اجازه میدهد تا با نام کاربری و رمز عبور وارد شوند.
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
export const { auth, signIn, signOut } = NextAuth({
providers: [Credentials({})],
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [Credentials({})],
});
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [Credentials({})],
});
بهتر است به این موضوع توجه داشته باشیم که، اگرچه ما از Credentials provider استفاده میکنیم، اما به طور کلی توصیه میشود از providerهای جایگزین مانند OAuth یا providerهای email استفاده نماییم. برای داشتن لیست کامل گزینهها، مطالعه مستندات NextAuth.js میتواند مفید باشد.
افزودن قابلیت sign in
میتوانیم از تابع
autorize
autorize
برای مدیریت منطق احراز هویت استفاده کنیم. مشابه
Server Actionها، میتوانیم از
zod
zod
برای اعتبارسنجی ایمیل و رمز عبور، قبل از بررسی وجود کاربر در پایگاه داده استفاده نماییم:
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
export const { auth, signIn, signOut } = NextAuth({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
},
}),
],
});
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
},
}),
],
});
پس از تأیید اعتبار، یک تابع
getUser
getUser
جدید ایجاد میکنیم که کاربر را از پایگاه داده درخواست میکند.
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
async function getUser(email: string): Promise<User | undefined> {
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
console.error('Failed to fetch user:', error);
throw new Error('Failed to fetch user.');
export const { auth, signIn, signOut } = NextAuth({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
async function getUser(email: string): Promise<User | undefined> {
try {
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
return user.rows[0];
} catch (error) {
console.error('Failed to fetch user:', error);
throw new Error('Failed to fetch user.');
}
}
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
if (!user) return null;
}
return null;
},
}),
],
});
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
async function getUser(email: string): Promise<User | undefined> {
try {
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
return user.rows[0];
} catch (error) {
console.error('Failed to fetch user:', error);
throw new Error('Failed to fetch user.');
}
}
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
if (!user) return null;
}
return null;
},
}),
],
});
سپس،
bcrypt.compare
bcrypt.compare
را فراخوانی میکنیم تا بررسی کنیم که آیا رمزهای عبور باهم مطابقت دارند یا نه:
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
export const { auth, signIn, signOut } = NextAuth({
async authorize(credentials) {
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
const passwordsMatch = await bcrypt.compare(password, user.password);
if (passwordsMatch) return user;
console.log('Invalid credentials');
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { sql } from '@vercel/postgres';
import { z } from 'zod';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
// ...
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
// ...
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;
}
console.log('Invalid credentials');
return null;
},
}),
],
});
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { sql } from '@vercel/postgres';
import { z } from 'zod';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
// ...
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
// ...
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;
}
console.log('Invalid credentials');
return null;
},
}),
],
});
در نهایت، اگر رمزهای عبور مطابقت داشته باشند میخواهیم تا کاربر را return کنیم، در غیر این صورت، برای جلوگیری از ورود کاربر،
null
null
را return میکنیم.
به روز رسانی فرم لاگین
اکنون باید منطق تأیید اعتبار را به فرم لاگین خود متصل کنیم. در فایل
actions.ts
actions.ts
خود یک action جدید به نام
authenticate
authenticate
ایجاد میکنیم. این action باید تابع
signIn
signIn
را از
auth.ts
auth.ts
import کند:
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';
export async function authenticate(
prevState: string | undefined,
await signIn('credentials', formData);
if (error instanceof AuthError) {
case 'CredentialsSignin':
return 'Invalid credentials.';
return 'Something went wrong.';
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;
}
}
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;
}
}
اگر خطای
'CredentialsSignin'
'CredentialsSignin'
وجود داشته باشد، میخواهیم یک پیام خطای مناسب نشان دهیم. برای آشنایی بیشتر در مورد
خطاهای NextAuth.js مطالعه مستندات آن میتواند مفید باشد.
در نهایت، در کامپوننت
login-form.tsx
login-form.tsx
، میتوانیم از
useFormState
useFormState
React برای فراخوانی server action و رسیدگی به خطاهای فرم، و از
useFormStatus
useFormStatus
برای رسیدگی به pending state فرم استفاده نماییم:
import { lusitana } from '@/app/ui/fonts';
} from '@heroicons/react/24/outline';
import { ArrowRightIcon } from '@heroicons/react/20/solid';
import { Button } from '@/app/ui/button';
import { useFormState, useFormStatus } from 'react-dom';
import { authenticate } from '@/app/lib/actions';
export default function LoginForm() {
const [errorMessage, dispatch] = useFormState(authenticate, undefined);
<form action={dispatch} className="space-y-3">
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
<h1 className={`${lusitana.className} mb-3 text-2xl`}>
Please log in to continue.
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
<div className="relative">
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
placeholder="Enter your email address"
<AtSymbolIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
<div className="relative">
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
placeholder="Enter password"
<KeyIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
className="flex h-8 items-end space-x-1"
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
const { pending } = useFormStatus();
<Button className="mt-4 w-full" aria-disabled={pending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
'use client';
import { lusitana } from '@/app/ui/fonts';
import {
AtSymbolIcon,
KeyIcon,
ExclamationCircleIcon,
} from '@heroicons/react/24/outline';
import { ArrowRightIcon } from '@heroicons/react/20/solid';
import { Button } from '@/app/ui/button';
import { useFormState, useFormStatus } from 'react-dom';
import { authenticate } from '@/app/lib/actions';
export default function LoginForm() {
const [errorMessage, dispatch] = useFormState(authenticate, undefined);
return (
<form action={dispatch} className="space-y-3">
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
<h1 className={`${lusitana.className} mb-3 text-2xl`}>
Please log in to continue.
</h1>
<div className="w-full">
<div>
<label
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
htmlFor="email"
>
Email
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
id="email"
type="email"
name="email"
placeholder="Enter your email address"
required
/>
<AtSymbolIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
</div>
</div>
<div className="mt-4">
<label
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
htmlFor="password"
>
Password
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
id="password"
type="password"
name="password"
placeholder="Enter password"
required
minLength={6}
/>
<KeyIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
</div>
</div>
</div>
<LoginButton />
<div
className="flex h-8 items-end space-x-1"
aria-live="polite"
aria-atomic="true"
>
{errorMessage && (
<>
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
</>
)}
</div>
</div>
</form>
);
}
function LoginButton() {
const { pending } = useFormStatus();
return (
<Button className="mt-4 w-full" aria-disabled={pending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
</Button>
);
}
'use client';
import { lusitana } from '@/app/ui/fonts';
import {
AtSymbolIcon,
KeyIcon,
ExclamationCircleIcon,
} from '@heroicons/react/24/outline';
import { ArrowRightIcon } from '@heroicons/react/20/solid';
import { Button } from '@/app/ui/button';
import { useFormState, useFormStatus } from 'react-dom';
import { authenticate } from '@/app/lib/actions';
export default function LoginForm() {
const [errorMessage, dispatch] = useFormState(authenticate, undefined);
return (
<form action={dispatch} className="space-y-3">
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
<h1 className={`${lusitana.className} mb-3 text-2xl`}>
Please log in to continue.
</h1>
<div className="w-full">
<div>
<label
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
htmlFor="email"
>
Email
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
id="email"
type="email"
name="email"
placeholder="Enter your email address"
required
/>
<AtSymbolIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
</div>
</div>
<div className="mt-4">
<label
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
htmlFor="password"
>
Password
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
id="password"
type="password"
name="password"
placeholder="Enter password"
required
minLength={6}
/>
<KeyIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
</div>
</div>
</div>
<LoginButton />
<div
className="flex h-8 items-end space-x-1"
aria-live="polite"
aria-atomic="true"
>
{errorMessage && (
<>
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
</>
)}
</div>
</div>
</form>
);
}
function LoginButton() {
const { pending } = useFormStatus();
return (
<Button className="mt-4 w-full" aria-disabled={pending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
</Button>
);
}
افزودن قابلیت logout
برای افزودن logout به
<SideNav />
<SideNav />
، تابع
signOut
signOut
را از
auth.ts
auth.ts
در المنت
<form>
<form>
خود فراخوانی میکنیم:
import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';
export default function SideNav() {
<div className="flex h-full flex-col px-3 py-4 md:px-2">
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<button className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';
export default function SideNav() {
return (
<div className="flex h-full flex-col px-3 py-4 md:px-2">
// ...
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<form
action={async () => {
'use server';
await signOut();
}}
>
<button className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
</button>
</form>
</div>
</div>
);
}
import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';
export default function SideNav() {
return (
<div className="flex h-full flex-col px-3 py-4 md:px-2">
// ...
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<form
action={async () => {
'use server';
await signOut();
}}
>
<button className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
</button>
</form>
</div>
</div>
);
}
جمعبندی
در این مقاله سعی کردیم تا با استفاده از NextAuth.js، مفهوم احراز هویت را به برنامه Next.js اضافه کنیم. در نهایت باید بتوانیم با استفاده از اطلاعات زیر وارد برنامه خود شده و از آن خارج شویم:
ایمیل:
user@nextmail.com
user@nextmail.com
رمز عبور:
۱۲۳۴۵۶
۱۲۳۴۵۶
دیدگاهها:
فرشید انجیلی
بهمن 3, 1402 در 12:49 ب.ظ
بسیار عالی و مفید , سپاس از شما