Authentication
Configure Better Auth, email/password, magic links, Google, GitHub, and server-side auth helpers.
ShipNext uses Better Auth as the authentication framework. It supports email/password login, magic links, social login, session management, and server-side helpers.
Environment variables
Set these values in .env.local:
| Variable | Description |
|---|---|
BETTER_AUTH_URL | Better Auth root URL. Keep it equal to NEXT_PUBLIC_APP_URL. |
BETTER_AUTH_SECRET | Signing secret for sessions and tokens. |
BETTER_AUTH_URL=http://localhost:3000
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=your-secret-keyGoogle login
Create OAuth credentials
Open Google Cloud Console, choose or create a project, then click Create credentials and select OAuth client ID.
Configure the web application
Choose Web application.
- Name: Any name.
- Authorized JavaScript origins: Your domain, for example
https://your-domain.comorhttp://localhost:3000. - Authorized redirect URIs:
http://localhost:3000/api/auth/callback/googlelocally, orhttps://your-domain.com/api/auth/callback/googlein production.
The /api/auth/callback/google path is fixed by Better Auth.
Add the environment variables
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secretEnable Google in website config
const authConfig = {
enableEmailVerification: true,
enableCredentialLogin: true,
enableMagicLinkLogin: true,
enableGoogleLogin: true,
enableGitHubLogin: true,
captcha: {
enable: false,
scenarios: {
signIn: false,
signUp: false,
},
},
};GitHub login
Create an OAuth App
Open GitHub Developer Settings, then click New OAuth app.
Fill in callback settings
- Application name: Any name.
- Homepage URL:
http://localhost:3000locally or your production domain. - Authorization callback URL:
http://localhost:3000/api/auth/callback/githublocally, orhttps://your-domain.com/api/auth/callback/githubin production.
The /api/auth/callback/github path is fixed by Better Auth.
Generate a client secret
After registering the app, click Generate a new client secret.
Add environment variables
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secretEnable GitHub in website config
const authConfig = {
enableEmailVerification: true,
enableCredentialLogin: true,
enableMagicLinkLogin: true,
enableGoogleLogin: true,
enableGitHubLogin: true,
captcha: {
enable: false,
scenarios: {
signIn: false,
signUp: false,
},
},
};Email login
Email registration and sign-in require an email provider. ShipNext defaults to Resend; see Email.
ShipNext enables email/password login and email verification by default:
const authConfig = {
enableEmailVerification: true,
enableCredentialLogin: true,
enableMagicLinkLogin: true,
enableGoogleLogin: true,
enableGitHubLogin: true,
captcha: {
enable: false,
scenarios: {
signIn: false,
signUp: false,
},
},
};Email verification is strongly recommended in production to reduce abuse and account takeover risk.
Magic links
Magic links are passwordless login links sent by email. They are enabled by enableMagicLinkLogin.
const authConfig = {
enableMagicLinkLogin: true,
// ...
};Magic links apply to login. Registration still uses email activation when verification is enabled.
Client usage
Common client-side operations are exposed from src/modules/auth/client.ts.
Read session state
import { useAuth } from "@/modules/auth/client";
const { session } = useAuth();
if (session.loading) {
return <div>Loading...</div>;
}Email sign-in
import { useAuth } from "@/modules/auth/client";
const { signIn } = useAuth();
await signIn.email({ email, password });Sign out
import { useAuth } from "@/modules/auth/client";
const { signOut } = useAuth();
await signOut();Magic link
import { useAuth } from "@/modules/auth/client";
const { signIn } = useAuth();
await signIn.magicLink({ email });Social login
import { useAuth } from "@/modules/auth/client";
const { signIn } = useAuth();
await signIn.social({ provider: "google" });Server usage
Server helpers live in src/modules/auth/server.ts.
Protect an API route
import { getUser } from "@/modules/auth/server";
import { unauthorizedResponse } from "@/core/response/responses";
export async function GET(request: NextRequest) {
const user = await getUser(request);
if (!user) {
return unauthorizedResponse();
}
return NextResponse.json({ user });
}Require admin
import { requireAdmin } from "@/modules/auth/server";
export async function POST(request: NextRequest) {
const user = await requireAdmin(request);
// other code
}