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

@flyingsquirrel0419/jwt-refresh

v0.1.0

Published

JWT refresh token rotation, race-condition prevention, reuse detection, and session management for Node.js.

Readme


The Problem

Every hand-rolled JWT stack eventually hits the same refresh wall:

jsonwebtoken only         --> signs and verifies tokens, but leaves refresh flows to app code
custom refresh endpoint   --> works until rotation, replay detection, and logout rules pile up
multiple browser tabs     --> all try to refresh at once, then users get logged out by accident
stolen refresh token      --> gets replayed without a clear security response

The failure mode is almost always the same: a team solves "issue access tokens" and underestimates "manage the refresh lifecycle safely".

The Solution

jwt-refresh packages the refresh lifecycle as a first-class system:

client request
  -> access token verification
  -> refresh cookie extraction
  -> refresh token verification
  -> session lookup
  -> race-condition guard
  -> rotation or replay decision
  -> blacklist / revoke / event emission
  -> new access token + refresh token

Instead of sprinkling refresh logic across routes, services, and middleware, you get one manager that owns:

  • refresh token rotation
  • reuse detection
  • legitimate race handling
  • access-token blacklisting
  • session revocation
  • framework-friendly handlers

Quick Start

Install

Install the published package from npm:

npm install @flyingsquirrel0419/jwt-refresh jsonwebtoken

Minimal example

import express from 'express'
import { JwtManager, MemoryTokenStore } from '@flyingsquirrel0419/jwt-refresh'

const app = express()
app.use(express.json())

const jwt = new JwtManager({
  access: {
    secret: process.env.JWT_SECRET!,
    ttl: '15m',
  },
  refresh: {
    secret: process.env.REFRESH_SECRET!,
    ttl: '7d',
    rotation: true,
    reuseDetection: true,
    absoluteExpiry: '90d',
  },
  cookie: {
    name: 'refreshToken',
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    path: '/auth',
  },
  refreshBuffer: 120,
  store: new MemoryTokenStore(),
})

app.post('/auth/login', async (_req, res) => {
  const { accessToken } = await jwt.issueTokens(
    res,
    {
      userId: 'user-1',
      email: '[email protected]',
      roles: ['member'],
    },
    {
      deviceId: 'browser',
    },
  )

  res.json({ accessToken })
})

app.post('/auth/refresh', jwt.refreshHandler())
app.get('/api/me', jwt.authenticate(), (req, res) => res.json(req.user))

What refresh handling looks like

POST /auth/refresh
  -> reads refresh cookie
  -> verifies JWT signature and claims
  -> checks stored session state
  -> returns 409 retry for a legitimate in-flight race
  -> revokes all sessions on replayed rotated tokens
  -> rotates refresh token and returns a fresh access token

Features

Token lifecycle

| Feature | What it does | |---|---| | Refresh token rotation | Issues a new refresh token on successful refresh and revokes the previous session | | Absolute expiry | Stops endless refresh chains and forces re-authentication after a fixed window | | Access-token blacklisting | Rejects still-valid access tokens immediately after sensitive events | | Cookie-aware refresh flow | Reads and writes refresh cookies without route boilerplate |

Security

| Feature | What it does | |---|---| | Reuse detection | Detects replayed rotated refresh tokens and revokes all active sessions for the user | | Race-condition protection | Distinguishes a real replay from a legitimate multi-request refresh burst | | Session-level revocation | Revokes one device, the current device, or all devices | | Security events | Emits token:reuse-detected, token:revoked, and refresh-related events |

Developer experience

| Feature | What it does | |---|---| | Typed payloads | JwtManager<TPayload> preserves your access-token payload type | | Framework helpers | Express-first handlers plus Fastify, Next.js, and NestJS adapters | | Multiple store adapters | Memory store for tests, plus Redis, Prisma, and Drizzle adapter surfaces | | ESM + CJS | Ships generated declaration files and dual module output |

Quality

| Feature | What it does | |---|---| | 64 passing tests | Integration, security, and deep unit coverage across core and adapter branches | | 99.05% line coverage | High confidence in the refresh, security, and adapter control flow | | GitHub Actions CI | Lint, test, coverage, and build on push and pull request |


Integrations

jwt-refresh is designed to sit inside the web stack you already use:

| Framework | Integration | |---|---| | Express | jwt.authenticate() and jwt.refreshHandler() as route-ready handlers | | Fastify | createFastifyHandler(jwt, 'authenticate' | 'refresh') bridge | | Next.js App Router | verifyNextRequest() and createNextRefreshHandler() helpers | | NestJS | createNestGuard() and getJwtUser() for guard-style integration |

import express from 'express'
import { JwtManager, MemoryTokenStore } from '@flyingsquirrel0419/jwt-refresh'

const app = express()
const jwt = new JwtManager({
  access: { secret: process.env.JWT_SECRET!, ttl: '15m' },
  refresh: { secret: process.env.REFRESH_SECRET!, ttl: '7d', rotation: true, reuseDetection: true },
  store: new MemoryTokenStore(),
})

app.post('/auth/refresh', jwt.refreshHandler())
app.get('/api/me', jwt.authenticate(), (req, res) => res.json(req.user))
import { createNextRefreshHandler } from '@flyingsquirrel0419/jwt-refresh/integrations/nextjs'

export const POST = createNextRefreshHandler(jwt)
import { createNestGuard } from '@flyingsquirrel0419/jwt-refresh/integrations/nestjs'

const JwtAuthGuard = createNestGuard(jwt)

API

The public API is intentionally compact:

class JwtManager<TPayload extends AccessTokenPayload = AccessTokenPayload> {
  issueTokens(res, payload, sessionMeta?)
  signAccessToken(payload)
  signRefreshToken(userId, sessionMeta?)
  verifyAccessToken(token)
  verifyRefreshToken(token)
  authenticate(options?)
  refreshHandler(options?)
  revokeCurrentSession(req, res)
  revokeSession(userId, sessionId)
  revokeAllSessions(userId)
  getSessions(userId, currentSessionId?)
  blacklistToken(token, options?)
}

See API Reference for the full option and method surface.


Security Defaults

Recommended production defaults:

  • refresh.rotation: true
  • refresh.reuseDetection: true
  • refresh.absoluteExpiry: '90d'
  • cookie.httpOnly: true
  • cookie.sameSite: 'strict'
  • cookie.secure: true in production

See the full security checklist.


Comparison

jwt-refresh is not trying to replace low-level JWT libraries. It sits above them and solves the lifecycle problems they intentionally leave to application code.

| Package | Signs JWTs | Rotation | Reuse detection | Race handling | Session revoke | Access blacklist | |---|---:|---:|---:|---:|---:|---:| | jwt-refresh | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | jsonwebtoken | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | jose | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | passport-jwt | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | @nestjs/jwt | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |

More detail: Comparison


Development

npm install
npm run lint
npm test
npm run coverage
npm run build

Repository docs

License

MIT