A modern, full-stack authentication system built with React, Better Auth, and deployed on Cloudflare infrastructure. Features email whitelisting, Google OAuth, and a beautiful glassmorphic UI.
- Clean, minimalist sign-in page with glassmorphism design
- Purple gradient background with smooth animations
- Responsive design that works on all devices
- Loading states and error handling with visual feedback
- Google OAuth integration via Better Auth
- Email whitelist system for controlled access
- Secure cookie-based sessions with
SameSite=Noneandsecureflags - Custom error handling and user-friendly error pages
- Cloudflare Workers for serverless API
- D1 SQLite database for data persistence
- Drizzle ORM for type-safe database operations
- Global edge network for low latency
- TypeScript for type safety across the stack
- Hot module replacement with Vite
- Automated database migrations with Drizzle Kit
- Environment-based configuration
BaGOuthApp/
โโโ ba-gouth-app/ # Frontend (React + Vite)
โ โโโ src/
โ โ โโโ routes/ # Page components
โ โ โ โโโ SignIn.tsx
โ โ โ โโโ AuthError.tsx
โ โ โ โโโ Blog.tsx
โ โ โโโ lib/
โ โ โโโ auth-client.ts # Better Auth client
โ โโโ package.json
โ
โโโ ba-gouth-apis/ # Backend (Cloudflare Workers + Hono)
โโโ src/
โ โโโ index.ts # API routes & auth configuration
โ โโโ schemas/ # Drizzle ORM schemas
โโโ migrations/ # Database migrations
โโโ package.json
- Bun or Node.js 18+
- Cloudflare account
- Google Cloud Console project with OAuth credentials
git clone https://github.com/Shisui-Genjutsu/React_BetterAuth_GoogleOAuth_Cloudflare_SQLite.git
cd React_BetterAuth_GoogleOAuth_Cloudflare_SQLitecd ba-gouth-apis
bun install # or npm installCreate a wrangler.jsonc file:
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable Google+ API
- Create OAuth 2.0 credentials:
- Authorized JavaScript origins:
http://localhost:5173,https://your-app.pages.dev - Authorized redirect URIs:
https://your-worker.workers.dev/api/auth/callback/google
- Authorized JavaScript origins:
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRETnpx wrangler d1 create ba-gouth-dbCopy the database ID to your wrangler.jsonc.
bun run db:generate # Generate migrations
bun run db:push # Apply to local DB
npx wrangler d1 execute ba-gouth-db --remote --file=./migrations/0000_*.sql
npx wrangler d1 execute ba-gouth-db --remote --file=./migrations/0001_*.sqlnpx wrangler d1 execute ba-gouth-db --remote --command="INSERT INTO allowed_emails (id, email, created_at) VALUES ('uuid-here', '[email protected]', datetime('now'))"bun run deploycd ../ba-gouth-app
bun install # or npm installCreate a .env file:
VITE_BETTER_AUTH_URL=https://your-worker.workers.devbun run devVisit http://localhost:5173/sign-in
bun run deployid: User ID (primary key)name: User's full nameemail: User's email addressemailVerified: Email verification statusimage: Profile picture URLcreatedAt: Account creation timestampupdatedAt: Last update timestamp
id: Session ID (primary key)expiresAt: Session expiration timetoken: Session tokenipAddress: User's IP addressuserAgent: Browser user agentuserId: Foreign key tousertable
id: Account ID (primary key)accountId: OAuth provider account IDproviderId: OAuth provider (e.g., "google")userId: Foreign key tousertableaccessToken: OAuth access tokenrefreshToken: OAuth refresh tokenexpiresAt: Token expiration time
id: Record ID (primary key)email: Whitelisted email addresscreated_at: Timestamp when email was added
- React 19 - UI library
- TypeScript - Type safety
- Vite - Build tool and dev server
- React Router 7 - Client-side routing
- Better Auth Client - Authentication SDK
- Cloudflare Workers - Serverless runtime
- Hono - Lightweight web framework
- Better Auth - Authentication framework
- Drizzle ORM - Type-safe database toolkit
- D1 - Cloudflare's SQLite database
The backend uses a custom Better Auth plugin to enforce email whitelisting:
const emailWhitelistPlugin = (db: any) => ({
id: "email-whitelist",
init: (instance: any) => {
return {
options: {
databaseHooks: {
user: {
create: {
async before(user: any) {
const email = user.email.toLowerCase()
const allowedUser = await db.select()
.from(schema.allowedEmails)
.where(eq(schema.allowedEmails.email, email))
.limit(1)
if (!allowedUser || allowedUser.length === 0) {
const error: any = new Error("unauthorized_email")
error.status = 403
throw error
}
return user
}
}
}
}
}
}
}
})Configured for cross-origin requests with credentials:
app.use(cors({
origin: (origin, c) => {
const allowedOrigins = [
"http://localhost:5173",
c.env.CLOUDFLARE_FRONTEND_BASE_URL
];
return origin && allowedOrigins.some(allowed => origin.includes(allowed)) ? origin : null;
},
allowMethods: ['POST', 'GET', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'cookie'],
credentials: true,
exposeHeaders: ["set-cookie"]
}))- Ensure your email is added to the
allowed_emailstable in D1 - Check that the email matches exactly (case-insensitive comparison is used)
- Verify
CLOUDFLARE_FRONTEND_BASE_URLis set correctly inwrangler.jsonc - Check that your frontend URL is in the
trustedOriginsarray
- Confirm D1 database is created and bound correctly
- Verify migrations have been applied to the remote database
- Check database ID in
wrangler.jsoncmatches your D1 database
- Ensure redirect URIs in Google Console match your worker URL exactly
- Check that
baseURLandbasePathare configured correctly in Better Auth
This project is open source and available under the MIT License.
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.
Bhaskar G
- GitHub: @Shisui-Genjutsu
Give a โญ๏ธ if this project helped you!
The application includes a dedicated error page for authentication failures with clear messaging and navigation options.
Built with โค๏ธ using React, Better Auth, and Cloudflare

{ "name": "ba-gouth-apis", "main": "src/index.ts", "compatibility_date": "2024-01-01", "vars": { "CLOUDFLARE_BASE_URL": "https://your-worker.workers.dev", "CLOUDFLARE_FRONTEND_BASE_URL": "https://your-app.pages.dev" }, "d1_databases": [ { "binding": "DB", "database_name": "ba-gouth-db", "database_id": "your-database-id" } ], "secrets": [ "GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET" ] }