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

@bevingh/auth

v0.1.0

Published

Dual-secret access+refresh JWT with pluggable revocation, cookie helpers, thin Bearer verify, and optional Express adapters (not product OTP/guest domains).

Downloads

196

Readme

@bevingh/auth

Phase 3 / PR-13 — extracted. Dual-secret JWT + pluggable revocation + single-secret bearer + service API-key surface. Thin Express adapters (KD13).

Purpose

Dual-secret access+refresh JWT with cookie helpers, thin Bearer verify, and optional Express middleware adapters (not product OTP/guest domains).

| Field | Value | |---|---| | surfaceShape | pure_core_plus_express_adapter | | dependsOnPackages | @bevingh/errors (real implementation) | | extractionOrderHint | 3 | | status | extracted (implementation + tests) |

Design decision: pluggable revocation (not a silent winner)

Phase 1 found four different revocation/session architectures:

| Strategy | Repos | Mechanism | |---|---|---| | token_version | roomsplit | JWT claim vs stored per-user integer; bump = revoke all | | blocklist | mirrly (Redis), Academicx (memory), UVT nomination | lookup jti/token as revoked | | persisted RefreshToken | imep-portal-api | refresh validity = live DB row (not just a deny-list) | | none (gap) | UVT ticketing (vs nomination) | no check — security inconsistency |

These are not small variants of one algorithm. The package does not hard-code a single strategy.

RevocationChecker interface

interface TokenContext {
  kind: 'access' | 'refresh';
  token: string;           // raw JWT
  payload: AccessTokenClaims | RefreshTokenClaims;
}

interface RevocationChecker {
  isActive(ctx: TokenContext): boolean | Promise<boolean>;
}

| Factory | Maps to | |---|---| | createTokenVersionRevocationChecker(getTokenVersion) | Reference default — roomsplit security baseline | | createBlocklistRevocationChecker(isRevoked) | mirrly / Academicx / UVT nomination | | createNoRevocationChecker() | Explicit opt-out only — never silent default |

Persisted RefreshToken (imep): implement isActive yourself — for kind === 'refresh', require a live hashed-token row; for access, use short TTL and/or your own session rule. The interface covers this without forcing imep onto token_version.

Does the interface unify cleanly? Yes as an injection surface. It does not unify the storage models into one algorithm (that would be a false merge). Apps keep their store; they only implement isActive.

Middleware factories require a revocation argument. Omitting checks means passing createNoRevocationChecker() on purpose (addresses UVT ticketing gap).

Claim key normalization: sub

All minted/verified access tokens use JWT standard claim sub for the principal id.

| Repo pattern | On adopt | |---|---| | roomsplit, ussd-service, payment-gatway (payload.sub) | already aligned | | apps using userId (or similar) in JWT payload | map userId → sub at sign time, or accept a one-time claim migration |

Public API (pure core — @bevingh/auth)

| Area | Exports | |---|---| | Dual-secret JWT | createDualSecretTokenService | | Single-secret JWT | signSingleSecretAccessToken, verifySingleSecretAccessToken | | Cookies | refreshCookieOptions, REFRESH_COOKIE_NAME | | Revocation | createTokenVersionRevocationChecker, createBlocklistRevocationChecker, createNoRevocationChecker | | Auth core | authenticateAccessToken, extractBearerToken, assertHasRole, authenticationRequired, forbidden | | Service API keys | parseApiKeyEnvironment, matchApiKey (separate from user sessions) | | Arkesel edge | verifyArkeselSignature |

Express — @bevingh/auth/adapters/express

| Export | Role | |---|---| | createRequireAuth / createDualSecretRequireAuth / createSingleSecretRequireAuth | Bearer + required revocation | | createRequireRoles | role gate | | createArkeselAuth | pg/ussd HMAC ingress | | createApiKeyAuth | service-to-service (conduit-style); inject loadCandidates + bcrypt compare |

import {
  createDualSecretTokenService,
  createTokenVersionRevocationChecker,
} from '@bevingh/auth';
import { createDualSecretRequireAuth, createRequireRoles } from '@bevingh/auth/adapters/express';

const tokens = createDualSecretTokenService({
  accessSecret: process.env.JWT_ACCESS_SECRET!,
  refreshSecret: process.env.JWT_REFRESH_SECRET!,
});

const revocation = createTokenVersionRevocationChecker(async (sub) => {
  const user = await db.users.findById(sub);
  return user?.token_version ?? 0;
});

app.use(createDualSecretRequireAuth({ tokens, revocation }));
app.get('/admin', createRequireRoles('admin'), handler);

What was NOT extracted

| Item | Reason | |---|---| | Didipay refresh | Stubbed / non-functional — do not champion | | Didipay OTP | Optional future surface; not mixed into session core this PR | | Bevin-Photos guest_cap | product domain | | maame Supabase JWKS | project-specific IdP | | Product User models | apps inject loaders / version getters |

Byte-identical pg/ussd finding (documented + ported)

requireAuth + requireRoles + arkeselAuth are shared lineage between payment-gatway and ussd-service. Ported as:

  • single-secret sign/verify + createSingleSecretRequireAuth
  • createRequireRoles
  • createArkeselAuth / verifyArkeselSignature

This is dedup evidence, not a separate package.

openVariances status

| Variance | Handling | |---|---| | dual-secret revocation strategies | Pluggable interface — not forced merge | | Didipay incomplete refresh | Not extracted | | claim key shapes | Normalized to sub; mapping documented | | UVT blacklist optionality | Revocation required on middleware; no-op is explicit |

Tests

npm run test -w @bevingh/auth
npm run build -w @bevingh/auth

Coverage: dual/single JWT round-trip, expired rejection, token_version + blocklist checkers (two mocks), Express bearer + roles + arkesel.

Champion files (read-only)

  • roomsplit jwt.js, auth.service.js (tokenPair / refresh version check pattern), auth.middleware.js
  • ussd-service middleware/auth.js (+ arkesel via sibling)
  • conduit apiKeyAuth.js (mechanism only)

mustNotContain (verified)

| Constraint | Status | |---|---| | Bevin-Photos guest_cap | OK | | maame Supabase JWKS | OK | | Didipay stubbed refresh as champion | OK | | product user models | OK — only sub / injectable getters |