npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@steve31415/auth-client

v1.1.0

Published

Cached client for the Plasticine auth service — Hono middleware plus a lower-level whoami() for WebSocket upgrades and Durable Objects

Downloads

391

Readme

@steve31415/auth-client

Cached client for the Plasticine auth service. Provides a Hono middleware plus a lower-level whoami() function for WebSocket upgrades, Durable Object fetch handlers, and queue consumers.

The library memoizes GET /api/whoami responses in the Cloudflare Cache API — 10 minutes for successful lookups, 30 seconds for 401s — keyed by a SHA-256 hash of the caller's credential. A single caller's steady-state traffic to the auth service drops to ≤6 requests/hour per unique bearer token or session cookie, per colo.

Install

npm install @steve31415/auth-client

You'll also need hono (peer dependency) and a service binding to the auth worker:

# wrangler.toml
[[services]]
binding = "AUTH"
service = "auth"

Hono middleware (recommended)

Drop-in replacement for the hand-rolled auth middleware:

import { Hono } from "hono";
import { createAuthMiddleware } from "@steve31415/auth-client";

type Env = { AUTH: Fetcher; ENVIRONMENT?: string };

const app = new Hono<{
  Bindings: Env;
  Variables: { user: { user_id: string; email: string } };
}>();

app.use(
  "*",
  createAuthMiddleware<Env>({
    getAuth: (env) => env.AUTH,
    domain: "secondthoughts.workers.dev",
  }),
);

app.get("/api/items", (c) => {
  const user = c.get("user");
  return c.json({ email: user.email });
});

Behavior:

  • ENVIRONMENT === "test" → injects a synthetic user ({ user_id: "test-user", email: "[email protected]" }), skips the AUTH call entirely.
  • Bearer token from Authorization: Bearer <token> wins over session cookie if both are present.
  • 401 on a path starting with /api/401 {"error":"Unauthorized"}.
  • 401 on any other path → redirect to https://auth.${domain}/login?redirect=<current URL>.

Options

| Option | Type | Default | |--------|------|---------| | getAuth | (env) => Fetcher | — (required) | | domain | string | — (required) | | testUser | { user_id, email } | { user_id: "test-user", email: "[email protected]" } | | cacheTtlSeconds | number | 600 | | negativeCacheTtlSeconds | number | 30 | | skip | (c: Context) => boolean | — |

skip lets you exempt specific routes (webhooks, health checks, OAuth callbacks, etc.) from auth without splitting the middleware mount points. When the predicate returns true, the middleware passes through without setting c.var.user — handlers must tolerate c.get("user") being undefined.

createAuthMiddleware<Env>({
  getAuth: (env) => env.AUTH,
  domain: "secondthoughts.workers.dev",
  skip: (c) =>
    c.req.path.startsWith("/webhooks/") ||
    c.req.path === "/robots.txt",
});

For more involved customisation (hybrid auth schemes, header-based response decisions, custom failure logging), call whoami() directly from your own middleware instead of trying to configure createAuthMiddleware.

whoami() for non-Hono contexts

For WebSocket upgrades, Durable Object fetch handlers, queue consumers, and crons:

import { whoami } from "@steve31415/auth-client";

async fetch(request: Request): Promise<Response> {
  if (request.headers.get("Upgrade") === "websocket") {
    const user = await whoami({
      auth: this.env.AUTH,
      cookie: request.headers.get("cookie") ?? undefined,
      ctx: this.ctx,
    });
    if (!user) return new Response("Unauthorized", { status: 401 });

    const pair = new WebSocketPair();
    this.ctx.acceptWebSocket(pair[1]);
    return new Response(null, { status: 101, webSocket: pair[0] });
  }
  // ...
}

Returns AuthUser on 200, null on 401, throws on transport errors or any other response status.

Pass ctx (ExecutionContext / DurableObjectState) when you have one — cache writes are dispatched via ctx.waitUntil() so they don't delay the response.

How the cache works

  • Backing store: caches.open("auth-client-v1") — a named, versioned Cache API namespace. The v1 suffix is your invalidation lever: bumping it wipes all cached entries across every caller.
  • Key: SHA-256 of "<type>:<credential>", where <type> is bearer or session. Identical credentials used as both a bearer token and a session cookie do not collide.
  • Session cookie extraction: only the session=... value is hashed — changes to other cookies (themes, lang, analytics) don't spuriously invalidate the cache.
  • Stored value: a small JSON envelope ({status, user?}) with Cache-Control: max-age=<ttl>. The Cache API handles expiry.
  • Scope: Cloudflare's Cache API is per-colo, not global. Each data centre your Worker runs in keeps its own copy. For a 10-min TTL this is usually fine — a caller hitting two colos fetches twice per 10 min instead of once.

Staleness you should know about

A 10-minute positive cache means:

  • Revoking a bearer token (DELETE /api/tokens/:hash) may still authenticate callers for up to ~10 min in each colo that has cached it.
  • Logging out (POST /api/logout) may leave a user appearing logged in to other apps for up to ~10 min.

This is acceptable for the vast majority of workloads. Routes that perform sensitive operations (payments, permission changes, account takeover risk) should do an uncached server-side recheck in addition to passing the middleware — do not rely on the cache for authorization-critical decisions.

Migrating from hand-rolled auth middleware

Before:

app.use("*", async (c, next) => {
  if (c.env.ENVIRONMENT === "test") {
    c.set("user", { email: "[email protected]" });
    return next();
  }
  const res = await c.env.AUTH.fetch("https://auth/api/whoami", {
    headers: { Cookie: c.req.header("Cookie") || "" },
  });
  if (res.ok) {
    c.set("user", await res.json());
    return next();
  }
  if (c.req.path.startsWith("/api/")) {
    return c.json({ error: "Unauthorized" }, 401);
  }
  return c.redirect(
    `https://auth.secondthoughts.workers.dev/login?redirect=${encodeURIComponent(c.req.url)}`,
  );
});

After:

app.use(
  "*",
  createAuthMiddleware<Env>({
    getAuth: (env) => env.AUTH,
    domain: "secondthoughts.workers.dev",
  }),
);

The library takes over the test-env bypass, credential detection, API vs page 401 handling, and login redirect construction. The net change per app is ~20 lines → 5.

License

MIT