المصادقة في Next.js — دليل شامل 2026
دليل عملي مفصل للمصادقة في Next.js — NextAuth.js، JWT، Middleware، وحماية المسارات
في المقال السابق، تعلمت Styling. الآن سنتعلم المصادقة (Authentication) — وهي الميزة الأكثر تعقيداً في أي تطبيق حقيقي.
في هذا الدليل العملي، سنأخذك خطوة بخطوة لفهم المصادقة في Next.js، مع تمارين وحلول.
ما هي المصادقة؟
المصادقة (Authentication) = التحقق من هوية المستخدم. التفويض (Authorization) = التحقق من صلاحياته.
مثال:
- Authentication: تسجيل الدخول باسم المستخدم وكلمة المرور.
- Authorization: هل هذا المستخدم مدير؟
أنواع المصادقة
| النوع | الوصف | الاستخدام |
|---|---|---|
| Sessions | جلسة على السيرفر | تطبيقات تقليدية |
| JWT | توكن مشفر | APIs و SPAs |
| OAuth | مزود خارجي | Google, GitHub |
| Magic Links | رابط بالبريد | تطبيقات حديثة |
| 2FA | عاملان | أمان عالٍ |
NextAuth.js (Auth.js)
NextAuth.js (الآن Auth.js) هو الحل الرسمي للمصادقة في Next.js.
المميزات:
- ✅ يدعم عدة مزودين (Google, GitHub, Credentials).
- ✅ Sessions و JWT.
- ✅ Middleware مدمج.
- ✅ Hooks جاهزة.
- ✅ TypeScript.
- ✅ قواعد بيانات متعددة.
تثبيت NextAuth
npm install next-auth@beta
⚠️ ملاحظة: @beta لأننا نستخدم Next.js 15 (App Router).
الإعداد الأساسي
1. .env.local
NEXTAUTH_SECRET=your-super-secret-key-here
NEXTAUTH_URL=http://localhost:3000
# لمزود Google
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# لمزود GitHub
GITHUB_ID=your-github-id
GITHUB_SECRET=your-github-secret
لإنشاء NEXTAUTH_SECRET:
openssl rand -base64 32
2. app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GitHubProvider from "next-auth/providers/github";
import CredentialsProvider from "next-auth/providers/credentials";
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
// هنا نتحقق من البيانات
if (credentials?.email === "[email protected]" && credentials?.password === "password") {
return { id: "1", name: "أحمد", email: "[email protected]" };
}
return null;
},
}),
],
pages: {
signIn: "/login",
error: "/login",
},
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
(session.user as any).id = token.id;
}
return session;
},
},
});
export { handler as GET, handler as POST };
3. SessionProvider
app/providers.tsx:
"use client";
import { SessionProvider } from "next-auth/react";
export default function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
4. app/layout.tsx
import Providers from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ar" dir="rtl">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
استخدام المصادقة
1. في Server Component
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
export default async function ProfilePage() {
const session = await getServerSession(authOptions);
if (!session) {
return <p>يجب تسجيل الدخول</p>;
}
return (
<div>
<h1>مرحباً {session.user?.name}</h1>
<p>{session.user?.email}</p>
</div>
);
}
2. في Client Component
"use client";
import { useSession, signIn, signOut } from "next-auth/react";
export default function AuthButton() {
const { data: session, status } = useSession();
if (status === "loading") {
return <p>جاري التحميل...</p>;
}
if (session) {
return (
<div className="flex items-center gap-4">
<span>مرحباً {session.user?.name}</span>
<button
onClick={() => signOut()}
className="bg-red-600 text-white px-4 py-2 rounded-lg"
>
خروج
</button>
</div>
);
}
return (
<button
onClick={() => signIn()}
className="bg-blue-600 text-white px-4 py-2 rounded-lg"
>
تسجيل الدخول
</button>
);
}
صفحات تسجيل الدخول
app/login/page.tsx
"use client";
import { signIn } from "next-auth/react";
import { useState } from "react";
export default function LoginPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
const res = await signIn("credentials", {
email,
password,
redirect: false,
});
if (res?.error) {
setError("بيانات الدخول غير صحيحة");
} else {
window.location.href = "/dashboard";
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<form
onSubmit={handleSubmit}
className="bg-white p-8 rounded-2xl shadow-lg w-full max-w-md"
>
<h1 className="text-2xl font-bold text-center mb-6">تسجيل الدخول</h1>
{error && (
<div className="bg-red-100 text-red-700 p-3 rounded-lg mb-4">
{error}
</div>
)}
<input
type="email"
placeholder="البريد"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-4 py-3 border rounded-lg mb-4"
/>
<input
type="password"
placeholder="كلمة المرور"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full px-4 py-3 border rounded-lg mb-4"
/>
<button
type="submit"
className="w-full bg-blue-600 text-white py-3 rounded-lg font-bold hover:bg-blue-700 transition mb-4"
>
دخول
</button>
<div className="border-t pt-4">
<p className="text-center text-sm text-gray-500 mb-4">
أو سجل باستخدام
</p>
<div className="flex gap-2">
<button
type="button"
onClick={() => signIn("google", { callbackUrl: "/dashboard" })}
className="flex-1 bg-white border-2 border-gray-200 py-3 rounded-lg hover:border-gray-300 transition"
>
🔵 Google
</button>
<button
type="button"
onClick={() => signIn("github", { callbackUrl: "/dashboard" })}
className="flex-1 bg-gray-900 text-white py-3 rounded-lg hover:bg-gray-800 transition"
>
⚫ GitHub
</button>
</div>
</div>
</form>
</div>
);
}
حماية المسارات (Middleware)
middleware.ts (في جذر المشروع):
import { withAuth } from "next-auth/middleware";
export default withAuth({
pages: {
signIn: "/login",
},
});
export const config = {
matcher: ["/dashboard/:path*", "/profile/:path*", "/admin/:path*"],
};
النتيجة: أي وصول لـ /dashboard/* يتطلب تسجيل دخول.
حماية المسارات (بدون Middleware)
app/dashboard/page.tsx:
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect("/login");
}
return (
<div>
<h1>لوحة التحكم</h1>
<p>مرحباً {session.user?.name}</p>
</div>
);
}
Types (TypeScript)
types/next-auth.d.ts:
import NextAuth from "next-auth";
declare module "next-auth" {
interface Session {
user: {
id: string;
name: string;
email: string;
role?: string;
};
}
interface User {
id: string;
role?: string;
}
}
declare module "next-auth/jwt" {
interface JWT {
id: string;
role?: string;
}
}
نموذج كامل مع قاعدة بيانات
1. lib/auth.ts
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
import { prisma } from "./prisma";
export const authOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user) return null;
const isValid = await bcrypt.compare(
credentials.password,
user.password
);
if (!isValid) return null;
return {
id: user.id,
name: user.name,
email: user.email,
role: user.role,
};
},
}),
],
session: { strategy: "jwt" as const },
callbacks: {
async jwt({ token, user }: any) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }: any) {
if (session.user) {
session.user.id = token.id;
session.user.role = token.role;
}
return session;
},
},
pages: {
signIn: "/login",
},
};
استخدام الدوال المساعدة
lib/auth-helpers.ts:
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "./auth";
export async function requireAuth() {
const session = await getServerSession(authOptions);
if (!session) redirect("/login");
return session;
}
export async function requireAdmin() {
const session = await getServerSession(authOptions);
if (!session) redirect("/login");
if (session.user.role !== "admin") redirect("/");
return session;
}
الاستخدام:
export default async function DashboardPage() {
const session = await requireAuth();
return <h1>مرحباً {session.user.name}</h1>;
}
تمارين عملية
تمرين 1: إعداد NextAuth
أعدّ NextAuth مع Google.
الحل:
const handler = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
});
تمرين 2: زر تسجيل الدخول
أنشئ زر تسجيل دخول.
الحل:
"use client";
import { signIn } from "next-auth/react";
export default function LoginButton() {
return <button onClick={() => signIn()}>تسجيل الدخول</button>;
}
تمرين 3: عرض Session
اعرض بيانات المستخدم.
الحل:
"use client";
import { useSession } from "next-auth/react";
export default function Profile() {
const { data: session } = useSession();
if (!session) return <p>غير مسجل</p>;
return <p>مرحباً {session.user?.name}</p>;
}
تمرين 4: حماية صفحة
احمِ /dashboard.
الحل:
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@/lib/auth";
export default async function Page() {
const session = await getServerSession(authOptions);
if (!session) redirect("/login");
return <h1>محمي</h1>;
}
تمرين 5: Middleware
احمِ /admin/*.
الحل:
import { withAuth } from "next-auth/middleware";
export default withAuth();
export const config = { matcher: ["/admin/:path*"] };
تمرين 6: تسجيل الخروج
أضف زر خروج.
الحل:
"use client";
import { signOut } from "next-auth/react";
export default function LogoutButton() {
return <button onClick={() => signOut()}>خروج</button>;
}
تمرين 7: صفحة تسجيل دخول مخصصة
أنشئ /login.
الحل: (راجع المثال أعلاه)
تمرين 8: تطبيق كامل
ابنِ تطبيق مع مصادقة كاملة.
الحل: (راجع الأمثلة أعلاه)
حل المشاكل الشائعة
🔴 المشكلة 1: NEXTAUTH_SECRET is missing
الحل: أضف NEXTAUTH_SECRET في .env.local.
🔴 المشكلة 2: signIn لا يعمل
السبب: لم تُضف SessionProvider.
الحل: أضفها في layout.tsx.
🔴 المشكلة 3: Session فارغ
الحل: تأكد من getServerSession(authOptions).
🔴 المشكلة 4: Middleware لا يعمل
السبب: matcher ناقص.
الحل:
export const config = {
matcher: ["/dashboard/:path*"],
};
🔴 المشكلة 5: useSession لا يعمل
السبب: لم تستخدم SessionProvider.
الحل: أضفه في providers.tsx.
جدول دوال NextAuth
| الدالة | الوظيفة |
|---|---|
signIn() | تسجيل الدخول |
signOut() | تسجيل الخروج |
useSession() | الحصول على Session |
getServerSession() | Session على السيرفر |
withAuth() | Middleware |
قائمة تحقق نهائية
| المهمة | الحالة |
|---|---|
| تثبيت NextAuth | ⬜ |
| إعداد المزودين | ⬜ |
| صفحة تسجيل دخول | ⬜ |
| حماية المسارات | ⬜ |
| Middleware | ⬜ |
| TypeScript Types | ⬜ |
| حل التمارين الثمانية | ⬜ |
ماذا بعد هذا المقال؟
الآن بعد أن أتقنت المصادقة، أنت جاهز للمقال التالي:
- Deployment — النشر.
- مشروع كامل — مدونة متكاملة.
الخلاصة
في هذا المقال، تعلمت:
- ✅ ما هي المصادقة.
- ✅ NextAuth.js.
- ✅ Credentials و OAuth.
- ✅ Sessions و JWT.
- ✅ حماية المسارات.
- ✅ Middleware.
- ✅ TypeScript Types.
- ✅ حل 8 تمارين عملية.
تذكر: المصادقة هي أعقد ميزة — لكن NextAuth يجعلها سهلة.
📚 مقالات ذات صلة
مشروع Next.js متكامل — مدونة احترافية 2026
مشروع عملي شامل لبناء مدونة كاملة بـ Next.js — مع Markdown، SEO، نشر تلقائي، وتصميم احترافي
نشر تطبيق Next.js — دليل شامل 2026
دليل عملي مفصل لنشر تطبيق Next.js — Vercel، Firebase، Netlify، والتصدير الثابت
Styling في Next.js — دليل شامل 2026
دليل عملي مفصل لـ Styling في Next.js — Tailwind CSS، CSS Modules، Styled Components، مع تمارين وحلول