Auth with JsonToken

Issue tokens on a public login route; protect later routes with .use() and typed context.

Prerequisites

  • JWT_SECRET (or any secret string) in the environment
  • Register public routes before .use(authenticate) so they stay open

Tokens and middleware

import {
  HttpServer,
  JsonToken,
  type Middleware,
  z,
} from "jsr:@webtools/expressapi";

type Session = { userId: string; role: string };

const tokens = new JsonToken(Deno.env.get("JWT_SECRET") ?? "dev-secret");
const sessionSchema = z.object({ userId: z.string(), role: z.string() });

const authenticate: Middleware<{ session: Session }> = async (req, res) => {
  const header = req.headers.get("authorization");
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing authorization header" });
  }

  const session = await tokens.verify(header.slice(7), sessionSchema);
  if (!session) return res.status(401).json({ error: "Invalid token" });

  req.data.session = session;
};

const requireAdmin: Middleware<{ isAdmin: boolean }, { session: Session }> = (req, res) => {
  if (req.data.session.role !== "admin") {
    return res.status(403).json({ error: "Forbidden" });
  }
  req.data.isAdmin = true;
};

Public login, then protected routes

export const server = new HttpServer()
  .post("/login", async (req, res) => {
    const token = await tokens.sign({ userId: "1", role: "admin" });
    return res.json({ token, email: req.body.email });
  }, [], {
    body: z.object({
      email: z.string().email(),
      password: z.string().min(6),
    }),
  })
  .use(authenticate)
  .get("/profile", (req, res) =>
    res.json({ userId: req.data.session.userId }))
  .get("/admin", (req, res) =>
    res.json({ role: req.data.session.role }), [requireAdmin]);

export type AppRouter = typeof server;