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

@ogcio/api-auth

v6.0.0

Published

Fastify plugin that verifies [Logto](https://logto.io/) JWTs, checks route permissions against token scopes, and decorates the Fastify instance with auth helpers.

Readme

@ogcio/api-auth

Fastify plugin that verifies Logto JWTs, checks route permissions against token scopes, and decorates the Fastify instance with auth helpers.

Installation

pnpm add @ogcio/api-auth fastify fastify-plugin @ogcio/shared-errors

Optional dependencies (required only when piiHasher is provided):

pnpm add @ogcio/pii-utils @aws-sdk/client-secrets-manager

Plugin registration

import Fastify from "fastify";
import apiAuthPlugin from "@ogcio/api-auth";

const app = Fastify({ logger: true });

await app.register(apiAuthPlugin, {
  jwkEndpoint: "https://your-logto-tenant.logto.app/oidc/jwks",
  oidcEndpoint: "https://your-logto-tenant.logto.app/oidc",
});

Options

| Option | Type | Required | Description | |---|---|---|---| | jwkEndpoint | string | ✓ | URL of the Logto JWKS endpoint | | oidcEndpoint | string | ✓ | Logto OIDC issuer URL | | getLocalJwksFn | () => JSONWebKeySet \| undefined | — | Returns a locally cached JWKS to avoid round trips | | storeLocalJwkSetFn | (jwks: JSONWebKeySet) => Promise<void> | — | Persists a freshly fetched JWKS | | piiHasher | PiiHasher | — | When provided, computes request.userData.pseudoUser using HMAC hashing of the user ID |

Decorators

After registration the plugin adds the following to the Fastify instance and request objects.

fastify.checkPermissions(request, reply, permissions, matchConfig?)

Verifies the bearer token, checks that the token scopes satisfy the required permissions, and stores the resolved user data on request.userData. Throws 401 when the authorization header is missing or the token is unverifiable, and 403 when permissions are insufficient.

app.get("/payments", async (req, reply) => {
  await app.checkPermissions(req, reply, ["payments:payment:create"]);
  // req.userData is now populated
  return { userId: req.userData!.userId };
});

// AND-match (all permissions must be present)
app.delete("/payments/:id", async (req, reply) => {
  await app.checkPermissions(req, reply, ["payments:payment:read", "payments:payment:delete"], {
    method: "AND",
  });
  return { ok: true };
});

request.ensureUserIsSet()

Synchronous guard that returns request.userData as non-nullable ExtractedUserData, or throws 401 when auth has not yet been performed for the request. Use it as a compact assertion after checkPermissions or getScopes.

app.get("/profile", async (req, reply) => {
  await app.checkPermissions(req, reply, ["profile:read"]);
  const user = req.ensureUserIsSet(); // ExtractedUserData — non-nullable
  return { userId: user.userId };
});

request.ensureIsPublicServant()

Calls ensureUserIsSet() first, then asserts the user belongs to an organisation (i.e. the JWT audience is an org URN). Returns { organisationId: string } on success, or throws 403.

app.get("/admin/report", async (req, reply) => {
  await app.checkPermissions(req, reply, ["report:read"]);
  const { organisationId } = req.ensureIsPublicServant();
  return { organisationId };
});

request.ensureIsCitizen()

Calls ensureUserIsSet() first, then asserts the user is not associated with any organisation. Throws 403 when userData.organizationId is set.

app.get("/citizen/profile", async (req, reply) => {
  await app.checkPermissions(req, reply, ["profile:read"]);
  req.ensureIsCitizen();
  return { userId: req.userData!.userId };
});

request.getScopes()

Extracts and normalizes the token scopes without enforcing any route permissions. Safe to call from middleware or routes that need to inspect scopes before deciding whether to gate access. Returns:

  • { scopes: string[] } — on a valid, verifiable bearer token
  • { invalidAuthorizationHeader: true } — when the header is missing, malformed, or the token cannot be verified

Populates request.userData as a side-effect when verification succeeds (same behavior as checkPermissions).

app.get("/me/scopes", async (req) => {
  const result = await req.getScopes();

  if ("invalidAuthorizationHeader" in result) {
    return { authenticated: false };
  }

  return { scopes: result.scopes };
});

request.userData

Populated by checkPermissions and getScopes after a successful auth flow.

interface ExtractedUserData {
  userId: string;
  accessToken: string;
  isM2MApplication: boolean;
  scopes: string[];          // normalized string[] from JWT scope claim
  organizationId?: string;   // set when the token audience is an org URN
  signInMethod?: string;
  pseudoUser?: {             // only when piiHasher is configured
    id: string;
    version: string;
  };
}

Starts as undefined before any auth decorator runs. Within a single request, calling getScopes() then checkPermissions() with the same bearer token reuses the already-extracted userData without re-verifying the JWT.

Optional: PII pseudonymisation

Pass a piiHasher instance (compatible with @ogcio/pii-utils PiiHasher) to have both checkPermissions and getScopes compute a pseudonymous identifier for the user. Requires @aws-sdk/client-secrets-manager to be installed.

import { PiiHasher } from "@ogcio/pii-utils";

const piiHasher = new PiiHasher({ ... });
await piiHasher.warmup();

await app.register(apiAuthPlugin, {
  jwkEndpoint: "...",
  oidcEndpoint: "...",
  piiHasher,
});

// After checkPermissions / getScopes:
// req.userData.pseudoUser === { id: "<hmac-hash>", version: "v1" }

Low-level helpers

These stateless functions are exported for use outside of a Fastify request lifecycle.

import { checkPermissions, getScopes, getPermissions } from "@ogcio/api-auth";

// Verify JWT and check permissions (throws on failure)
const userData = await checkPermissions("Bearer <token>", config, ["payments:payment:create"]);

// Return normalized scopes without enforcing permissions
const scopes = await getScopes("Bearer <token>", config);

// @deprecated — alias for getScopes
const scopes = await getPermissions("Bearer <token>", config);

Helper: ensureUserCanAccessUser

Asserts that the logged-in user may access the requested user's data (either by matching userId or by having an organizationId).

import { ensureUserCanAccessUser } from "@ogcio/api-auth";

app.get("/users/:userId/profile", async (req) => {
  await app.checkPermissions(req, reply, ["profile:read"]);
  const accessingUser = ensureUserCanAccessUser(req.userData, req.params.userId);
  // ...
});