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,
});

Web frontend (soulmapatlas.app) dan API (api.soulmapatlas.app) share session cookie via cookie domain config:

ts
cookie: {
  secure: true,
  sameSite: "lax",
  domain: env.BETTER_AUTH_COOKIE_DOMAIN, // e.g., "soulmapatlas.app"
}

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",

Secret dan Trusted Origins

Secret

secret wajib di-set untuk session encryption. Kalau tidak di-set, better-auth auto-generate dan session bakal invalidate tiap deploy:

ts
secret: env.BETTER_AUTH_SECRET,

Trusted Origins

Untuk security, better-auth 1.6 butuh whitelist origin yang boleh hit auth endpoints:

ts
trustedOrigins: ["https://soulmapatlas.app", "https://api.soulmapatlas.app"],

Bisa di-set via env BETTER_AUTH_TRUSTED_ORIGIN (comma-separated).

Environment Variables

VariableScopeDescription
BETTER_AUTH_SECRETSecretSession encryption key. Wajib di production.
BETTER_AUTH_URLVarBase URL API. Dev: http://localhost:8787, Prod: https://api.soulmapatlas.app
BETTER_AUTH_COOKIE_DOMAINVarCookie domain untuk cross-subdomain. Prod: soulmapatlas.app
BETTER_AUTH_TRUSTED_ORIGINVarComma-separated trusted origins.
GOOGLE_CLIENT_IDVarGoogle OAuth client ID.
GOOGLE_CLIENT_SECRETSecretGoogle OAuth client secret.

Local Development

Copy template secrets:

bash
cp apps/api/.dev.vars.example apps/api/.dev.vars
# Edit apps/api/.dev.vars dengan value yang sesuai

.dev.vars sudah di-.gitignore — tidak akan ter-commit.

Production

Set secrets via Wrangler CLI:

bash
wrangler secret put BETTER_AUTH_SECRET --env production
wrangler secret put GOOGLE_CLIENT_SECRET --env production

Vars non-sensitive sudah di-config di wrangler.jsonc per environment.

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