Appearance
Authentication
Auth Flow Overview
Sistem auth menggunakan better-auth dengan Drizzle adapter, running di Cloudflare Workers. Ada 3 metode login dan 4 role yang saling eksklusif.
Stack
- better-auth 1.6.9 (latest stable)
- Drizzle adapter with SQLite provider
- Cloudflare Workers runtime
Login Methods
1. Google OAuth
User klik "Login dengan Google" → redirect ke Google consent → callback ke /api/auth/callback/google → session cookie di-set → redirect ke app.
Setup:
- Google Client ID dan Client Secret disimpan sebagai Cloudflare secrets.
- Redirect URI harus didaftarkan di Google Console:
https://<domain>/api/auth/callback/google. BETTER_AUTH_URLharus di-set ke domain aplikasi (e.g.,https://xprivate.id) untuk menghindariredirect_uri_mismatcherror.
Frontend:
ts
// apps/web/src/lib/auth.ts
import { createAuthClient } from "better-auth/svelte";
export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_API_URL || undefined,
});svelte
<script>
import { authClient } from "$lib/auth";
const session = authClient.useSession();
</script>
<button onclick={() => authClient.signIn.social({ provider: "google" })}>
Masuk dengan Google
</button>2. Email / Password
User input email + password → /api/auth/sign-in/email → better-auth verify dengan scrypt → session cookie di-set.
Security:
- Password di-hash dengan scrypt via
node:crypto.scrypt. nodejs_compatflag diaktifkan di wrangler config.- Tidak pakai bcrypt atau argon2 (native bindings tidak work di Workers).
3. Magic Link (Email OTP)
User input email → klik "Kirim Magic Link" → Resend kirim email dengan link → user klik link → auto-login → session cookie di-set.
Setup:
- Resend API key disimpan sebagai Cloudflare secret.
- Email template dikelola di
@packages/email.
Role System
4 Roles
| Role | Deskripsi | Dibuat oleh |
|---|---|---|
student | Student — bisa create order, confirm sessions | Self-registration |
teacher | Teacher — bisa confirm sessions, submit presence | Self-registration (perlu admin verification) |
admin | Backoffice staff — full admin access kecuali invite | Owner invitation only |
owner | Platform owner — full access + bisa invite admins | Manual seed/setup |
Role Rules
- Mutual exclusion:
admindanownertidak bisa jadistudentatauteacher. Sebaliknya,studentdanteachertidak bisa jadiadminatauowner. - Owner invite: Hanya
owneryang bisa invite admin baru via endpointPOST /admin/v1/inviteAdmin. - Default role: Registrasi baru default ke
student. Teacher yang register mungkin perlu approval admin (configurable).
Middleware Chain
oRPC menggunakan middleware chain untuk progressive context enrichment:
| Procedure | Middleware Chain | Use For |
|---|---|---|
publicProcedure | base | Public routes (health, landing data) |
authedProcedure | base.use(withPassiveAuth).use(requireAuth) | Any logged-in user |
studentProcedure | authedProcedure.use(requireStudent) | Role = student |
teacherProcedure | authedProcedure.use(requireTeacher) | Role = teacher |
adminProcedure | authedProcedure.use(requireAdminOrOwner) | Role = admin atau owner |
ownerProcedure | authedProcedure.use(requireOwner) | Role = owner only |
Session Strategy
- better-auth menggunakan database sessions (D1-backed).
- Session cookie name:
glp.session_token - Session middleware resolve passively dulu, then require auth untuk protected routes.
Bearer Token (Non-Browser / API Testing)
Untuk non-browser clients dan OpenAPI testing, better-auth Bearer plugin mengizinkan autentikasi via Authorization: Bearer <token> header. Bearer token di-obtain dari response header set-auth-token setelah sign-in.
JWT Plugin
JWT plugin menyediakan:
- Endpoint
/api/auth/token— generate JWT token dari session aktif - Endpoint
/api/auth/jwks— public key set untuk verifikasi JWT eksternal - Table
jwksdi D1 untuk menyimpan key pair
JWT tidak menggantikan database session — digunakan untuk services yang memerlukan JWT atau testing dari OpenAPI client.
Flow:
- Sign-in via Google/Email/Magic Link → dapat session cookie
- Panggil
GET /api/auth/tokendengan session cookie → dapat JWT - Gunakan
Authorization: Bearer <jwt>untuk API calls selanjutnya
2FA (Post-MVP)
- better-auth support 2FA via TOTP plugin.
- Jangan implementasikan di MVP. Schema harus allow future addition tapi tidak ada UI atau API untuk itu dulu.
Env Variables
| Variable | Purpose |
|---|---|
BETTER_AUTH_URL | Base URL aplikasi (e.g., https://xprivate.id) |
BETTER_AUTH_SECRET | Secret untuk session signing |
GOOGLE_CLIENT_ID | Google OAuth client ID |
GOOGLE_CLIENT_SECRET | Google OAuth client secret |
RESEND_API_KEY | Resend API key untuk email |
RESEND_FROM_EMAIL | From address untuk email (e.g. noreply@xprivate.id) |
Decisions
Scrypt over bcrypt/argon2
bcrypt dan argon2 memerlukan native bindings yang tidak work di Cloudflare Workers. node:crypto.scrypt dengan nodejs_compat flag sudah verified working dan secure.
Database sessions sebagai default
Database sessions lebih aman untuk platform ini karena:
- Session bisa di-revoke secara instan (penting untuk admin actions).
- Tidak perlu handle JWT refresh complexity.
- D1 adalah edge database — latency session lookup minimal.
JWT hanya digunakan untuk specific use cases: non-browser clients, external service integration, dan OpenAPI testing.