Hooks & Middleware
Faire Auth provides two mechanisms for customizing endpoint behavior: route hooks and typed middleware. Both are keyed by operationId (e.g. signUpEmail, getSession) and receive full type inference from your plugin configuration.
These are new Faire Auth features. If you need global before/after hooks that run on every request, see Global Hooks below.
Route Hooks
Route hooks run after Zod request validation but before the route handler. They receive the validation result and can short-circuit the request by returning a response.
import { faireAuth, defineOptions } from "faire-auth";
const cfg = defineOptions({
baseURL: "http://localhost:3000",
plugins: [organization()],
routeHooks: {
signUpEmail: (result, ctx) => {
if (!result.success) return; // let default error handling take over
const { email } = result.data.body;
if (!email.endsWith("@example.com")) {
return ctx.json({ error: "Only @example.com emails allowed" }, 403);
}
},
},
})
const auth = faireAuth(cfg)Route hooks are typed per-route — the result.data shape matches the specific endpoint's validated input.
Typed Middleware
Typed middleware runs before the route handler in the Hono middleware chain. It's standard Hono middleware with full type inference for the route's context variables.
import { faireAuth, defineOptions } from "faire-auth";
import { createMiddleware } from "faire-auth/plugins";
const cfg = defineOptions({
baseURL: "http://localhost:3000",
middleware: {
getSession: createMiddleware()(async (ctx, next) => {
console.log("getSession called at", new Date().toISOString());
await next();
}),
},
})
const auth = faireAuth(cfg)Use createMiddleware<E>()() (curried) to create middleware with custom context variable types:
import { createMiddleware } from "faire-auth/plugins";
const adminOnly = createMiddleware<{ isAdmin: boolean }>()(async (ctx, next) => {
const session = ctx.get("session");
ctx.set("isAdmin", session?.user?.role === "admin");
await next();
});DTO Transforms
DTO transforms let you control what data is exposed to clients. They're keyed by schema name (user, session, account, etc.) and applied automatically to all route responses.
const cfg = defineOptions({
baseURL: "http://localhost:3000",
dto: {
user: (user) => ({
id: user.id,
name: user.name,
email: user.email,
// omit internal fields like createdAt, updatedAt
}),
},
})DTO transforms are type-threaded — the client's inferred response types will reflect the transformed shape, not the raw database model.
Global Hooks
For hooks that run on every request (not per-route), use the hooks.before and hooks.after options. These are factory functions that receive the auth options and return a hook handler:
import { faireAuth } from "faire-auth";
import { createHook } from "faire-auth/plugins";
const auth = faireAuth({
baseURL: "http://localhost:3000",
hooks: {
before: (options) => createHook()(async (ctx) => {
// runs before every endpoint
console.log("Request to:", ctx.req.path);
}),
after: (options) => createHook()(async (ctx) => {
// runs after every endpoint
}),
},
})createHook and createMiddleware are both curried factories exported from faire-auth/plugins. The first call sets the type parameters, the second provides the handler.
Context
Inside middleware and hooks, the Hono context ctx provides:
ctx.req: The Hono request object with typedparam(),query(),json(),header().ctx.get("session"): The current session (if session middleware has run).ctx.json(data, status): Send a JSON response.ctx.redirect(url): Redirect the client.ctx.header(name, value): Set response headers.
Auth-specific context is available via ctx.get():
ctx.get("options"): The resolved Faire Auth options.ctx.get("secret"): The auth secret.ctx.get("authCookies"): Cookie configuration for session tokens.ctx.get("adapter"): The database adapter withfindOne,findMany,create,update,delete.ctx.get("internalAdapter"): Higher-level operations likecreateSession,createUser.ctx.get("generateId"): ID generation function.
Rate Limiting
Rate limiting is built-in and configurable per-route:
const cfg = defineOptions({
baseURL: "http://localhost:3000",
rateLimit: {
enabled: true,
customRules: {
"/sign-in/email": (req) => {
// return false to skip rate limiting for this request
return true;
},
},
},
})Custom rules receive a typed HonoRequest specific to the route path.
Reusable Hooks
If you need to reuse hooks or middleware across multiple routes, consider creating a plugin. Plugins can declare their own routeHooks and middleware which are merged with the top-level options. Learn more in the Plugins Documentation.