Authentication
NextShip uses Better Auth for authentication and Casbin for role-based access control.
Features
- Email/password authentication
- OAuth providers (Google, GitHub)
- Magic link login
- Two-factor authentication (2FA)
- Session management
- Role-based access control (RBAC)
Configuration
The auth configuration is in src/lib/auth.ts:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
});Environment Variables
# Required
BETTER_AUTH_SECRET=your-random-secret
# OAuth Providers (optional)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=Getting the Current User
Server Components
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
export default async function Page() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
redirect("/login");
}
return <div>Welcome, {session.user.name}</div>;
}Client Components
"use client";
import { authClient } from "@/lib/auth-client";
export function UserProfile() {
const { data: session } = authClient.useSession();
if (!session) return null;
return <div>{session.user.email}</div>;
}Protected Routes
Using Layout Protection
// src/app/[locale]/(dashboard)/layout.tsx
import { requireAuth } from "@/lib/permissions";
export default async function DashboardLayout({ children }) {
await requireAuth(); // Redirects to login if not authenticated
return <DashboardShell>{children}</DashboardShell>;
}Using Permission Helpers
// src/lib/permissions.ts
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "@/i18n/navigation";
export async function requireAuth() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
redirect("/login");
}
return session;
}
export async function requireAdmin() {
const session = await requireAuth();
const isAdmin = await checkPermission(session.user.id, "admin", "access");
if (!isAdmin) {
throw new Error("Admin access required");
}
return session;
}
export async function requirePermission(object: string, action: string) {
const session = await requireAuth();
const hasPermission = await checkPermission(session.user.id, object, action);
if (!hasPermission) {
throw new Error("Permission denied");
}
return session;
}Role-Based Access Control (RBAC)
NextShip uses Casbin for flexible RBAC. See the RBAC documentation for details.
Role Hierarchy
superadmin
└── admin
└── user
- superadmin: Full access to everything
- admin: Access to admin dashboard, user management
- user: Basic access to own resources
Checking Roles
// In Server Actions or API routes
import { getUserRoles, checkUserRole } from "@/server/actions/permission";
// Get all roles for a user
const roles = await getUserRoles(userId);
// Returns: ["user", "admin"]
// Check if user has specific role
const isAdmin = await checkUserRole("admin");
// Returns: true/falseProtecting Admin Pages
// src/app/[locale]/(dashboard)/users/page.tsx
import { requireAdmin } from "@/lib/permissions";
export default async function UsersPage() {
await requireAdmin(); // Only admins can access
const users = await getUsers();
return <UsersTable users={users} />;
}Database Schema
Better Auth uses these tables (defined in src/lib/db/schema.ts):
users- User accountssessions- Active sessionsaccounts- OAuth provider connectionsverifications- Email verification tokens
Adding OAuth Providers
- Create OAuth app in provider's developer console
- Add credentials to
.env.local - Add provider to
src/lib/auth.ts
See Better Auth docs for all available providers.
Automatic Role Assignment
New users automatically get the "user" role via Better Auth database hooks:
// src/lib/auth.ts
export const auth = betterAuth({
// ...
databaseHooks: {
user: {
create: {
after: async (user) => {
// Assign default "user" role
await assignUserRole(user.id, "user");
},
},
},
},
});