mcpField
N
neon-auth-nextjs

neon-auth-nextjs

github.com/neondatabase开发1956 次安装2 天前更新
代码仓库

关于 neon-auth-nextjs

neon-auth-nextjs 是面向开发者的配置技能,用于在 Next.js App Router 应用中接入 @neondatabase/auth,无需数据库。内容涵盖服务端与客户端设置、路由保护和认证界面,并强调正确导入及会话刷新。

主要功能

  1. 01服务端与客户端认证设置
  2. 02路由处理器与中间件
  3. 03预构建认证界面
  4. 04Server Components 会话处理
  5. 05CSS 与主题配置

适用场景

  1. 01在 App Router 应用中接入 Neon Auth
  2. 02保护仪表板和账户路由
  3. 03构建登录与账户页面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/neondatabase/neon-js --skill neon-auth-nextjs

安装后在 Claude Code 中输入/neon-auth-nextjs确认已加载。

详细介绍

Next.js 的 Neon Auth

帮助开发者在 Next.js App Router 应用中设置 @neondatabase/auth(仅认证,不含数据库)。

适用场景

在以下情况下使用此技能:

  • 在 Next.js(App Router)中设置 Neon Auth
  • 用户在使用 Neon Auth 时提到“next.js”、“next”或“app router”
  • 仅设置认证(无需数据库)

重要规则

  1. 服务端与客户端导入:使用正确的导入路径
  2. 'use client' 指令:使用 Hook 的客户端组件必须添加此指令
  3. CSS 导入:二选一——使用 /ui/css 或 /ui/tailwind,绝不能同时使用
  4. onSessionChange:始终调用 router.refresh() 以更新 Server Components

重要导入

用途导入来源
统一服务端接口(createNeonAuth)@neondatabase/auth/next/server
客户端认证@neondatabase/auth/next
UI 组件@neondatabase/auth/react/ui
视图路径(静态参数)@neondatabase/auth/react/ui/server

注意:使用 @neondatabase/auth/next/server 中的 createNeonAuth() 获取统一的 auth 实例,该实例提供:

  • .handler() - API 路由处理器
  • .middleware() - 路由保护中间件
  • 所有 Better Auth 服务端方法(.signIn、.signUp、.getSession 等)

设置

1. 安装
npm install @neondatabase/auth
2. 环境变量(.env.local)
NEON_AUTH_BASE_URL=https://your-auth.neon.tech
NEON_AUTH_COOKIE_SECRET=your-secret-at-least-32-characters-long

重要:为生产环境生成安全密钥(32 个字符以上):

openssl rand -base64 32
3. 服务端设置(lib/auth/server.ts)

创建一个提供处理器、中间件和服务端方法的 auth 实例:

import { createNeonAuth } from '@neondatabase/auth/next/server';

export const auth = createNeonAuth({
  baseUrl: process.env.NEON_AUTH_BASE_URL!,
  cookies: {
    secret: process.env.NEON_AUTH_COOKIE_SECRET!,
    sessionDataTtl: 300,          // Optional: session data cache TTL in seconds (default: 300 = 5 min)
    domain: '.example.com',       // Optional: for cross-subdomain cookies
  },
});
4. API 路由(app/api/auth/[...path]/route.ts)
import { auth } from '@/lib/auth/server';

export const { GET, POST } = auth.handler();
5. 中间件(middleware.ts)
import { auth } from '@/lib/auth/server';

export default auth.middleware({
  loginUrl: '/auth/sign-in',
});

export const config = {
  matcher: ['/dashboard/:path*', '/account/:path*'],
};
6. 客户端(lib/auth/client.ts)
'use client';
import { createAuthClient } from '@neondatabase/auth/next';

export const authClient = createAuthClient();
7. Provider(app/providers.tsx)
'use client';
import { NeonAuthUIProvider } from '@neondatabase/auth/react/ui';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { authClient } from '@/lib/auth/client';

export function Providers({ children }: { children: React.ReactNode }) {
  const router = useRouter();

  return (
    <NeonAuthUIProvider
      authClient={authClient}
      navigate={router.push}
      replace={router.replace}
      onSessionChange={() => router.refresh()}
      redirectTo="/dashboard"
      Link={({href, children}) => <Link to={href}>{children}</Link>}
    >
      {children}
    </NeonAuthUIProvider>
  );
}
8. 布局(app/layout.tsx)
import { Providers } from './providers';
import '@neondatabase/auth/ui/css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
9. 认证页面(app/auth/[path]/page.tsx)
import { AuthView } from '@neondatabase/auth/react/ui';
import { authViewPaths } from '@neondatabase/auth/react/ui/server';

export function generateStaticParams() {
  return Object.values(authViewPaths).map((path) => ({ path }));
}

export default async function AuthPage({ params }: { params: Promise<{ path: string }> }) {
  const { path } = await params;
  return <AuthView pathname={path} />;
}

CSS 与样式

导入选项

不使用 Tailwind(预构建 CSS 包约 47 KB):

// app/layout.tsx
import '@neondatabase/auth/ui/css';

使用 Tailwind CSS v4(app/globals.css):

@import 'tailwindcss';
@import '@neondatabase/auth/ui/tailwind';

重要:绝不能同时导入两者,否则会导致样式重复。

深色模式

Provider 包含 next-themes。通过 defaultTheme 属性进行控制:

<NeonAuthUIProvider
  defaultTheme="system" // 'light' | 'dark' | 'system'
  // ...
>
自定义主题

在 globals.css 中覆盖 CSS 变量:

:root {
  --primary: hsl(221.2 83.2% 53.3%);
  --primary-foreground: hsl(210 40% 98%);
  --background: hsl(0 0% 100%);
  --foreground: hsl(222.2 84% 4.9%);
  --card: hsl(0 0% 100%);
  --card-foreground: hsl(222.2 84% 4.9%);
  --border: hsl(214.3 31.8% 91.4%);
  --input: hsl(214.3 31.8% 91.4%);
  --ring: hsl(221.2 83.2% 53.3%);
  --radius: 0.5rem;
}

.dark {
  --background: hsl(222.2 84% 4.9%);
  --foreground: hsl(210 40% 98%);
  /* ... dark mode overrides */
}

NeonAuthUIProvider 属性

完整配置选项:

<NeonAuthUIProvider
  // Required
  authClient={authClient}

  // Navigation (Next.js specific)
  navigate={router.push}        // router.push for navigation
  replace={router.replace}      // router.replace for redirects
  onSessionChange={() => router.refresh()} // Refresh Server Components!
  redirectTo="/dashboard"       // Where to redirect after auth
  Link={({href, children}) => <Link to={href}>{children}</Link>}                   // Next.js Link component

  // Social/OAuth Providers
  social={{
    providers: ['google'],
  }}

  // Feature Flags
  emailOTP={true}               // Enable email OTP sign-in
  emailVerification={true}      // Require email verification
  magicLink={false}             // Magic link (disabled by default)
  multiSession={false}          // Multiple sessions (disabled)

  // Credentials Configuration
  credentials={{
    forgotPassword: true,       // Show forgot password link
  }}

  // Sign Up Fields
  signUp={{
    fields: ['name'],           // Additional fields: 'name', 'username', etc.
  }}

  // Account Settings Fields
  account={{
    fields: ['image', 'name', 'company', 'age', 'newsletter'],
  }}

  // Organization Features
  organization={{}}             // Enable org features

  // Dark Mode
  defaultTheme="system"         // 'light' | 'dark' | 'system'

  // Custom Labels
  localization={{
    SIGN_IN: 'Welcome Back',
    SIGN_UP: 'Create Account',
    FORGOT_PASSWORD: 'Forgot Password?',
    OR_CONTINUE_WITH: 'or continue with',
  }}
>
  {children}
</NeonAuthUIProvider>

Server Components(RSC)

在 Server Component 中获取会话
// NO 'use client' - this is a Server Component
import { auth } from '@/lib/auth/server';

// Server components using `auth` methods must be rendered dynamically
export const dynamic = 'force-dynamic'

export async function Profile() {
  const { data: session } = await auth.getSession();

  if (!session?.user) return <div>Not signed in</div>;

  return (
    <div>
      <p>Hello, {session.user.name}</p>
      <p>Email: {session.user.email}</p>
    </div>
  );
}
带认证的路由处理器
// app/api/user/route.ts
import { auth } from '@/lib/auth/server';
import { NextResponse } from 'next/server';

export async function GET() {
  const { data: session } = await auth.getSession();

  if (!session?.user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  return NextResponse.json({ user: session.user });
}

Server Actions

Server Actions 使用来自 lib/auth/server.ts 的同一个 auth 实例:

登录 Action
// app/actions/auth.ts
'use server';
import { auth } from '@/lib/auth/server';
import { redirect } from 'next/navigation';

export async function signIn(formData: FormData) {
  const { error } = await auth.signIn.email({
    email: formData.get('email') as string,
    password: formData.get('password') as string,
  });

  if (error) {
    return { error: error.message };
  }

  redirect('/dashboard');
}

export async function signUp(formData: FormData) {
  const { error } = await auth.signUp.email({
    email: formData.get('email') as string,
    password: formData.get('password') as string,
    name: formData.get('name') as string,
  });

  if (error) {
    return { error: error.message };
  }

  redirect('/dashboard');
}

export async function signOut() {
  await auth.signOut();
  redirect('/');
}
可用的服务端方法

createNeonAuth() 返回的 auth 实例提供所有 Better Auth 服务端方法:

// Authentication
auth.signIn.email({ email, password })
auth.signUp.email({ email, password, name })
auth.signOut()
auth.getSession()

// User Management
auth.updateUser({ name, image })

// Organizations
auth.organization.create({ name, slug })
auth.organization.list()

// Admin (if enabled)
auth.admin.listUsers()
auth.admin.banUser({ userId })

客户端组件

会话 Hook
'use client';
import { authClient } from '@/lib/auth/client';

export function Dashboard() {
  const { data: session, isPending, error } = authClient.useSession();

  if (isPending) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!session) return <div>Not signed in</div>;

  return <div>Hello, {session.user.name}</div>;
}
客户端认证方法
'use client';
import { authClient } from '@/lib/auth/client';

// Sign in
await authClient.signIn.email({ email, password });

// Sign up
await authClient.signUp.email({ email, password, name });

// OAuth
await authClient.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
});

// Sign out
await authClient.signOut();

// Get session
const session = await authClient.getSession();

UI 组件

AuthView - 主要认证界面
import { AuthView } from '@neondatabase/auth/react/ui';

// Handles: sign-in, sign-up, forgot-password, reset-password, callback, sign-out
<AuthView pathname={path} />
条件渲染
import {
  SignedIn,
  SignedOut,
  AuthLoading,
  RedirectToSignIn,
} from '@neondatabase/auth/react/ui';

function MyPage() {
  return (
    <>
      <AuthLoading>
        <LoadingSpinner />
      </AuthLoading>

      <SignedIn>
        <Dashboard />
      </SignedIn>

      <SignedOut>
        <LandingPage />
      </SignedOut>

      {/* Auto-redirect if not signed in */}
      <RedirectToSignIn />
    </>
  );
}
UserButton
import { UserButton } from '@neondatabase/auth/react/ui';

function Header() {
  return (
    <header>
      <nav>...</nav>
      <UserButton />
    </header>
  );
}
账户管理
import {
  AccountSettingsCards,
  SecuritySettingsCards,
  SessionsCard,
  ChangePasswordCard,
  ChangeEmailCard,
  DeleteAccountCard,
  ProvidersCard,
} from '@neondatabase/auth/react/ui';
组织组件
import {
  OrganizationSwitcher,
  OrganizationSettingsCards,
  OrganizationMembersCard,
  AcceptInvitationCard,
} from '@neondatabase/auth/react/ui';

社交/OAuth 提供商

配置
<NeonAuthUIProvider
  social={{
    providers: ['google'],
  }}
>
以编程方式进行 OAuth
// Client-side
await authClient.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
});
支持的提供商

google、github、twitter、discord、apple、microsoft、facebook、linkedin、spotify、twitch、gitlab、bitbucket


中间件配置

基本受保护路由
import { auth } from '@/lib/auth/server';

export default auth.middleware({
  loginUrl: '/auth/sign-in',
});

export const config = {
  matcher: ['/dashboard/:path*', '/account/:path*', '/settings/:path*'],
};
自定义逻辑
import { auth } from '@/lib/auth/server';
import { NextResponse } from 'next/server';

export default auth.middleware({
  loginUrl: '/auth/sign-in',
});

账户页面设置

账户布局(app/account/[path]/page.tsx)
import {
  SignedIn,
  RedirectToSignIn,
  AccountSettingsCards,
  SecuritySettingsCards,
  SessionsCard,
  ChangePasswordCard,
} from '@neondatabase/auth/react/ui';

export default async function AccountPage({ params }: { params: Promise<{ path: string }> }) {
  const { path = 'settings' } = await params;

  return (
    <>
      <RedirectToSignIn />
      <SignedIn>
        {path === 'settings' && <AccountSettingsCards />}
        {path === 'security' && (
          <>
            <ChangePasswordCard />
            <SecuritySettingsCards />
          </>
        )}
        {path === 'sessions' && <SessionsCard />}
      </SignedIn>
    </>
  );
}

高级功能

匿名访问

为未认证用户启用基于 RLS 的数据访问:

// lib/auth/client.ts
'use client';
import { createAuthClient } from '@neondatabase/auth/next';

export const authClient = createAuthClient({
  allowAnonymous: true,
});
获取 JWT 令牌
const token = await authClient.getJWTToken();

// Use in API calls
const response = await fetch('/api/data', {
  headers: { Authorization: `Bearer ${token}` },
});
跨标签页同步

通过 BroadcastChannel 自动同步。在一个标签页中退出登录会使所有标签页都退出登录。

在 Server Components 中刷新会话

对于 Next.js,onSessionChange 回调至关重要:

<NeonAuthUIProvider
  onSessionChange={() => router.refresh()} // Refreshes Server Components!
  // ...
>

否则,登录或退出登录后,Server Components 不会更新。


错误处理

Server Actions
'use server';

export async function signIn(formData: FormData) {
  const { error } = await authServer.signIn.email({
    email: formData.get('email') as string,
    password: formData.get('password') as string,
  });

  if (error) {
    // Return error to client
    return { error: error.message };
  }

  redirect('/dashboard');
}
客户端组件
'use client';

const { error } = await authClient.signIn.email({ email, password });

if (error) {
  toast.error(error.message);
}
常见错误
错误原因
Invalid credentials电子邮件或密码错误
User already exists该电子邮件已注册
Email not verified需要验证
Session not found会话已过期或无效

常见问题 / 故障排除

登录后服务器组件未更新?

请确保在 provider 中设置了 onSessionChange={() => router.refresh()}:

<NeonAuthUIProvider
  onSessionChange={() => router.refresh()}
  // ...
>
匿名访问无法正常工作?

在数据库中向 anonymous 角色授予权限:

GRANT SELECT ON public.posts TO anonymous;
GRANT SELECT ON public.products TO anonymous;

并配置 RLS 策略:

CREATE POLICY "Anyone can read published posts"
  ON public.posts FOR SELECT
  USING (published = true);
中间件未保护路由?

检查你的 matcher 配置:

export const config = {
  matcher: [
    '/dashboard/:path*',
    '/account/:path*',
    // Add your protected routes here
  ],
};
OAuth 回调错误?

确保 API 路由已在 app/api/auth/[...path]/route.ts 正确设置:

import { auth } from '@/lib/auth/server';
export const { GET, POST } = auth.handler();
会话未持久化?
  1. 检查是否已启用 Cookie
  2. 确认 .env.local 中的 NEON_AUTH_BASE_URL 正确
  3. 确认已设置 NEON_AUTH_COOKIE_SECRET,且至少为 32 个字符
  4. 确保未在禁用 Cookie 的无痕模式下浏览
会话数据缓存无法正常工作?
  1. 确认 NEON_AUTH_COOKIE_SECRET 至少为 32 个字符
  2. 检查是否已将 cookies.secret 传入 createNeonAuth()
  3. 可选:配置 cookies.sessionDataTtl(默认值:300 秒)

性能说明

  • 会话数据缓存:使用带可配置 TTL 的 JWT 签名 session_data Cookie(默认:5 分钟)
    • 通过 cookies.sessionDataTtl 以秒为单位进行配置
    • 会话查询耗时低于 1 毫秒(<1ms)
    • 缓存未命中时自动回退到上游 /get-session
  • 请求去重:并发调用共享单个网络请求(冷启动速度提升 10 倍)
  • 服务器组件:使用 auth.getSession() 零 JS 获取会话
  • 跨标签页同步:通过 BroadcastChannel 实现,耗时 <50ms
  • Cookie 域名:可选配置 cookies.domain,以便在子域间共享 Cookie

评论

支持 Markdown