Skip to content

Hono

Hono is the primary supported framework. Workers Auth returns Hono apps and middleware directly — no adapters, no wrappers. You mount the handler, apply middleware, and you’re done.

import { WorkersAuth } from 'workers-auth';
const auth = WorkersAuth({ /* config */ });
// auth.handler — Hono app with login/logout/session routes
// auth.admin — Hono app with user management endpoints
// auth.authenticate — middleware that validates sessions
// auth.authorize — middleware factory for role/permission checks

auth.handler is a full Hono app. Mount it at /auth to register all authentication routes:

app.route('/auth', auth.handler);

This single line gives you every route your frontend needs:

RouteMethodDescription
/auth/magic-link/sendPOSTSend a magic link email
/auth/magic-link/verifyGETVerify token and create session
/auth/githubGETStart GitHub OAuth flow
/auth/github/callbackGETHandle GitHub OAuth callback
/auth/sessionGETGet current session/user
/auth/logoutPOSTDestroy session

Which routes appear depends on which strategies you configure. The session and logout routes are always present.

auth.authenticate is Hono middleware that reads the session cookie, validates it, and makes the user available to downstream handlers. Apply it to any route group that requires a logged-in user:

app.use('/api/*', auth.authenticate);

If the session is missing or invalid, it returns 401 Unauthorized and stops the request. If the user’s account has been banned, it revokes the session and returns 401 as well.

On success, two values are set on the Hono context:

KeyTypeDescription
c.get('user')UserThe authenticated user object
c.get('sessionId')stringThe current session ID

auth.authorize(type, value) returns Hono middleware that checks whether the authenticated user meets a specific role or permission requirement.

// Require the "admin" role
app.use('/admin/*', auth.authorize('role', 'admin'));
// Require the "write" permission
app.post('/api/posts', auth.authorize('permission', 'write'), createPost);

The authenticate middleware must run first — authorize reads the user from context. If the check fails, it returns 403 Forbidden.

// Correct ordering: authenticate before authorize
app.use('/api/*', auth.authenticate);
app.use('/api/admin/*', auth.authorize('role', 'admin'));

After auth.authenticate runs, the User object is available via c.get('user'):

app.get('/api/me', (c) => {
const user = c.get('user');
return c.json(user);
});

The User object:

interface User {
id: string;
email: string;
status: 'active' | 'banned';
createdAt: number;
updatedAt: number;
}

auth.admin is a separate Hono app that exposes user management endpoints — listing users, banning/unbanning, and managing role assignments. Mount it wherever you want, and protect it with your own middleware:

app.use('/admin/*', auth.authenticate);
app.use('/admin/*', auth.authorize('role', 'admin'));
app.route('/admin', auth.admin);

This registers the following routes:

RouteMethodDescription
/admin/usersGETList users (supports ?limit= and ?offset=)
/admin/users/:idGETGet a single user with their roles
/admin/users/:id/banPOSTBan user and revoke all sessions
/admin/users/:id/unbanPOSTUnban user
/admin/users/:id/rolesPOSTAssign a role (body: { "role": "editor" })
/admin/users/:id/roles/:roleDELETERemove a role

The admin handler has no built-in access control. You decide who can reach it by applying authenticate and authorize before mounting.

Hono supports generic type parameters for your Worker’s environment bindings. Define your Env type once and pass it to new Hono() for full type safety across handlers:

type Env = {
Bindings: {
DB: D1Database;
SESSIONS: KVNamespace;
RESEND_API_KEY: string;
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET: string;
};
};
const app = new Hono<Env>();

This gives you autocomplete and type checking when accessing c.env.DB, c.env.SESSIONS, and your secrets.

A complete Worker using magic links, GitHub OAuth, RBAC, and the admin API:

import { Hono } from 'hono';
import { WorkersAuth } from 'workers-auth';
import { MagicLinkStrategy } from 'workers-auth/authn/magic-link';
import { GitHubStrategy } from 'workers-auth/authn/github';
import { RBACPolicy } from 'workers-auth/authz/rbac';
import { ResendProvider } from 'workers-auth/providers/resend';
import { D1Adapter } from 'workers-auth/adapters/d1';
import { KVAdapter } from 'workers-auth/adapters/kv';
import { defaultTemplate } from 'workers-auth/templates/default';
type Env = {
Bindings: {
DB: D1Database;
SESSIONS: KVNamespace;
RESEND_API_KEY: string;
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET: string;
};
};
const app = new Hono<Env>();
const auth = WorkersAuth({
database: (binding) => D1Adapter(binding),
cache: (binding) => KVAdapter(binding),
authn: {
strategies: [
MagicLinkStrategy({
provider: ResendProvider({
apiKey: process.env.RESEND_API_KEY!,
from: 'auth@yourdomain.com',
}),
template: defaultTemplate,
}),
GitHubStrategy({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
session: { secret: 'your-secret-key-here' },
},
authz: RBACPolicy({
roles: {
admin: ['*'],
editor: ['read', 'write'],
user: ['read'],
},
defaultRole: 'user',
}),
redirectUrl: '/dashboard',
});
// Auth routes — login, logout, session
app.route('/auth', auth.handler);
// All API routes require a valid session
app.use('/api/*', auth.authenticate);
// Admin routes require the admin role
app.use('/admin/*', auth.authenticate);
app.use('/admin/*', auth.authorize('role', 'admin'));
app.route('/admin', auth.admin);
// Route handlers
app.get('/api/me', (c) => c.json(c.get('user')));
app.get('/api/posts', auth.authorize('permission', 'read'), (c) => {
return c.json({ posts: [] });
});
app.post('/api/posts', auth.authorize('permission', 'write'), async (c) => {
const body = await c.req.json();
return c.json({ created: true });
});
export default app;

Deploy with wrangler deploy and the D1 tables are created automatically on the first request.