Integrate Better Auth and Google One Tap with Hono and Svelte
Published at Oct 9, 2025
This guide demonstrates how to integrate Better Auth with Hono in a SvelteKit application, including Google One Tap sign-in functionality. The stack uses Cloudflare Workers for deployment and Drizzle ORM with Cloudflare D1 for the database.
Prerequisites
Before starting, ensure you have:
SvelteKit Project: Create a new SvelteKit project with the Cloudflare Workers adapter
pnpm create cloudflare@latest my-app --framework=svelteGoogle OAuth Credentials:
- Visit Google Cloud Console
- Create an OAuth 2.0 Client ID for a web application
- Add
http://localhost:5173/api/auth/callback/googleto Authorized redirect URIs - Note down your Client ID and Client Secret
Environment Variables: Create a
.dev.varsfile in your project root (weβll populate this later)
Installation
Install the required dependencies:
# Core dependencies
pnpm add better-auth drizzle-orm hono @hono/valibot-validator valibot
# Development dependencies
pnpm add -D better-sqlite3 drizzle-kit @types/better-sqlite3 Note: better-sqlite3 is only used during development to generate the Better Auth schema. Production uses Cloudflare D1.
Project Structure
Create the following directory structure:
.
βββ src/
β βββ lib/
β β βββ server/
β β βββ api/
β β β βββ index.ts
β β βββ better-auth/
β β β βββ index.ts
β β β βββ options.ts
β β βββ db/
β β βββ schema.ts
β β βββ better-auth-schema.ts
β βββ routes/
β β βββ api/
β β βββ [...path]/
β β βββ +server.ts
β βββ app.d.ts
βββ better-auth.config.ts
βββ temp-betterauth.db
βββ .dev.vars Configuration Files
1. TypeScript Types
Update src/app.d.ts to include Cloudflare environment bindings:
// src/app.d.ts
import type { RequestIdVariables } from "hono/request-id";
declare global {
namespace App {
interface Platform {
env: Cloudflare.Env;
cf: CfProperties;
ctx: ExecutionContext;
}
interface Api {
Bindings: Cloudflare.Env;
Variables: RequestIdVariables;
}
}
namespace Cloudflare {
interface Env {
DATABASE_URL: string;
BASE_PATH: string;
BETTER_AUTH_URL: string;
BETTER_AUTH_SECRET: string;
GOOGLE_CLIENT_ID: string;
GOOGLE_CLIENT_SECRET: string;
ASSETS: Fetcher;
AUTH_DB: D1Database;
}
}
}
export {}; 2. Environment Variables
Create .dev.vars with your configuration:
BASE_PATH=http://localhost:5173
BETTER_AUTH_URL=http://localhost:5173
BETTER_AUTH_SECRET=your-secret-key-here
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret Important: Generate a secure random string for BETTER_AUTH_SECRET. You can use:
openssl rand -base64 32 3. Better Auth Schema Generator
Create better-auth.config.ts (used only for schema generation):
// better-auth.config.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
import * as schema from './src/lib/server/db/schema';
const sqlite = new Database('./temp-betterauth.db');
const db = drizzle(sqlite, { schema });
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
}),
}); Create an empty SQLite database file:
touch temp-betterauth.db 4. Better Auth Options
Define your Better Auth configuration in src/lib/server/better-auth/options.ts:
// src/lib/server/better-auth/options.ts
import type { BetterAuthOptions } from "better-auth";
export const betterAuthOptions: BetterAuthOptions = {
/**
* Application name displayed in authentication flows
*/
appName: 'My Awesome App',
/**
* Base path for Better Auth endpoints
* @default "/api/auth"
*/
basePath: '/api/auth',
/**
* Session configuration
*/
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day
},
}; 5. Better Auth Instance
Create the main Better Auth instance in src/lib/server/better-auth/index.ts:
// src/lib/server/better-auth/index.ts
import { drizzle } from "drizzle-orm/d1";
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { betterAuth } from 'better-auth';
import { bearer, oneTap } from "better-auth/plugins";
import { betterAuthOptions } from './options';
import * as schema from "../db/better-auth-schema";
export const auth = (env: Cloudflare.Env): ReturnType<typeof betterAuth> => {
const db = drizzle(env.AUTH_DB, { schema });
return betterAuth({
...betterAuthOptions,
database: drizzleAdapter(db, { provider: 'sqlite' }),
baseURL: env.BETTER_AUTH_URL,
secret: env.BETTER_AUTH_SECRET,
/**
* Enable email/password authentication
*/
emailAndPassword: {
enabled: true,
requireEmailVerification: false, // Set to true in production
},
/**
* Social authentication providers
*/
socialProviders: {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
}
},
/**
* Better Auth plugins
*/
plugins: [
bearer(), // Bearer token authentication
oneTap(), // Google One Tap
],
/**
* Trusted origins for CORS
*/
trustedOrigins: [
env.BASE_PATH,
],
});
}; Hono API Setup
1. Create API Router
Set up your Hono router in src/lib/server/api/index.ts:
// src/lib/server/api/index.ts
import { Hono } from 'hono';
import { trimTrailingSlash } from 'hono/trailing-slash';
import { logger } from 'hono/logger';
import { prettyJSON } from 'hono/pretty-json';
import { auth } from "$lib/server/better-auth";
const router = new Hono<App.Api>()
.use('*', trimTrailingSlash())
.use(logger())
.use('*', prettyJSON())
.on(["POST", "GET"], "/auth/*", (c) => auth(c.env).handler(c.req.raw));
export const api = new Hono<App.Api>().route('/api', router); 2. Create SvelteKit Endpoint
Connect Hono to SvelteKit in src/routes/[...path]/+server.ts:
// src/routes/[...path]/+server.ts
import { api } from '$lib/server/api';
import type { RequestHandler } from "./$types";
const handler: RequestHandler = ({ request, platform }) => {
return api.fetch(request, platform?.env, platform?.ctx);
};
export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const DELETE = handler;
export const PATCH = handler; Database Setup
1. Generate Better Auth Schema
Run the Better Auth CLI to generate the database schema:
pnpm dlx @better-auth/cli@latest generate --config ./better-auth.config.ts --output ./src/lib/server/db/better-auth-schema.ts This creates the necessary tables in src/lib/server/db/better-auth-schema.ts.
you might face this error Error: Could not locate the bindings file.
Solution
you need to update your package.json under pnpm to this
{
...,
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"better-sqlite3"
]
} Then delete your node_modules and you good to go
2. Create D1 Database
Create your Cloudflare D1 database:
pnpm wrangler d1 create auth-db Update your wrangler.jsonc with the database binding:
{
"d1_databases": [
{
"binding": "AUTH_DB",
"database_id": "74afa6fa-30aa-4279-953a-abcd",
"database_name": "auth",
"migrations_dir": "auth-migrations"
}
]
} 3. Run Migrations
Apply the schema to your D1 database:
wrangler d1 migrations apply "auth" Client Setup (Frontend)
Create a Better Auth client in your SvelteKit app:
// src/lib/auth-client.ts
import { createAuthClient } from "better-auth/svelte";
import { oneTapClient } from "better-auth/client/plugins";
export const authClient = (data) => createAuthClient({
baseURL: data.basePath,
plugins: [
oneTapClient({
clientId: data.googleClientId,
// Optional client configuration:
autoSelect: false,
cancelOnTapOutside: true,
context: "signin",
additionalOptions: {
// Any extra options for the Google initialize method
},
// Configure prompt behavior and exponential backoff:
promptOptions: {
baseDelay: 1000, // Base delay in ms (default: 1000)
maxAttempts: 5 // Maximum number of attempts before triggering onPromptNotification (default: 5)
}
})
]
});
export const { signIn, signUp, signOut, useSession } = authClient; Usage Example
Hereβs a simple login page component:
<!-- src/routes/login/+page.svelte -->
<script lang="ts">
import { authClient } from '$lib/auth-client';
async function handleGoogleSignIn() {
await authClient.signIn.social({
provider: 'google',
});
}
</script>
<div class="login-container">
<h1>Sign In</h1>
<!-- Google One Tap will appear automatically -->
<button on:click={handleGoogleSignIn}>
Sign in with Google
</button>
</div> Deployment
When deploying to Cloudflare Workers:
Set environment variables in Cloudflare dashboard or via Wrangler:
pnpm wrangler secret put BETTER_AUTH_SECRET pnpm wrangler secret put GOOGLE_CLIENT_ID pnpm wrangler secret put GOOGLE_CLIENT_SECRETUpdate
BETTER_AUTH_URLandBASE_PATHto your production domainAdd your production domain to Google OAuth authorized redirect URIs
Deploy:
pnpm run deploy
Troubleshooting
Issue: βDatabase not foundβ error
- Solution: Ensure AUTH_DB binding is correctly configured in
wrangler.toml
Issue: Google One Tap not appearing
- Solution: Verify your domain is authorized in Google Cloud Console and the client ID matches
Issue: CORS errors
- Solution: Add your frontend domain to
trustedOriginsin Better Auth config
References
- Better Auth Documentation
- Hono Documentation
- Drizzle ORM Documentation
- better-sqlite3 GitHub Issue #146
Conclusion
You now have a fully functional authentication system with Google One Tap integration running on Cloudflare Workers. This setup provides a secure, scalable solution with minimal latency thanks to Cloudflareβs edge network.
For production use, remember to:
- Enable email verification
- Set up proper error handling
- Implement rate limiting
- Configure secure session management
- Add monitoring and logging
