Hono
This guide walks through setting up authentication in a Hono application using ideal-auth. By the end, you will have working login, registration, logout, route protection via middleware, and access to the current user in your route handlers. Hono works across runtimes, including Node.js, Bun, Cloudflare Workers, Deno, and more.
Installation
Section titled “Installation”-
Install ideal-auth
Terminal window bun add ideal-auth -
Set the session secret
Add a secret to your environment. It must be at least 32 characters.
.env IDEAL_AUTH_SECRET="at-least-32-characters-long-secret-here"Terminal window npx wrangler secret put IDEAL_AUTH_SECRETOr add it to your
wrangler.tomlfor development:wrangler.toml [vars]IDEAL_AUTH_SECRET = "at-least-32-characters-long-secret-here"Generate a strong secret:
Terminal window bunx ideal-auth secret
Cookie bridge
Section titled “Cookie bridge”Hono provides getCookie, setCookie, and deleteCookie helpers from hono/cookie. The bridge maps these to the three functions ideal-auth expects.
import type { Context } from 'hono';import { getCookie, setCookie, deleteCookie } from 'hono/cookie';import type { CookieBridge } from 'ideal-auth';
export function createCookieBridge(c: Context): CookieBridge { return { get(name: string) { return getCookie(c, name); }, set(name, value, options) { setCookie(c, name, value, { httpOnly: options.httpOnly, secure: options.secure, sameSite: options.sameSite === 'lax' ? 'Lax' : options.sameSite === 'strict' ? 'Strict' : 'None', path: options.path ?? '/', ...(options.maxAge !== undefined && { maxAge: options.maxAge }), }); }, delete(name) { deleteCookie(c, name, { path: '/' }); }, };}Auth setup
Section titled “Auth setup”Create an auth() factory that accepts a Hono Context and returns an AuthInstance.
import { createAuth, createHash } from 'ideal-auth';import { createCookieBridge } from './cookies';import { db } from './db'; // your database client
type User = { id: string; email: string; name: string; password: string;};
export const hash = createHash({ rounds: 12 });
export function auth(c: import('hono').Context) { const authFactory = createAuth<User>({ secret: getSecret(c), cookie: createCookieBridge(c), hash,
async resolveUser(id) { return db.user.findUnique({ where: { id } }); },
async resolveUserByCredentials(credentials) { return db.user.findUnique({ where: { email: credentials.email }, }); }, });
return authFactory();}
function getSecret(c: import('hono').Context): string { // Works with both env vars (Node/Bun) and Cloudflare Workers bindings return c.env?.IDEAL_AUTH_SECRET ?? process.env.IDEAL_AUTH_SECRET!;}App setup
Section titled “App setup”import { Hono } from 'hono';import { csrf } from 'hono/csrf';import { authRoutes } from './routes/auth';import { dashboardRoutes } from './routes/dashboard';import { requireAuth } from './middleware/auth';
const app = new Hono();
// CSRF protection for state-changing requestsapp.use(csrf());
// Public auth routesapp.route('/auth', authRoutes);
// Protected routesapp.use('/dashboard/*', requireAuth);app.route('/dashboard', dashboardRoutes);
export default app;Login route
Section titled “Login route”import { Hono } from 'hono';import { auth, hash } from '../lib/auth';import { db } from '../lib/db';
export const authRoutes = new Hono();
authRoutes.post('/login', async (c) => { const body = await c.req.json();
if (!body.email || !body.password) { return c.json({ error: 'Email and password are required.' }, 400); }
const session = auth(c); const success = await session.attempt( { email: body.email, password: body.password }, { remember: body.remember ?? false }, );
if (!success) { return c.json({ error: 'Invalid email or password.' }, 401); }
return c.json({ success: true });});Registration route
Section titled “Registration route”authRoutes.post('/register', async (c) => { const body = await c.req.json();
if (!body.email || !body.name || !body.password) { return c.json({ error: 'All fields are required.' }, 400); }
if (body.password.length < 8) { return c.json({ error: 'Password must be at least 8 characters.' }, 400); }
if (body.password !== body.passwordConfirmation) { return c.json({ error: 'Passwords do not match.' }, 400); }
const existing = await db.user.findUnique({ where: { email: body.email } }); if (existing) { return c.json({ error: 'An account with this email already exists.' }, 409); }
const user = await db.user.create({ data: { email: body.email, name: body.name, password: await hash.make(body.password), }, });
// Log the user in immediately after registration const session = auth(c); await session.login(user);
return c.json({ success: true }, 201);});Logout route
Section titled “Logout route”authRoutes.post('/logout', async (c) => { const session = auth(c); await session.logout();
return c.json({ success: true });});Current user route
Section titled “Current user route”authRoutes.get('/me', async (c) => { const session = auth(c); const user = await session.user();
if (!user) { return c.json({ user: null }); }
return c.json({ user: { id: user.id, email: user.email, name: user.name }, });});Auth middleware
Section titled “Auth middleware”Create a middleware that protects routes from unauthenticated access and stores the user in the Hono context.
import { createMiddleware } from 'hono/factory';import { auth } from '../lib/auth';
type User = { id: string; email: string; name: string;};
// Extend Hono's context variablestype Env = { Variables: { user: User; };};
export const requireAuth = createMiddleware<Env>(async (c, next) => { const session = auth(c); const user = await session.user();
if (!user) { return c.json({ error: 'Authentication required.' }, 401); }
// Store non-sensitive user data in context c.set('user', { id: user.id, email: user.email, name: user.name });
await next();});Protected route example
Section titled “Protected route example”import { Hono } from 'hono';
type Env = { Variables: { user: { id: string; email: string; name: string }; };};
export const dashboardRoutes = new Hono<Env>();
dashboardRoutes.get('/', (c) => { const user = c.get('user'); return c.json({ message: `Welcome, ${user.name}`, user, });});
dashboardRoutes.get('/settings', (c) => { const user = c.get('user'); return c.json({ message: 'Settings page', user, });});Getting the current user
Section titled “Getting the current user”In any route handler (after middleware)
Section titled “In any route handler (after middleware)”app.get('/api/profile', requireAuth, (c) => { const user = c.get('user'); return c.json({ user });});Optional auth (user may or may not be logged in)
Section titled “Optional auth (user may or may not be logged in)”import { createMiddleware } from 'hono/factory';import { auth } from '../lib/auth';
type Env = { Variables: { user: { id: string; email: string; name: string } | null; };};
export const optionalAuth = createMiddleware<Env>(async (c, next) => { const session = auth(c); const user = await session.user();
c.set('user', user ? { id: user.id, email: user.email, name: user.name } : null);
await next();});app.get('/', optionalAuth, (c) => { const user = c.get('user'); if (user) { return c.json({ message: `Hello, ${user.name}` }); } return c.json({ message: 'Hello, guest' });});CSRF protection
Section titled “CSRF protection”Hono has a built-in csrf() middleware that validates the Origin header on non-safe HTTP methods.
import { csrf } from 'hono/csrf';
// Apply to all routesapp.use(csrf());
// Or with custom optionsapp.use( csrf({ origin: ['https://yourdomain.com', 'https://www.yourdomain.com'], }),);The csrf() middleware:
- Allows
GET,HEAD, andOPTIONSrequests through - Validates the
Originheader onPOST,PUT,PATCH, andDELETErequests - Returns a
403if the origin does not match
Edge runtime considerations
Section titled “Edge runtime considerations”Hono is designed to run on edge runtimes like Cloudflare Workers. Here are the key considerations for using ideal-auth on the edge.
Cloudflare Workers
Section titled “Cloudflare Workers”ideal-auth uses bcryptjs (pure JavaScript bcrypt) and Node.js crypto APIs for session sealing. On Cloudflare Workers:
- bcryptjs works out of the box (pure JS, no native dependencies).
- Node.js crypto requires the
nodejs_compatcompatibility flag.
compatibility_flags = ["nodejs_compat"]Environment variables
Section titled “Environment variables”On Cloudflare Workers, access env vars via c.env:
const secret = c.env.IDEAL_AUTH_SECRET;The getSecret helper in the auth setup above handles this automatically.
Database access
Section titled “Database access”On edge runtimes, use an edge-compatible database:
- Cloudflare D1 (SQLite at the edge)
- Turso (distributed SQLite)
- PlanetScale (serverless MySQL)
- Neon (serverless Postgres)
Complete file structure
Section titled “Complete file structure”src/ index.ts # Hono app entry point lib/ auth.ts # Auth factory cookies.ts # Cookie bridge db.ts # Database client middleware/ auth.ts # requireAuth middleware optional-auth.ts # Optional auth middleware routes/ auth.ts # Login, register, logout, me dashboard.ts # Protected dashboard routesSecurity notes
Section titled “Security notes”- Session secret: Store
IDEAL_AUTH_SECRETas an environment variable. On Cloudflare Workers, usewrangler secret put. Never hard-code secrets. - HTTPS: On most edge platforms (Cloudflare Workers, Vercel Edge), HTTPS is enforced automatically. For Node.js/Bun deployments, set
NODE_ENV=productionso session cookies are markedSecure. - Cookie scope: The default
SameSite=LaxandHttpOnly=truesettings protect against CSRF and XSS cookie theft. - Context variables: Use Hono’s
c.set()/c.get()to pass the authenticated user through the middleware chain. Use TypeScript generics (Hono<Env>) for full type safety. - CSRF middleware: Always enable Hono’s
csrf()middleware in production. It prevents cross-origin form submissions and AJAX requests. - Rate limiting: Protect your login endpoint from brute-force attacks. Use
ideal-auth’screateRateLimiteror Cloudflare’s built-in rate limiting for Workers. - bcrypt on edge:
bcryptjsis CPU-intensive. On edge runtimes with strict CPU time limits (e.g., Cloudflare Workers free tier), consider reducing bcrypt rounds to 10 if you hit timeout limits, though 12 rounds is preferable for security.