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
(defaultuser
, supportsadmin
), encapsulatedgetSession()
,isAdmin()
andAuthGuard
- 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
- Google:
- 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 */},
}
);
Email Magic Link Login (with Turnstile Verification)
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
.

Now re-login with the admin account, and you can see the 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 touser.referral
- Send welcome email
- Read referral code from Cookie
- 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
orNEXT_PUBLIC_SITE_URL
to correct site address. - For cross-domain callbacks, supplement
trustedOrigins
.
- Set
- Turnstile verification failed
- Frontend ensure passing
x-captcha-response
header; backendTURNSTILE_SECRET_KEY
required.
- Frontend ensure passing
- Social login error
- Check corresponding
CLIENT_ID/SECRET
; add callback domain/path to whitelist in platform dashboard.
- Check corresponding
- 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.
- Server must pass request headers through
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