Логотип Аурабаза
аурабазадокументы
документы›Путеводители›Next.js sign-in pages

Готовые страницы входа для Next.js

Восемь небольших файлов обеспечивают приложению Next.js полный процесс входа в систему, включая страницы, на которые указывают ссылки в электронных письмах Aurabase.

8 мин читать·Уровень средний·Пересмотрено 25 окт. 2026 г.
Этот текст на английском языке был создан автоматически на основе французского оригинала и еще не проверялся.
Английский источник изменился после этого перевода. Некоторая информация может быть устаревшей.
Эта страница была переведена автоматически. Английская версия является авторитетной.
#
Настраивать

1. Клиенты и прокси

Эти страницы используют App Router с сеансами cookie (см. 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(),
      // Серверные компоненты не могут записывать файлы cookie: вместо этого прокси-сервер обновляет сеанс.
      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` имеет значение null.
  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-адрес сайта в Studio › Аутентификация › Конфигурация.

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>
  )
}

Письма с подтверждением и магической ссылкой ссылаются на <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>
  )
}
#
Тест

6. Попробуйте локально

В локальном стеке электронные письма захватываются Mailpit, а не отправляются. Откройте его почтовый ящик с помощью kubectl -n aurabase-dev port-forward svc/aurabase-mailpit 8025:8025, затем http://localhost:8025. Добавьте источник вашего приложения (например, http://localhost:3000/*) в Studio › Аутентификация › Конфигурация › URL-адреса перенаправления и установите для него URL-адрес сайта.

Информация

proxy.ts перенаправляет только по маршрутам, перечисленным в PROTECTED; страница панели мониторинга снова проверяет пользователя с помощью getUser(), поскольку прокси сам по себе не является границей авторизации.

Последнее обновление · 25 окт. 2026 г.