Skip to content

Authentication

Auth system kita pakai better-auth 1.6 dengan Drizzle adapter untuk Cloudflare D1. Setup-nya pure di Cloudflare Workers tanpa backend tambahan.

Auth Methods

Google OAuth

User bisa login/register pakai Google account. Client ID dan client secret diambil dari Worker env bindings:

ts
socialProviders: {
  google: {
    clientId: env.GOOGLE_CLIENT_ID,
    clientSecret: env.GOOGLE_CLIENT_SECRET,
  },
}

Email / Password

User bisa signup dengan email dan password. Password di-hash pakai scrypt via node:crypto.scrypt:

ts
async function hashPassword(password: string): Promise<string> {
  const salt = randomBytes(16).toString("hex");
  const derivedKey = await scryptAsync(password, salt, 64);
  return `${salt}:${derivedKey.toString("hex")}`;
}

Verifikasi pakai timingSafeEqual untuk constant-time comparison.

Role System

3 Role Sederhana

RoleAkses
memberData sendiri doang
adminBisa akses data user lain untuk moderation
ownerSama kayak admin

Config

Role di-define via additionalFields di better-auth config:

ts
additionalFields: {
  role: {
    type: "string",
    defaultValue: "member",
    required: false,
  },
}

Middleware Check

Role check di oRPC middleware — simple string comparison:

ts
if (user.role !== "admin" && user.role !== "owner") {
  throw new ORPCError("FORBIDDEN");
}

Tidak ada ACL system atau permission matrix. Kalau suatu saat butuh lebih granular, baru refactor.

Session Management

better-auth pakai cookie-based sessions secara default. Session token disimpan di browser, di-validate setiap request.

Session Flow

User login → better-auth create session → set cookie → subsequent requests carry cookie → middleware validate session

Get Session di API

ts
const session = await authInstance.api.getSession({
  headers: request.headers,
});

Auth Routes

Semua auth routes di-handle oleh better-auth handler di apps/api/src/index.ts:

ts
if (url.pathname.startsWith("/api/auth/")) {
  return authInstance.handler(request);
}

Endpoint yang tersedia otomatis:

  • POST /api/auth/sign-in/email — login dengan email
  • POST /api/auth/sign-up/email — register dengan email
  • GET /api/auth/sign-in/google — initiate Google OAuth
  • GET /api/auth/callback/google — Google OAuth callback
  • GET /api/auth/get-session — get current session
  • POST /api/auth/sign-out — logout

Drizzle Adapter

Import Path (better-auth 1.6)

ts
// ✅ Benar
import { drizzleAdapter } from "better-auth/adapters/drizzle";

// ❌ Salah
import { drizzleAdapter } from "better-auth/adapters/drizzle-adapter";
import { drizzleAdapter } from "@better-auth/drizzle-adapter";

Schema

Auth schema (user, session, account, verification) ada di packages/db/src/schema/auth.ts. Drizzle adapter akan otomatis create/migrate tabel-tabel ini.

Database Provider

ts
database: drizzleAdapter(db, {
  provider: "sqlite",
  schema,
}),

baseURL Requirement

better-auth 1.6 butuh baseURL di config. Kalau tidak ada, muncul warning dan OAuth callback bisa gagal:

ts
baseURL: env.BETTER_AUTH_URL || "http://localhost:8787",

Security Notes

  • Password selalu di-hash — tidak pernah store plain text.
  • Session cookie HttpOnly dan Secure (di production).
  • CORS header di-set manual di fetch handler (tidak ada CORS plugin di oRPC 1.14).
  • OAuth redirect URL wajib didaftarkan di Google Console.

References