صفحات تسجيل دخول جاهزة لـ Next.js
توفر ثمانية ملفات صغيرة لتطبيق Next.js تدفقًا كاملاً لتسجيل الدخول، بما في ذلك الصفحات التي تشير إليها الروابط الموجودة في رسائل البريد الإلكتروني Aurabase.
8 د قراءة·المستوى وسيطة·تمت المراجعة في 25/10/2026
تم إنشاء هذا النص الإنجليزي تلقائيًا من النص الأصلي الفرنسي ولم تتم مراجعته بعد.
لقد تغير المصدر الإنجليزي منذ هذه الترجمة. قد تكون بعض المعلومات قديمة.
تمت ترجمة هذه الصفحة تلقائيًا. النسخة الإنجليزية موثوقة.
#الإعداد
1. العملاء والوكيل
تستخدم هذه الصفحات جهاز توجيه التطبيق مع جلسات ملفات تعريف الارتباط (راجع Next.js SSR). قم بتعيين NEXT_PUBLIC_AURABASE_URL و NEXT_PUBLIC_AURABASE_ANON_KEY، ثم قم بإضافة عميل الخادم والوكيل. في Next.js 15، قم بتسمية الملف middleware.ts والدالة middleware.
lib/aurabase/server.tstypescript
import { cookies } from 'next/headers'
import { createServerClient } from '@aurabase/aurabase-js/ssr'
export async function serverClient() {
const store = await cookies()
return createServerClient(process.env.NEXT_PUBLIC_AURABASE_URL!, process.env.NEXT_PUBLIC_AURABASE_ANON_KEY!, {
cookies: {
getAll: () => store.getAll(),
// لا يمكن لمكونات الخادم كتابة ملفات تعريف الارتباط: يقوم الوكيل أدناه بتحديث الجلسة بدلاً من ذلك.
setAll(list) {
try {
list.forEach(({ name, value, options }) => store.set(name, value, options))
} catch {}
},
},
})
}
proxy.tstypescript
import { NextResponse, type NextRequest } from 'next/server'
import { createServerClient } from '@aurabase/aurabase-js/ssr'
const PROTECTED = ['/dashboard']
export async function proxy(request: NextRequest) {
const response = NextResponse.next({ request })
const aura = createServerClient(process.env.NEXT_PUBLIC_AURABASE_URL!, process.env.NEXT_PUBLIC_AURABASE_ANON_KEY!, {
cookies: {
getAll: () => request.cookies.getAll(),
setAll(list) {
list.forEach(({ name, value, options }) => {
request.cookies.set(name, value)
response.cookies.set(name, value, options)
})
},
},
})
// يقوم getUser() بتحديث الرموز المميزة ويسأل الخادم عن هوية المستخدم. لا تقم مطلقًا بالتفويض باستخدام getSession().
const { data: user } = await aura.auth.getUser()
if (!user && PROTECTED.some((p) => request.nextUrl.pathname.startsWith(p))) {
return NextResponse.redirect(new URL('/login', request.url))
}
return response
}
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] }
#تسجيل الدخول
2. قم بتسجيل الدخول والاشتراك
app/login/actions.tstypescript
'use server'
import { redirect } from 'next/navigation'
import { serverClient } from '@/lib/aurabase/server'
export async function login(formData: FormData) {
const aura = await serverClient()
const { data, error } = await aura.auth.signInWithPassword({
email: String(formData.get('email')),
password: String(formData.get('password')),
})
if (error) redirect(`/login?error=${encodeURIComponent(error.code ?? 'ERROR')}`)
// يحصل المستخدم الذي تم تمكين MFA عليه على تحدي بدلاً من جلسة: اطلب الرمز هنا.
if (data && 'mfa_required' in data) redirect(`/login?error=MFA_REQUIRED`)
redirect('/dashboard')
}
export async function sendMagicLink(formData: FormData) {
const aura = await serverClient()
await aura.auth.signInWithMagicLink({ email: String(formData.get('email')) })
redirect('/login?sent=1')
}
app/login/page.tsxtsx
import Link from 'next/link'
import { login, sendMagicLink } from './actions'
export default async function LoginPage({ searchParams }: { searchParams: Promise<{ error?: string; sent?: string }> }) {
const { error, sent } = await searchParams
return (
<main>
<h1>Sign in</h1>
{error && <p role="alert">Sign-in failed: {error}</p>}
{sent && <p>If this address has an account, a sign-in link was sent.</p>}
<form action={login}>
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Sign in</button>
</form>
<form action={sendMagicLink}>
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Email me a sign-in link</button>
</form>
<p>
<Link href="/signup">Create an account</Link> · <Link href="/forgot-password">Forgot your password?</Link>
</p>
</main>
)
}
app/signup/actions.tstypescript
'use server'
import { redirect } from 'next/navigation'
import { serverClient } from '@/lib/aurabase/server'
export async function signup(formData: FormData) {
const aura = await serverClient()
const { data, error } = await aura.auth.signUp({
email: String(formData.get('email')),
password: String(formData.get('password')),
})
if (error) redirect(`/signup?error=${encodeURIComponent(error.message)}`)
// عندما يتطلب المشروع تأكيدًا عبر البريد الإلكتروني، لا توجد جلسة بعد: `data.session` فارغ.
redirect(data?.session ? '/dashboard' : '/signup?check=1')
}
app/signup/page.tsxtsx
import Link from 'next/link'
import { signup } from './actions'
export default async function SignupPage({ searchParams }: { searchParams: Promise<{ error?: string; check?: string }> }) {
const { error, check } = await searchParams
return (
<main>
<h1>Create an account</h1>
{error && <p role="alert">{error}</p>}
{check && <p>Check your inbox: confirm your email address to finish.</p>}
<form action={signup}>
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required minLength={8} />
<button type="submit">Sign up</button>
</form>
<p>
<Link href="/login">Already have an account?</Link>
</p>
</main>
)
}
#إعادة تعيين
3. إعادة تعيين كلمة المرور
روابط البريد الإلكتروني لإعادة التعيين إلى <Site URL>/reset-password?token=…. قم بتعيين عنوان URL للموقع في الاستوديو ›المصادقة› التكوين.
app/forgot-password/actions.tstypescript
'use server'
import { redirect } from 'next/navigation'
import { serverClient } from '@/lib/aurabase/server'
export async function requestReset(formData: FormData) {
const aura = await serverClient()
await aura.auth.resetPasswordForEmail(String(formData.get('email')))
// نفس الإجابة سواء كان العنوان يحتوي على حساب أم لا (بدون تعداد حساب).
redirect('/forgot-password?sent=1')
}
app/forgot-password/page.tsxtsx
import { requestReset } from './actions'
export default async function ForgotPasswordPage({ searchParams }: { searchParams: Promise<{ sent?: string }> }) {
const { sent } = await searchParams
return (
<main>
<h1>Reset your password</h1>
{sent && <p>If this address has an account, a reset link was sent.</p>}
<form action={requestReset}>
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Send the link</button>
</form>
</main>
)
}
app/reset-password/actions.tstypescript
'use server'
import { redirect } from 'next/navigation'
import { serverClient } from '@/lib/aurabase/server'
export async function resetPassword(formData: FormData) {
const token = String(formData.get('token'))
const aura = await serverClient()
const { error } = await aura.auth.resetPassword(token, String(formData.get('password')))
if (error) redirect(`/reset-password?token=${encodeURIComponent(token)}&error=${encodeURIComponent(error.message)}`)
// تقوم عملية إعادة الضبط بتسجيل دخول المستخدم بجلسة جديدة؛ تم إلغاء الجلسات القديمة.
redirect('/dashboard')
}
app/reset-password/page.tsxtsx
import { resetPassword } from './actions'
export default async function ResetPasswordPage({ searchParams }: { searchParams: Promise<{ token?: string; error?: string }> }) {
const { token, error } = await searchParams
if (!token) return <main><p role="alert">This link is incomplete. Request a new one.</p></main>
return (
<main>
<h1>Choose a new password</h1>
{error && <p role="alert">{error}</p>}
<form action={resetPassword}>
<input type="hidden" name="token" value={token} />
<input name="password" type="password" placeholder="New password" required minLength={8} />
<button type="submit">Save</button>
</form>
</main>
)
}
#روابط
4. البريد الإلكتروني والروابط السحرية
تصل رسائل البريد الإلكتروني الخاصة بالتأكيد والرابط السحري إلى <Site URL>/verify-email?token=… و<Site URL>/magic-link/verify?token=…. يستهلك معالج المسار الرمز المميز، ثم يعيد التوجيه.
app/verify-email/route.tstypescript
import { NextResponse, type NextRequest } from 'next/server'
import { serverClient } from '@/lib/aurabase/server'
export async function GET(request: NextRequest) {
const token = request.nextUrl.searchParams.get('token')
if (token) {
const aura = await serverClient()
const { error } = await aura.auth.verifyOtp({ type: 'signup', token })
if (!error) return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.redirect(new URL('/login?error=LINK_INVALID', request.url))
}
app/magic-link/verify/route.tstypescript
import { NextResponse, type NextRequest } from 'next/server'
import { serverClient } from '@/lib/aurabase/server'
export async function GET(request: NextRequest) {
const token = request.nextUrl.searchParams.get('token')
if (token) {
const aura = await serverClient()
const { error } = await aura.auth.verifyMagicLink(token)
if (!error) return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.redirect(new URL('/login?error=LINK_INVALID', request.url))
}
#لوحة القيادة
5. الصفحة المحمية
app/dashboard/actions.tstypescript
'use server'
import { redirect } from 'next/navigation'
import { serverClient } from '@/lib/aurabase/server'
export async function logout() {
const aura = await serverClient()
await aura.auth.signOut()
redirect('/login')
}
app/dashboard/page.tsxtsx
import { redirect } from 'next/navigation'
import { serverClient } from '@/lib/aurabase/server'
import { logout } from './actions'
export default async function DashboardPage() {
const aura = await serverClient()
const { data: user } = await aura.auth.getUser()
if (!user) redirect('/login')
return (
<main>
<h1>Dashboard</h1>
<p>Signed in as {user.email}</p>
<form action={logout}>
<button type="submit">Sign out</button>
</form>
</main>
)
}
على المكدس المحلي، يتم التقاط رسائل البريد الإلكتروني بواسطة Mailpit بدلاً من إرسالها. افتح صندوق الوارد الخاص به بـ kubectl -n aurabase-dev port-forward svc/aurabase-mailpit 8025:8025 ثم http://localhost:8025. أضف أصل تطبيقك (على سبيل المثال http://localhost:3000/*) إلى الاستوديو › المصادقة › التكوين › إعادة توجيه عناوين URL وقم بتعيين عنوان URL للموقع عليه.
معلومات
proxy.ts عمليات إعادة التوجيه فقط على المسارات المدرجة في PROTECTED؛ تتحقق صفحة لوحة المعلومات من المستخدم مرة أخرى باستخدام getUser() لأن الوكيل ليس حد ترخيص في حد ذاته.