Docs/modules/Authentication

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/false

Protecting 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 accounts
  • sessions - Active sessions
  • accounts - OAuth provider connections
  • verifications - Email verification tokens

Adding OAuth Providers

  1. Create OAuth app in provider's developer console
  2. Add credentials to .env.local
  3. 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");
        },
      },
    },
  },
});