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

@factorialco/auth

v0.2.0

Published

Decode and verify Factorial ID JWT tokens.

Readme

@factorialco/auth

Verify and decode Factorial ID JWT access and ID tokens.

Library-agnostic: it depends only on jose and the global fetch, so it runs on Node.js (>= 22.14) and on edge / serverless runtimes.

  • Resolves the issuer + JWKS URI via OIDC discovery (cached, with stale fallback).
  • Verifies tokens (ES256 by default): signature, iss, aud, exp, nbf.
  • Refetches the JWKS automatically on key rotation (unknown kid).
  • Returns strict, typed, immutable claim objects.
  • Ships actor-identity value objects: ActorRef, ActorType, IdentityChain, ActType.
  • Provides a framework-agnostic extractBearerToken helper.

Install

pnpm add @factorialco/auth
# or: npm install / yarn add / bun add

Quick start

import { FactorialAuth } from "@factorialco/auth";

const auth = new FactorialAuth({
  oidcDiscoveryUrl: "https://id.factorialhr.com/.well-known/openid-configuration",
  audience: "your-client-id",
});

// Decode and verify an access token
const accessTokenClaim = await auth.decodeAccessToken(token);
console.log(accessTokenClaim.sub, accessTokenClaim.cid);

// Decode and verify an id token
const idTokenClaim = await auth.decodeIdToken(token);
console.log(idTokenClaim.sub, idTokenClaim.email);

Construction is cheap and synchronous — no network calls happen until the first decode*/tryDecode* call. Discovery and JWKS documents are fetched lazily and then cached (see Caching & key rotation).

Actor identity primitives

ActorRef and IdentityChain are small value objects for carrying authenticated actor identity across application boundaries. They are authentication primitives, not authorization contexts and not event transport objects.

ActorRef

ActorRef is an immutable reference to one concrete actor.

It answers: "which actor is this?"

import { ActorRef } from "@factorialco/auth";

ActorRef.employee("123");
ActorRef.company("42");
ActorRef.system("domain-events-audit-log-creator");

An actor ref serializes to a URI-like string:

f:act:employee:123

Use an ActorRef when code needs a compact, stable actor identifier without loading a model, carrying permissions, or encoding tenant scope. The id is always a string and must be opaque: never put emails, names, API keys, tokens, IPs, or other sensitive values in it.

IdentityChain

IdentityChain is a recursive chain of actor refs.

It answers: "who is the effective authenticated actor, and who is acting through whom?"

The chain root is the effective actor for the current action. If the action happened through a become/act flow, act points to the actor chain that initiated it, and actType describes the relationship between those two nodes.

import { ActorRef, ActType, IdentityChain } from "@factorialco/auth";

const employee = ActorRef.employee("123");
const admin = ActorRef.employee("999");

const identityChain = IdentityChain.create({
  actor: employee,
  act: IdentityChain.create({ actor: admin }),
  actType: ActType.AdminBecome,
});

identityChain.actor; // employee
identityChain.act?.actor; // admin

The serialized shape is string-keyed and recursive (the wire form uses the snake_case act_type):

identityChain.toObject();

// {
//   actor: "f:act:employee:123",
//   act: { actor: "f:act:employee:999" },
//   act_type: "admin_become",
// }

Use IdentityChain.parse only for trusted server-side metadata. Deserialization is capped at 3 nodes by default to keep parsing conservative; pass maxDepth to change it:

IdentityChain.parse(serializedObject);
IdentityChain.parse(serializedObject, { maxDepth: 5 });

How they relate

ActorRef is the leaf value. It identifies one actor and nothing else.

IdentityChain composes actor refs into the authenticated relationship graph. This keeps actor references stable and simple while preserving become/act context explicitly. Do not encode impersonation chains into an actor ref string; keep the actor ref as the subject and use IdentityChain.act plus IdentityChain.actType for the relationship.

For example, an admin becoming an employee is represented as:

employee actor ref -> act(admin actor ref), actType: admin_become

That lets application code choose the right projection for its own contract.

Never accept a client-supplied actor ref or identity chain as authoritative for writes. Build these objects from authenticated server-side context, credentials, or known system callers. An actor ref identifies the actor, not the tenant, so audit and domain-event flows must carry company or tenant scope separately.

Error handling

Every verification/decoding failure throws a subclass of AuthError. Catch AuthError to handle all of them, or a specific subclass for finer control. The tryDecode* methods swallow AuthError and return null; any other (unexpected) error propagates.

Error
├─ AuthError                       // base for everything this library throws
│  ├─ ConfigurationError           // invalid FactorialAuthConfig (thrown by the constructor)
│  ├─ OidcDiscoveryFetchError      // discovery endpoint unreachable / non-2xx / timeout
│  ├─ OidcDiscoveryParseError      // discovery document invalid JSON or missing issuer/jwks_uri
│  ├─ JwksFetchError               // JWKS endpoint unreachable / non-2xx / timeout
│  ├─ JwksParseError               // JWKS invalid JSON or malformed key set
│  └─ TokenError                   // base for token-level failures
│     ├─ InvalidToken              // bad signature, malformed token, unknown kid, or bad claim shape
│     ├─ ExpiredToken              // exp check failed
│     ├─ InvalidIssuer             // iss did not match the discovery document
│     ├─ InvalidAudience           // aud did not match config.audience
│     └─ ImmatureToken             // nbf check failed (token used before it is valid)
├─ ActorRefError                   // malformed ActorRef string/id (NOT an AuthError)
└─ IdentityChainError              // malformed identity chain (NOT an AuthError)

Note: ActorRefError and IdentityChainError extend Error directly, not AuthError, and are not affected by tryDecode*.

import {
  FactorialAuth,
  ExpiredToken,
  TokenError,
  OidcDiscoveryFetchError,
} from "@factorialco/auth";

try {
  const claims = await auth.decodeAccessToken(token);
} catch (error) {
  if (error instanceof ExpiredToken) {
    // prompt a refresh
  } else if (error instanceof TokenError) {
    // any other invalid-token case -> 401
  } else if (error instanceof OidcDiscoveryFetchError) {
    // upstream/infra problem -> 503
  } else {
    throw error;
  }
}

Caching & key rotation

  • The OIDC discovery document and the JWKS are cached in memory with a dual TTL: fresh for 10 minutes, then served stale for up to 1 hour if a refetch fails (so a transient discovery/JWKS outage doesn't break verification).
  • Concurrent first-time verifications trigger a single discovery fetch and a single JWKS fetch (in-flight requests are de-duplicated).
  • When a token carries a kid that isn't in the cached JWKS (key rotation), the JWKS is refetched once and the lookup retried. If the kid is still unknown, the token is rejected with InvalidToken.

Because caches live on the instance, create one FactorialAuth and reuse it for the lifetime of your process/worker rather than constructing one per request.

Integration patterns

Server request handler (Express-style)

import { FactorialAuth, extractBearerToken } from "@factorialco/auth";

const auth = new FactorialAuth({
  oidcDiscoveryUrl: process.env.FACTORIAL_OIDC_DISCOVERY_URL!,
  audience: process.env.FACTORIAL_AUDIENCE!,
});

app.use(async (req, res, next) => {
  const token = extractBearerToken(req.headers.authorization ?? null);

  if (token === null) {
    return res.status(401).json({ error: "missing bearer token" });
  }

  const claims = await auth.tryDecodeAccessToken(token);

  if (claims === null) {
    return res.status(401).json({ error: "invalid token" });
  }

  req.claims = claims;
  next();
});

Edge / Web Request

const token = extractBearerToken(request.headers.get("authorization"));
const claims = token ? await auth.tryDecodeAccessToken(token) : null;

Delegated identity (actIdentityChain)

The act claim is left as a raw object on the claims; parse it explicitly when you need the chain (e.g. staff-become / admin-become flows):

import { IdentityChain } from "@factorialco/auth";

const claims = await auth.decodeAccessToken(token);

if (claims.act) {
  const chain = IdentityChain.parse(claims.act); // throws IdentityChainError if malformed
  const effectiveActor = chain.actor; // ActorRef performing the action
}

Compatibility

  • Runtime: Node.js >= 22.14, and any edge/serverless runtime with global fetch and Web Crypto (Cloudflare Workers, Vercel Edge, Deno, Bun).
  • Modules: dual ESM (import) and CJS (require) entry points with bundled .d.ts.

Contributing

See CONTRIBUTING.md for local setup and development commands.