Documentation
Hono
Hono routes work on Node, Bun, and Cloudflare Workers.
app.ts
import { Hono } from "hono";
import { getCookie, setCookie } from "hono/cookie";
const app = new Hono<{ Bindings: { AUTH_ISSUER: string; AUTH_CLIENT_ID: string; AUTH_CLIENT_SECRET: string; AUTH_REDIRECT_URI: string } }>();
app.get("/auth/login", async (c) => {
const verifier = crypto.randomUUID() + crypto.randomUUID();
const challenge = await sha256Base64Url(verifier);
const state = crypto.randomUUID();
setCookie(c, "oidc", JSON.stringify({ verifier, state }), {
httpOnly: true,
secure: true,
maxAge: 600,
path: "/",
});
const url = new URL(`${c.env.AUTH_ISSUER}/oauth2/authorize`);
url.searchParams.set("response_type", "code");
url.searchParams.set("client_id", c.env.AUTH_CLIENT_ID); // YOUR_CLIENT_ID
url.searchParams.set("redirect_uri", c.env.AUTH_REDIRECT_URI);
url.searchParams.set("scope", "openid profile email offline_access");
url.searchParams.set("state", state);
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
return c.redirect(url.toString());
});
app.get("/auth/callback", async (c) => {
const code = c.req.query("code");
const state = c.req.query("state");
const saved = JSON.parse(getCookie(c, "oidc") ?? "{}");
if (!code || state !== saved.state) return c.text("Invalid state", 400);
const body = new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: c.env.AUTH_REDIRECT_URI,
client_id: c.env.AUTH_CLIENT_ID,
client_secret: c.env.AUTH_CLIENT_SECRET, // YOUR_CLIENT_SECRET
code_verifier: saved.verifier,
});
const tokenRes = await fetch(`${c.env.AUTH_ISSUER}/oauth2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});
const tokens = await tokenRes.json();
return c.redirect("/");
});