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

@eristack/jwt-auth

v0.4.5

Published

Canonical JWT access + refresh-token auth primitives for Eristack

Readme

@eristack/jwt-auth

Canonical JWT access-token + opaque refresh-token primitives for ERP and business apps.

  • Pure core (no HTTP, no DB, no framework)
  • Refresh rotation with family reuse detection
  • Drizzle adapters for pgsql / mysql / sqlite
  • Headless REST actions + Express / Nest shells
  • Headless browser client + headless React bindings

Install

pnpm add @eristack/jwt-auth
# optional peers depending on entry:
# drizzle-orm | express | @nestjs/common @nestjs/core | react

Export map

| Import | Purpose | | --- | --- | | @eristack/jwt-auth | Issue / verify / refresh / revoke | | @eristack/jwt-auth/drizzle | Dialect table + RefreshTokenStore | | @eristack/jwt-auth/rest | Headless REST actions + createRequireAuth | | @eristack/jwt-auth/express | Express router + middleware | | @eristack/jwt-auth/nest | Nest module + guard + controller | | @eristack/jwt-auth/client | Headless frontend client | | @eristack/jwt-auth/react | Headless React provider/hooks (no UI) |

Core

import {
  createJwtAuth,
  createMemoryCredentialStore,
  createMemoryRefreshTokenStore,
} from "@eristack/jwt-auth";

const auth = createJwtAuth({
  accessSecret: process.env.JWT_ACCESS_SECRET!,
  store: createMemoryRefreshTokenStore(), // swap for Drizzle in production
  credentials: createMemoryCredentialStore(), // optional; required for login
  accessTokenTtl: "15m",
  refreshTokenTtl: "30d",
});

// App owns `users`. Attach credentials as a child (`subject` = user id):
await auth.registerCredentials({
  subject: user.id,
  username: "demo",
  password: "password123",
});
const tokens = await auth.login({ username: "demo", password: "password123" });

// SSO / magic-link / already-verified subjects can still use issueTokens:
const issued = await auth.issueTokens({
  subject: user.id,
  claims: { role: user.role },
});

const verified = await auth.verifyAccessToken(tokens.accessToken);
const rotated = await auth.refresh(tokens.refreshToken);
await auth.revoke(rotated.refreshToken);

// Device / session management (safe metadata only — no refresh secrets)
const sessions = await auth.listSessions(user.id);
await auth.revokeSession({ sessionId: sessions[0]!.id, subject: user.id });

The package stores username + password hash in jwt_auth_credentials (or your name). It does not own a users table — credentials are always a child of the app's users (subject).

HTTP routes (Express / Nest / REST): POST /auth/login, POST /auth/change-password, GET /auth/sessions, DELETE /auth/sessions/:sessionId (Bearer where noted).

Docs

| Guide | Topic | | --- | --- | | Introduction | What / isn’t, layers | | Getting started | Register → login → refresh | | Concepts | Users, credentials, families | | Credentials | Password lifecycle | | Tokens & refresh | Rotation + reuse | | Sessions | DataGridResult device list | | HTTP | REST / Express / Nest | | Database | Drizzle tables | | Client & React | Headless SPA wiring | | Recipes | Signup, SSO, examples | | Security | Production checklist |

AI agent skills

This package ships Agent Skills via TanStack Intent:

  • @eristack/jwt-auth#jwt-auth-core — token lifecycle and reuse detection
  • @eristack/jwt-auth#jwt-auth-adapters — Drizzle / REST / Express / Nest / client / React
npx @tanstack/intent@latest install
npx @tanstack/intent@latest load @eristack/jwt-auth#jwt-auth-core

Drizzle

import { createJwtAuth } from "@eristack/jwt-auth";
import {
  createCredentialsTable,
  createDrizzleCredentialStore,
  createDrizzleRefreshTokenStore,
  createRefreshTokenTable,
} from "@eristack/jwt-auth/drizzle";

// App schema also defines `users`; credentials.subject → users.id
const refreshTokens = createRefreshTokenTable("pgsql"); // "pgsql" | "mysql" | "sqlite"
const credentialsTable = createCredentialsTable("pgsql"); // default: jwt_auth_credentials

const auth = createJwtAuth({
  accessSecret: process.env.JWT_ACCESS_SECRET!,
  store: createDrizzleRefreshTokenStore({ dialect: "pgsql", db, table: refreshTokens }),
  credentials: createDrizzleCredentialStore({
    dialect: "pgsql",
    db,
    table: credentialsTable,
  }),
});

Headless REST → Express / Nest

import { createRestActions, createRequireAuth } from "@eristack/jwt-auth/rest";
import { createJwtAuthRouter, createExpressRequireAuth } from "@eristack/jwt-auth/express";
import { JwtAuthModule, JwtAuthGuard } from "@eristack/jwt-auth/nest";

const actions = createRestActions({ jwtAuth: auth, refreshTokenTransport: "body-or-cookie" });
const requireAuth = createRequireAuth({ jwtAuth: auth });

// Express
app.use("/auth", createJwtAuthRouter({ jwtAuth: auth }));
app.get("/me", createExpressRequireAuth({ jwtAuth: auth }), (req, res) => {
  res.json({ subject: req.auth!.subject });
});

// Nest — inject DB/config via registerAsync when needed
JwtAuthModule.register({ jwtAuth: auth });
// JwtAuthModule.registerAsync({ inject: [DRIZZLE], useFactory: (db) => ({ jwtAuth: ... }) })
// then @UseGuards(JwtAuthGuard)

Headless client + React

Inject host + storage (string or getters). The package does not read env or invent URLs.

import {
  createJwtAuthClient,
  createLocalStorageTokenStorage,
} from "@eristack/jwt-auth/client";
import { JwtAuthProvider, useJwtAuth } from "@eristack/jwt-auth/react";

const client = createJwtAuthClient({
  baseUrl: () => appConfig.apiBaseUrl,
  storage: createLocalStorageTokenStorage(),
  getHeaders: () => ({ "X-Tenant": tenantId }),
});

function App() {
  return (
    <JwtAuthProvider client={client}>
      <Profile />
    </JwtAuthProvider>
  );
}

// or: <JwtAuthProvider clientConfig={{ baseUrl, storage }} />

function Profile() {
  const { status, accessToken, ensureAccessToken, logout } = useJwtAuth();
  // render your own UI — this package ships no buttons/forms
}

Docs