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("/");
});