الانتقال إلى المحتوى
الوثائق · Next.js

بدء سريع: Next.js

أضف تسجيل الدخول إلى تطبيق Next.js يستخدم App Router. حزمة @usher/nextjs قادمة، وإلى أن تصدر، يستخدم هذا الدليل تدفق OpenID Connect القياسي، وهو بالضبط ما ستفعله الحزمة نيابةً عنك.

قبل أن تبدأ

  • تطبيق Next.js 15 أو 16 يستخدم App Router.
  • بيئة في Usher. في Usher Console، افتح مشروعك، واختر بيئة، ودوّن جهة الإصدار الخاصة بها.

سجّل تطبيقك

في Console، انتقل إلى التطبيقات وأنشئ تطبيق ويب. أضف عنوان URI لإعادة التوجيه هذا كما هو تمامًا:

http://localhost:3000/auth/callback

انسخ معرّف العميل وسر العميل. يُعرض السر مرة واحدة فقط.

أضف متغيرات البيئة

.env.local
USHER_ISSUER=https://acme.usherid.com
USHER_CLIENT_ID=your_client_id
USHER_CLIENT_SECRET=your_client_secret
APP_URL=http://localhost:3000

ثبّت عميل OpenID Connect

يعمل أي عميل معتمد. يستخدم هذا الدليل openid-client، الذي يتولى الاكتشاف و PKCE والتحقق من صحة الرموز المميزة.

الطرفية
pnpm add openid-client

اكتشف جهة الإصدار

يقرأ الاكتشاف نقاط النهاية والمفاتيح الخاصة بجهة الإصدار مرة واحدة، فلا يُكتب شيء منها بشكل ثابت في الشيفرة.

lib/usher.ts
import * as client from 'openid-client'

let config: Promise<client.Configuration> | undefined

export function usher() {
  config ??= client.discovery(
    new URL(process.env.USHER_ISSUER!),
    process.env.USHER_CLIENT_ID!,
    process.env.USHER_CLIENT_SECRET!,
  )
  return config
}

export const redirectUri = `${process.env.APP_URL}/auth/callback`

أرسل الأشخاص إلى Lobby

يُنشئ معالج المسار (route handler) مُحقِّق PKCE وقيمة state، ويحفظهما في ملف تعريف ارتباط قصير العمر، ثم يعيد التوجيه إلى Lobby.

app/auth/sign-in/route.ts
import * as client from 'openid-client'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { redirectUri, usher } from '@/lib/usher'

export async function GET() {
  const config = await usher()
  const codeVerifier = client.randomPKCECodeVerifier()
  const state = client.randomState()

  const jar = await cookies()
  jar.set('usher_flow', JSON.stringify({ codeVerifier, state }), {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    path: '/auth',
    maxAge: 600,
  })

  const url = client.buildAuthorizationUrl(config, {
    redirect_uri: redirectUri,
    scope: 'openid profile email offline_access',
    code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
    code_challenge_method: 'S256',
    state,
  })
  redirect(url.href)
}

عالج رد الاستدعاء

يعيد Usher الشخص ومعه رمز. استبدل به رموزًا مميزة، وسيتحقق openid-client من قيمة state ومُحقِّق PKCE وتوقيع رمز الهوية نيابةً عنك.

app/auth/callback/route.ts
import * as client from 'openid-client'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { usher } from '@/lib/usher'

export async function GET(request: Request) {
  const config = await usher()
  const jar = await cookies()
  const flow = JSON.parse(jar.get('usher_flow')?.value ?? '{}')
  jar.delete('usher_flow')

  const tokens = await client.authorizationCodeGrant(config, new URL(request.url), {
    pkceCodeVerifier: flow.codeVerifier,
    expectedState: flow.state,
  })
  const claims = tokens.claims()! // sub, email, name, …

  // start your own session here: store claims.sub, and keep the refresh token server-side
  redirect('/')
}

سجّل الخروج

أنهِ جلسة الشخص في تطبيقك، ثم أرسله إلى نقطة نهاية إنهاء الجلسة في Usher لكي ينساه Lobby أيضًا.

app/auth/sign-out/route.ts
import * as client from 'openid-client'
import { redirect } from 'next/navigation'
import { usher } from '@/lib/usher'

export async function GET() {
  const config = await usher()
  // clear your own session first
  const url = client.buildEndSessionUrl(config, {
    post_logout_redirect_uri: process.env.APP_URL!,
  })
  redirect(url.href)
}

باستخدام حزمة SDK

عند نشر @usher/nextjs، سيبدو التطبيق نفسه هكذا. وقد تتغير المعاينة أدناه قبل الإصدار.

app/layout.tsx · app/page.tsx (معاينة)
// app/layout.tsx
import { UsherProvider } from '@usher/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <UsherProvider>{children}</UsherProvider>
      </body>
    </html>
  )
}

// app/page.tsx
import { SignIn, UserButton, currentUser } from '@usher/nextjs'

export default async function Page() {
  const user = await currentUser()
  return user ? <UserButton /> : <SignIn />
}

الخطوات التالية