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

@waukeshamakerspace/auth-kit

v0.3.0

Published

Keymaster OIDC Backend-For-Frontend for Waukesha Makerspace services: discovery, PKCE, code exchange, id_token verification, and the signed session cookie. The SPA never sees a token.

Readme

@waukeshamakerspace/auth-kit

The Keymaster OIDC Backend-For-Frontend, as a package.

Atlas established this pattern in its ADR-0007 and Journeyman copied it. By the time this package was extracted, the two copies had drifted apart in ways that mattered, so a token-verification fix meant one pull request per service and an audit of which copies were missed. This is that code, once.

The client secret never leaves the API process, and the SPA never sees a token.

Install

npm install @waukeshamakerspace/auth-kit
# for the Fastify routes, which need these peers:
npm install fastify @fastify/cookie

Use

import {
  KeymasterOidcClient,
  SessionCodec,
  keymasterConfigFromEnv,
  sessionConfigFromEnv,
} from '@waukeshamakerspace/auth-kit';
import { registerAuthRoutes, sessionGuard } from '@waukeshamakerspace/auth-kit/fastify';

const oidc = new KeymasterOidcClient(keymasterConfigFromEnv());
const session = new SessionCodec(sessionConfigFromEnv('myapp_session'));

await registerAuthRoutes(app, { oidc, session });

That mounts four routes under /api/auth:

| Route | Purpose | |---|---| | GET /login?returnTo=/path | 302 to Keymaster with PKCE S256 + state | | GET /callback/keymaster | Code exchange, sets the session cookie | | GET /session | The SPA's session source of truth; data is null when signed out | | GET /logout | Clears the cookie, then RP-initiated logout at Keymaster |

Guarding a route:

const guard = sessionGuard(session);

app.post('/api/things', async (request, reply) => {
  const claims = await guard(request, reply);
  if (!claims) return; // guard already sent the 401
  // claims.sub is the Roster person ID
});

Environment variables

The house names from shared/environment-variables.md:

  • KEYMASTER_URL (optional, defaults to the production deployment)
  • KEYMASTER_CLIENT_ID, KEYMASTER_CLIENT_SECRET
  • SESSION_SECRET, at least 32 bytes

Service-specific session claims

Some services keep an extra claim in the cookie. Atlas keeps a tier and treats a session without one as no session at all. That is a type parameter plus a validator rather than a fork:

type Tier = { tier: string };

const session = new SessionCodec<Tier>(sessionConfigFromEnv('atlas_session'), {
  decode: (p) => (typeof p.tier === 'string' ? { tier: p.tier } : null),
});

await registerAuthRoutes(app, {
  oidc,
  session,
  extraClaims: async (identity) => ({ tier: await deriveTier(identity) }),
  onLogin: async (identity) => provisionUser(identity), // Atlas keeps user rows
});

onLogin runs after a successful code exchange and before the cookie is set; throwing there fails the sign-in. Whatever it returns is passed as the third argument to extraClaims and overrideClaims, so a value the provisioning step already read (Atlas's stored tier) shapes the cookie without a second lookup:

await registerAuthRoutes(app, {
  oidc,
  session,
  onLogin: async (identity) => upsertUser(identity), // returns the stored row
  extraClaims: (identity, _request, stored) => ({ tier: effectiveTier(stored, identity.roles) }),
  // Replace `name`, `email` or `roles` before the cookie is written. Atlas
  // stores a public "First L." name rather than the Keymaster display name.
  overrideClaims: (identity) => ({ name: publicName(identity.firstName, identity.lastName) }),
  // Runs on GET /session with a valid session. Return the same object to
  // leave the cookie alone, a new claims object to re-issue it (a stale-roles
  // refresh), or null to sign the session out.
  onSession: async (claims) =>
    rolesAreStale(claims) ? { ...claims, roles: await liveRoles(claims.sub), rolesSyncedAt: Date.now() } : claims,
});

A failed code exchange (Keymaster down, a replayed code) redirects to /?auth_error=exchange_failed and logs a warning, rather than surfacing as a 500. The other auth_error values are missing_state, bad_state, state_mismatch, and whatever error Keymaster itself sent back.

What changed during extraction

The two source copies disagreed. Rather than silently pick a winner, each difference was resolved deliberately:

| Concern | Atlas | Journeyman | Here | |---|---|---|---| | Signing key | SESSION_SECRET or NEXTAUTH_SECRET | SESSION_SECRET | SESSION_SECRET only | | Short secrets | unchecked | unchecked | rejected at startup, under 32 bytes | | Missing client id/secret | sent as '' | sent as '' | throws at construction | | tier claim | required | absent | opt-in via ExtraClaimsCodec | | firstName/lastName | populated | always null | populated | | Display name | name claim only | synthesised from given/family | both, name wins | | Discovery/JWKS cache | module-global | module-global | per instance | | returnTo sanitising | on the query param | on the query param | query param and the pending cookie |

Two of those are behaviour changes worth calling out before you adopt this in an existing service:

NEXTAUTH_SECRET is not read. It was a leftover from Atlas's NextAuth era. If Atlas still needs it, resolve the value in Atlas and pass it as secret, so the shim stays visible in the service rather than hidden in shared code.

Short session secrets now fail at startup rather than at sign-in. HS256 keys below 256 bits weaken the signature and jose rejects them anyway; this just turns a confusing runtime failure into a startup error with the fix in it.

The returnTo change closes a real gap. Both copies sanitised the query parameter but then trusted whatever returnTo came back out of the pending cookie, so anything able to write that cookie had an open redirector. It is now sanitised on the way out as well.

Testing against it

registerAuthRoutes takes an OidcClientLike, not the concrete client, so a service can inject a stub in its own route tests without standing up a provider or reaching into jose. See src/fastify.test.ts for the shape.