Next.js📅 2026-11-18⏱ 9 دقائق قراءة⚡ مقال 8 من 10

المصادقة في 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⬜
حل التمارين الثمانية⬜

ماذا بعد هذا المقال؟

الآن بعد أن أتقنت المصادقة، أنت جاهز للمقال التالي:

  1. Deployment — النشر.
  2. مشروع كامل — مدونة متكاملة.

الخلاصة

في هذا المقال، تعلمت:

  • ✅ ما هي المصادقة.
  • ✅ NextAuth.js.
  • ✅ Credentials و OAuth.
  • ✅ Sessions و JWT.
  • ✅ حماية المسارات.
  • ✅ Middleware.
  • ✅ TypeScript Types.
  • ✅ حل 8 تمارين عملية.

تذكر: المصادقة هي أعقد ميزة — لكن NextAuth يجعلها سهلة.

📚 مقالات ذات صلة

Next.js

مشروع Next.js متكامل — مدونة احترافية 2026

مشروع عملي شامل لبناء مدونة كاملة بـ Next.js — مع Markdown، SEO، نشر تلقائي، وتصميم احترافي

Next.js

نشر تطبيق Next.js — دليل شامل 2026

دليل عملي مفصل لنشر تطبيق Next.js — Vercel، Firebase، Netlify، والتصدير الثابت

Next.js

Styling في Next.js — دليل شامل 2026

دليل عملي مفصل لـ Styling في Next.js — Tailwind CSS، CSS Modules، Styled Components، مع تمارين وحلول