Menu

Nexty Authorization Feature Usage Guide

  • Core Library: Better-auth
  • Database: drizzle adapter, tables: user, session, account, verification
  • Supported Features:
    • Login Methods: Google, GitHub, Email Magic Link, Google One Tap
    • Security Enhancement: Cloudflare Turnstile CAPTCHA
    • Email Service: Welcome email for new user registration
    • Role & Access Control: user.role (default user, supports admin), encapsulated getSession(), isAdmin() and AuthGuard
    • User Source Tracking: Record user source based on access link parameters
    • User Blocking: Admins can easily block risky users to protect system security
    • Login Method Recording: Locally record user's last login method for faster login completion

Required Environment Variables

  • Basic:
    • NEXT_PUBLIC_SITE_URL (for auth callback base domain)
    • BETTER_AUTH_SECRET (server secret key)
  • Social Login:
    • Google: NEXT_PUBLIC_GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
    • GitHub: NEXT_PUBLIC_GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
  • CAPTCHA (Turnstile):
    • NEXT_PUBLIC_TURNSTILE_SITE_KEY (frontend)
    • TURNSTILE_SECRET_KEY (server)

Optional: Adjust trustedOrigins (lib/auth/index.ts) to match your deployment domain.

Server-side Usage

Reading Session & Protecting APIs

// Any Server Component / Route Handler / Server Action
import { getSession } from "@/lib/auth/server";
 
export async function SomeServerActions(req: Request) {
  const session = await getSession()
  const user = session?.user;
  if (!user) return actionResponse.unauthorized();
 
  // session.user: { id, email, role, ... }
  return actionResponse.success({});
}

Role Validation (Admin)

import { isAdmin } from "@/lib/auth/server";
 
export async function SomeAdminServerActions() {
  if (!(await isAdmin())) return actionResponse.forbidden('Admin privileges required.')
  // Admin logic
}

Page-level Guard (Synchronous Redirect)

// Component: components/auth/AuthGuard.tsx already encapsulated
import { AuthGuard } from "@/components/auth/AuthGuard";
 
export default async function LoginRequiredLayout() {
  return (
    <AuthGuard>
      {/* Content visible only to logged-in users */}
    </AuthGuard>
  );
}
// Component: components/auth/AuthGuard.tsx already encapsulated
import { AuthGuard } from "@/components/auth/AuthGuard";
 
export default async function AdminLayout() {
  return (
    <AuthGuard role="admin">
      {/* Content visible only to admins */}
    </AuthGuard>
  );
}

Client-side Usage

Getting Session & Login Status

import { authClient } from "@/lib/auth/auth-client";
 
// React client component
const { data: session, isPending } = authClient.useSession();

Social Login (Google/GitHub)

await authClient.signIn.social(
  {
    provider: "google", // or "github"
    callbackURL: window.location.origin, // where to redirect after login
    errorCallbackURL: "/redirect-error",
  },
  {
    onRequest: () => {/* loading UI */},
    onSuccess: (ctx) => {/* logged in */},
    onError: (ctx) => {/* error handling */},
  }
);
import { Turnstile } from "@marsidev/react-turnstile";
 
// 1) Frontend gets token
<Turnstile
  siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
  onSuccess={(token) => setCaptchaToken(token)}
/>
 
// 2) Call magic link login with CAPTCHA
await authClient.signIn.magicLink({
  email, // User input email
  name: "optional",
  callbackURL: window.location.origin,
  errorCallbackURL: "/redirect-error",
  fetchOptions: {
    headers: { "x-captcha-response": captchaToken }, // Key: pass to server
  },
});

Google One Tap (No Username/Password Input)

import { authClient } from "@/lib/auth/auth-client";
 
await authClient.oneTap({
  fetchOptions: {
    onSuccess: () => window.location.reload(),
    onError: (ctx) => console.error(ctx.error),
  },
  onPromptNotification: (note) => console.log(note),
});

Sign Out

await authClient.signOut({
  fetchOptions: {
    onSuccess: () => router.refresh(),
  },
});

Last Used Login Method

const lastLogin = authClient.getLastUsedLoginMethod(); // "google" | "github" | "email" | undefined

Role & Account Management

User table roles: 'user' | 'admin', default new registration is user.

Method to promote a user to admin:

Open the database user table, find the user you want to set as admin, and set role to admin.

Set Admin Account

Now re-login with the admin account, and you can see the admin directory.

Admin Directory

Business Hooks in Auth Flow (Built-in)

  • After user creation (databaseHooks.user.create.after):
    • Read referral code from Cookie referral_source and write to user.referral
    • Send welcome email
  • You can continue to extend these hooks in lib/auth/index.ts (such as risk control, audit logs, etc.).

Common Issues & Troubleshooting

  • Redirected to wrong domain after login
    • Set BETTER_AUTH_URL or NEXT_PUBLIC_SITE_URL to correct site address.
    • For cross-domain callbacks, supplement trustedOrigins.
  • Turnstile verification failed
    • Frontend ensure passing x-captcha-response header; backend TURNSTILE_SECRET_KEY required.
  • Social login error
    • Check corresponding CLIENT_ID/SECRET; add callback domain/path to whitelist in platform dashboard.
  • Session retrieval returns empty
    • Server must pass request headers through headers(), getSession() already encapsulated; confirm cookies are not blocked by cross-domain or SameSite policy.

Reference Files

  • Configuration & plugins: lib/auth/index.ts
  • Client SDK: lib/auth/auth-client.ts
  • Server encapsulation: lib/auth/server.ts (getSession, isAdmin)
  • Route guard component: components/auth/AuthGuard.tsx
  • Login form example: components/auth/LoginForm.tsx
  • One Tap: components/auth/GoogleOneTap.tsx
  • User menu/logout: components/header/UserInfo.tsx