Better Auth integrates with Hono by mounting the auth handler on a wildcard route. Before you start, make sure you have a Better Auth instance configured. If you haven’t done that yet, check out the installation.
Mount the handler
Use app.on to handle both GET and POST requests on the /api/auth/* path and forward them to auth.handler.
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
const app = new Hono();
app.on(["POST", "GET"], "/api/auth/*", (c) => {
return auth.handler(c.req.raw);
});
serve(app);
CORS configuration
Use the cors middleware from hono/cors. Register it before your routes so cross-origin requests are handled before they reach the auth endpoints.
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono();
app.use(
"/api/auth/*",
cors({
origin: "http://localhost:3001", // replace with your frontend origin
allowHeaders: ["Content-Type", "Authorization"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length"],
maxAge: 600,
credentials: true,
}),
);
app.on(["POST", "GET"], "/api/auth/*", (c) => {
return auth.handler(c.req.raw);
});
serve(app);
CORS middleware must be registered before your routes. If CORS is registered after the route, preflight requests will fail.
Session middleware
Add a global middleware that fetches the session and stores the user and session objects in Hono’s context. This lets every downstream route access them without repeating the session lookup.
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
const app = new Hono<{
Variables: {
user: typeof auth.$Infer.Session.user | null;
session: typeof auth.$Infer.Session.session | null;
};
}>();
app.use("*", async (c, next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers });
if (!session) {
c.set("user", null);
c.set("session", null);
await next();
return;
}
c.set("user", session.user);
c.set("session", session.session);
await next();
});
app.on(["POST", "GET"], "/api/auth/*", (c) => {
return auth.handler(c.req.raw);
});
serve(app);
Accessing session in routes
app.get("/session", (c) => {
const user = c.get("user");
const session = c.get("session");
if (!user) return c.body(null, 401);
return c.json({ session, user });
});
Cross-domain cookies
By default, all Better Auth cookies use SameSite=Lax. If your client and server share a subdomain, enable cross-subdomain cookies in your auth config:
export const auth = betterAuth({
advanced: {
crossSubDomainCookies: {
enabled: true,
},
},
});
If you need fully cross-origin cookies (SameSite=None), configure the default cookie attributes:
export const auth = betterAuth({
advanced: {
defaultCookieAttributes: {
sameSite: "none",
secure: true,
partitioned: true, // required by upcoming browser standards for third-party cookies
},
},
});
You can also override attributes on individual cookies:
export const auth = betterAuth({
advanced: {
cookies: {
sessionToken: {
attributes: {
sameSite: "none",
secure: true,
partitioned: true,
},
},
},
},
});
Hono client configuration
When using the Hono RPC client (hono/client) to call Better Auth-protected endpoints, set credentials: "include" so the client forwards cookies on cross-origin requests.
import { hc } from "hono/client";
import type { AppType } from "./server";
const client = hc<AppType>("http://localhost:8787/", {
init: {
credentials: "include",
},
});
const response = await client.someProtectedEndpoint.$get();
credentials: "include" must be paired with credentials: true in the server’s CORS configuration, and the origin must be set to a specific domain (not *).