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

ioserver-oidc

v0.1.4

Published

IOServer middleware set for OIDC/OAuth2 JWT authentication via JWKS (auth-service compatible)

Readme

ioserver-oidc

npm version License: MIT CI Build TypeScript

OIDC/OAuth2 JWT middleware set for IOServer.
Protects Fastify HTTP routes and Socket.IO namespaces by verifying access tokens issued by auth-service (BetterAuth + OAuth2 provider) via remote JWKS — zero secret storage on the application side.


Table of contents


Features

  • ✅ Verifies RS256 / ES256 JWT access tokens via remote JWKS (no secret distribution)
  • ✅ Validates iss, aud, and expiry claims
  • ✅ In-process JWKS key cache — one HTTP round-trip per key rotation
  • ✅ Auto-provisions local user records via appHandle.users.findOrCreate(...)
  • ✅ Rejects disabled accounts (403)
  • ✅ Injects sub, userId, userRole, roles, permissions, features on every authenticated request/socket
  • ✅ Admin role guard for Socket.IO namespaces
  • ✅ Full TypeScript declarations; ESM-only

Requirements

| Dependency | Version | | ---------- | ------- | | Node.js | ≥ 20 | | ioserver | ≥ 2.0.0 | | jose | ≥ 6.0.0 |


Installation

# npm
npm install ioserver-oidc

# pnpm
pnpm add ioserver-oidc

# yarn
yarn add ioserver-oidc

jose is bundled as a direct dependency — no extra installation required.


Quick start

1. Register the config manager

import {
  OidcConfigManager,
  OidcHttpMiddleware,
  OidcSocketMiddleware,
  OidcSocketAdminMiddleware,
} from "ioserver-oidc";
import { IOServer } from "ioserver";

const server = new IOServer({
  /* your IOServer options */
});

// Reads AUTH_SERVICE_URL + AUTH_SERVICE_APP_SLUG from process.env
server.addManager({ name: "oidcConfig", manager: OidcConfigManager });

2. Protect HTTP controllers

server.addController({
  name: "profile",
  controller: ProfileController,
  middlewares: [OidcHttpMiddleware], // ← JWT-required
  prefix: "/profile",
});

3. Protect Socket.IO services

// Any authenticated user
server.addService({
  name: "chat",
  service: ChatService,
  middlewares: [OidcSocketMiddleware],
});

// Admin-only namespace
server.addService({
  name: "users",
  service: UserService,
  middlewares: [OidcSocketMiddleware, OidcSocketAdminMiddleware],
});

4. Read the injected context in your handlers

// HTTP (Fastify)
fastify.get("/me", async (request) => {
  const req = request as any;
  return { userId: req.userId, role: req.userRole };
});

// Socket.IO
socket.on("ping", () => {
  console.log(socket.userId, socket.userRole);
});

Environment variables

| Variable | Required | Default | Description | | ----------------------- | -------- | ---------------------------------- | --------------------------------------------------------------------- | | AUTH_SERVICE_URL | ✅ | — | Public base URL of your auth-service. E.g. https://auth.example.com | | AUTH_SERVICE_APP_SLUG | ✅ | — | OAuth2 client_id / app slug registered in auth-service | | AUTH_SERVICE_JWKS_URI | ❌ | <AUTH_SERVICE_URL>/api/auth/jwks | Override the JWKS endpoint | | AUTH_SERVICE_ISSUER | ❌ | <AUTH_SERVICE_URL> | Override the expected iss claim |

All variables are read once at server startup by OidcConfigManager.start().
If OidcConfigManager is not registered, each middleware reads the same variables lazily on first request (without caching between restarts).


API reference

OidcConfigManager

Extends BaseManager. Reads environment variables and exposes the resolved OidcConfig to sibling middlewares via appHandle.oidcConfig.getConfig().

server.addManager({ name: "oidcConfig", manager: OidcConfigManager });

The name must be "oidcConfig" — the middlewares look for appHandle.oidcConfig by that exact key.


OidcHttpMiddleware

Extends BaseMiddleware. Verifies the Authorization: Bearer <token> header on every inbound Fastify request.

Flow:

  1. Extracts the Bearer token from Authorization header
  2. Verifies JWT signature via JWKS (iss + aud + expiry)
  3. Calls appHandle.users.findOrCreate(sub, { email, name }) if available
  4. Rejects disabled accounts with 403
  5. Injects auth context onto the request object

Returns 401 on missing/invalid tokens, 403 on disabled accounts, 500 if user provisioning fails.


OidcSocketMiddleware

Same as OidcHttpMiddleware but for Socket.IO connections.

Token is read from (in order):

  1. socket.handshake.auth.token — preferred, set by the Vue/web client
  2. socket.handshake.headers.authorization (Bearer prefix) — fallback

Calls appHandle.session.registerSocket(userId, socketId, sub) when the session manager is available.

Rejects with new Error("ERR_AUTH_TOKEN_REQUIRED") or "ERR_AUTH_TOKEN_INVALID" on failure.


OidcSocketAdminMiddleware

Role guard. Must be placed after OidcSocketMiddleware in the middlewares array (relies on socket.roles/socket.userRole being already set).

Rejects with new Error("ERR_FORBIDDEN") when the user does not hold the "admin" role.


verifyOidcToken

Low-level function — use this if you need to verify a token outside of the IOServer middleware system.

import { verifyOidcToken } from "ioserver-oidc";

const ctx = await verifyOidcToken(rawJwt, {
  authServiceUrl: "https://auth.example.com",
  appSlug: "my-app",
});
// ctx → OidcUserContext

Throws a jose JWTVerifyError (or subclass) on any verification failure.


Types

import type { OidcConfig, OidcUserContext, OidcFeatures } from "ioserver-oidc";

OidcConfig

interface OidcConfig {
  authServiceUrl: string; // e.g. "https://auth.example.com"
  appSlug: string; // OAuth2 client_id (= app slug)
  jwksUri?: string; // Override JWKS endpoint
  issuer?: string; // Override expected `iss` claim
}

OidcUserContext

interface OidcUserContext {
  userId: string; // Local DB user ID (after findOrCreate)
  sub: string; // OIDC sub claim
  email: string | null;
  name: string | null;
  userRole: string; // First element of roles[], fallback "user"
  roles: string[];
  permissions: string[];
  features: OidcFeatures; // Record<string, unknown>
}

Request / socket context

After successful authentication the following properties are available:

| Property | Type | Source | | ------------- | ------------------------ | ----------------------- | | sub | string | JWT sub claim | | userId | string | Local DB users.id | | userRole | string | roles[0] or "user" | | roles | string[] | JWT roles claim | | permissions | string[] | JWT permissions claim | | features | Record<string,unknown> | JWT features claim |

In TypeScript, cast the Fastify request or Socket.IO socket to any (or augment the types in your app) to access these properties.


Error codes

| Code | HTTP / Socket | Meaning | | --------------------------- | ------------- | ------------------------------------------ | | ERR_AUTH_TOKEN_REQUIRED | 401 / reject | No Authorization header or auth token | | ERR_AUTH_TOKEN_INVALID | 401 / reject | JWT signature / claims verification failed | | ERR_USER_DISABLED | 403 | User account is disabled in the local DB | | ERR_USER_PROVISION_FAILED | 500 | findOrCreate threw an error | | ERR_FORBIDDEN | — / reject | User lacks the required role |


Security notes

  • Access tokens are never stored — they are verified in-memory on every request/connection using the cached JWKS.
  • JWKS keys are fetched lazily and cached per URI. The jose library automatically re-fetches keys on signature verification failure (key rotation) with a minimum 5-minute cooldown.
  • The aud (audience) claim is always validated against OidcConfig.appSlug to prevent token substitution attacks between different applications sharing the same auth-service instance.
  • The iss (issuer) claim is validated against OidcConfig.authServiceUrl (or the explicit override).

Contributing

  1. Fork the repo and create a branch: git checkout -b feat/my-feature
  2. Make your changes (TypeScript in src/)
  3. Build: pnpm run build
  4. Open a Pull Request against main

License

MIT © 2026 x42en