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

@teikyo/oauth

v0.2.0

Published

OAuth token verifiers and protected resource metadata for Sozai HTTP servers

Readme

@teikyo/oauth

OAuth access token verifiers and RFC 9728 protected resource metadata helpers for Sozai HTTP servers.

Install

pnpm add @teikyo/oauth @sozai/http-server hono

Usage

import { createDIDVerifier, createIssuerVerifier, createJWKSVerifier } from '@teikyo/oauth'

const verifier = createIssuerVerifier({
  'https://as.example.com': createJWKSVerifier({ issuer: 'https://as.example.com' }),
})
const didVerifier = createDIDVerifier({ allowedIssuers: ['did:key:...'] })
  • createJWKSVerifier verifies RS256/ES256 JWTs against an issuer's JWKS. Fetches time out after 10 s by default (fetchTimeoutMs). Key lifetimes from Cache-Control: max-age are floored at 30 s, and when a refresh fails the cached keys keep serving for up to an hour past expiry, without refetching for 30 s after each failed refresh. Concurrent requests share one refresh.
  • createDIDVerifier verifies DID-signed tokens; allowedIssuers restricts the accepted DIDs (a list or a predicate) and a sub differing from iss is rejected.
  • createIssuerVerifier routes tokens to a verifier by their iss, rejecting unknown issuers before any fetch.

Claim policies

The two verifiers apply different claim policies:

  • JWKS verifier: standard claims checked against the configured issuer -- iss matches, aud includes the protected resource, exp is required, exp/nbf are checked with a clock-skew tolerance (30 s by default), and an iat further in the future than that tolerance is rejected -- and sub must be a string. Only RS256 and ES256 signatures are accepted. The typ header must be absent, JWT or at+jwt (compared case-insensitively, with an optional application/ prefix), so other JWT kinds the authorisation server signs, such as logout or ID tokens, are rejected.
  • DID verifier: the signing DID is the issuer. allowedIssuers (a list or a predicate, on the normalised DID) restricts it, sub must equal iss when present, aud must include the resource, and exp is required. exp, nbf and iat use the same clock-skew tolerance as the JWKS verifier.

Verifiers throw TokenVerificationError for credential failures only. Its messages are fixed strings and never carry fetched or driver text.

Resource plugin

Plugin name: oauth:resource (exported as OAUTH_RESOURCE). oauthResourcePlugin registers the oauth:resource plugin on a @sozai/http-server. It serves the RFC 9728 metadata document and exposes requireBearer() middleware for per-route protection (hono is a peer dependency).

import { createServer, definePlugin } from '@sozai/http-server'
import { getAuthInfo, OAUTH_RESOURCE, oauthResourcePlugin } from '@teikyo/oauth'

const api = definePlugin({
  name: 'api',
  dependsOn: [OAUTH_RESOURCE],
  setup(ctx) {
    const { requireBearer } = ctx.use(OAUTH_RESOURCE)
    ctx.route('post', '/mcp', requireBearer({ scopes: ['read'] }), (c) =>
      c.json({ subject: getAuthInfo(c).subject }),
    )
  },
})

const server = await createServer({
  plugins: [
    oauthResourcePlugin({
      resource: 'https://mcp.example.com/mcp',
      authorizationServers: ['https://as.example.com'],
      verifier,
    }),
    api,
  ],
})

| Option | Type | Description | | --- | --- | --- | | resource | string | Absolute http(s) URL identifying the protected resource, without query or fragment. | | authorizationServers | Array<string> | Authorization server issuers advertised in the metadata. Must not be empty, and each entry must be an absolute http(s) URL. | | verifier | OAuthTokenVerifier | Verifies bearer tokens, for example the result of createIssuerVerifier(). |

  • resource must be an absolute http(s) URL and authorizationServers non-empty; the factory throws otherwise.
  • A missing token, a non-Bearer scheme, an empty token or several tokens answer 401 with WWW-Authenticate: Bearer resource_metadata="..." and no error parameter (RFC 6750). The bearer scheme is case-insensitive.
  • A TokenVerificationError answers 401 with error="invalid_token", its fixed message as error_description and resource_metadata. Missing scopes answer 403 with error="insufficient_scope", scope and resource_metadata. Every header value is quoted with \ and " escaped.
  • Any other verifier error is logged and answers 503 with a fixed body and no WWW-Authenticate; the cause is never returned.
  • Custom verifiers must import TokenVerificationError from the same @teikyo/oauth instance as the plugin (checked with instanceof), otherwise their credential failures surface as 503. Control and non-ASCII characters in error_description are replaced with ?.
  • resource must not contain a query or fragment. A TokenVerificationError with code insufficient_scope answers 403.
  • On success the AuthInfo is available through getAuthInfo(c), which throws on a route without requireBearer().

Key-infrastructure failures are not credential failures: createJWKSVerifier throws a plain error and the plugin answers 503, so clients retry instead of discarding a valid token. This covers:

  • the authorization server metadata or JWKS fetch failing (network error, timeout, non-200 status, oversized or non-JSON body) when no usable cached keys exist;
  • a forced refresh after an unknown kid failing, even when other keys are cached;
  • metadata that does not match the issuer or lacks jwks_uri, a JWKS without a keys array or with too many keys;
  • a non-https jwks_uri discovered from the metadata (plain http is accepted only for loopback hosts).

Configuration errors fail at construction instead: createJWKSVerifier throws when issuer or jwksURI is not https (plain http is accepted only for loopback hosts), or when a numeric option is out of range.

Known behaviour: a malformed key published by the authorization server is the exception, reported as a TokenVerificationError, so clients see 401 rather than 503 in that case.