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

unauth

v0.0.8

Published

Low-level OIDC utilities

Readme

unauth

npm version npm downloads bundle size

A collection of low-level and high-level, server-agnostic, Authentication and Authorization utilities.

[!WARNING] This package is in active development. It is not recommended for production use yet unless you are willing to help with testing and feedback. Expect breaking changes, as I prioritize usability and correctness over stability at this stage.

Features

  • Session management — Encrypted JWE sessions with auto-refresh
  • Token pairs — Access (JWS) + Refresh (JWE) token lifecycle with coordinated login/logout
  • CSRF protection — Double-submit cookie pattern with HMAC
  • MiddlewarerequireSession, optionalSession, requireAuth, optionalAuth
  • Runtime-agnostic — Built on Web Crypto API
  • OAuth 2.1 and OIDC — Planned

Built on top of minimal dependencies:

  • unjwt — Low-level JWT (JWS/JWE/JWK) via Web Crypto
  • unsecure — Cryptographic utilities (HMAC, secure compare, etc.)

Usage

Install the package:

npx nypm install unauth

Session

Encrypted session cookies (JWE) with auto-refresh support.

import { defineSession, generateJWK, requireSession } from "unauth/h3v2";

const sessionKey = await generateJWK("A256GCM");

const useSession = defineSession<{ userId: string; role: string }>({
  key: sessionKey,
  maxAge: "7D",
  hooks: {
    async onRefresh({ session, refresh }) {
      // Refresh with updated data from your database
      const user = await db.users.findById(session.data.userId);
      await refresh({ userId: user.id, role: user.role });
    },
  },
});

const app = new H3()
  .post("/login", async (event) => {
    const session = await useSession(event);
    await session.update({ userId: "u1", role: "admin" });
    return { ok: true };
  })
  .get(
    "/me",
    async (event) => {
      const session = await useSession(event);
      return { user: session.data };
    },
    { middleware: [requireSession(useSession)] },
  )
  .post("/logout", async (event) => {
    const session = await useSession(event);
    await session.clear();
    return { ok: true };
  });

The onRefresh hook fires when the session crosses the refreshAfter threshold (default: 75% of maxAge). You control what happens:

  • await refresh() — Sliding window (re-issue with same data)
  • await refresh({ role: "admin" }) — Update data during refresh
  • await clear() — Destroy the session
  • Don't call either — Skip, session stays as-is

Token Pair

Access token (JWS, short-lived, client-readable) + refresh token (JWE, long-lived, encrypted) with coordinated lifecycle.

import { defineTokenPair, generateJWK, requireAuth } from "unauth/h3v2";

const atKeys = await generateJWK("ES256");
const rtKey = await generateJWK("A256GCM");

const useAuth = defineTokenPair<
  { sub: string; permissions: string[] },
  { sub: string; family: string }
>({
  access: { key: atKeys, maxAge: "15m" },
  refresh: { key: rtKey, maxAge: "30D" },
  hooks: {
    async onRefresh({ refresh, issue }) {
      const user = await db.users.findById(refresh.data.sub);
      if (!user || user.suspended) return; // don't issue — AT stays empty
      await issue({
        accessData: { sub: user.id, permissions: user.permissions },
        // refreshData is optional — omit to rotate with current data
      });
    },
    onAfterRefresh({ access, refresh, previousRefresh }) {
      logger.info("token_refresh", {
        sub: access.data.sub,
        newAtId: access.id,
        oldRtId: previousRefresh.id,
        newRtId: refresh.id,
      });
    },
  },
});

const app = new H3()
  .post("/login", async (event) => {
    const auth = await useAuth(event);
    await auth.issue({
      accessData: { sub: user.id, permissions: user.permissions },
      refreshData: { sub: user.id, family: crypto.randomUUID() },
    });
    return { ok: true };
  })
  .get(
    "/me",
    async (event) => {
      const { access } = await useAuth(event);
      return { user: access.data };
    },
    { middleware: [requireAuth(useAuth)] },
  )
  .post("/logout", async (event) => {
    const auth = await useAuth(event);
    await auth.revoke();
    return { ok: true };
  });

When the access token expires:

  • onRefresh fires with the valid refresh token
  • Call issue({ accessData, refreshData? }) to re-issue the AT and rotate the RT
  • Call revoke() to clear both tokens (e.g., user banned, family revoked)
  • Don't call either to skip (AT stays empty, RT preserved)
  • Throw to forward errors to onError without destroying tokens

Both access and refresh are unjwt session managers exposed directly — call .update() or .clear() on them for escape-hatch scenarios that bypass hooks.

CSRF

Double-submit cookie pattern with HMAC-generated tokens.

import { defineCsrf } from "unauth/h3v2";

const csrf = defineCsrf({ secret: process.env.CSRF_SECRET! });

const app = new H3()
  .get("/form", handler, { middleware: [csrf] })
  .post("/form", handler, { middleware: [csrf] });

Middleware

Separate middleware for sessions and token pairs:

import { requireSession, optionalSession } from "unauth/h3v2";
import { requireAuth, optionalAuth } from "unauth/h3v2";

// Session middleware
app.get("/me", handler, { middleware: [requireSession(useSession)] });
app.get("/feed", handler, { middleware: [optionalSession(useSession)] });

// Token pair middleware
app.get("/me", handler, { middleware: [requireAuth(useAuth)] });
app.get("/feed", handler, { middleware: [optionalAuth(useAuth)] });

// With authorization checks
app.delete("/admin/users/:id", handler, {
  middleware: [
    requireAuth(useAuth, {
      onAuthenticated({ session }) {
        if (!session.data.permissions.includes("admin:users:delete")) {
          throw new HTTPError("Forbidden", { status: 403 });
        }
      },
    }),
  ],
});

Development

  • Clone this repository
  • Install latest LTS version of Node.js
  • Enable Corepack using corepack enable
  • Install dependencies using pnpm install
  • Run interactive tests using pnpm dev

Why unauth?

I started by building unjwt, as I needed a cryptographically secure way to transmit sensitive information between various programming languages and servers. Not long after I started requiring some standardization, in particular on how to prepare and expect authorization data to be shared between parties (client and servers), but as I was testing various libraries I've never been satisfied by their DX (although most of them were great for someone that already knows the topic). So I started building unauth as a collection of low-level primitives that then can be wrapped in higher-level abstractions, via adapters, to provide a "batteries included" experience while retaining control and flexibility of using your preferred storage, database and web frameworks.

Credits

License

Published under the MIT license. Made by community 💛


🤖 auto updated with automd