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

@amend-id/server

v0.2.0

Published

Sign in with Amend iD, on your server — verify Amend iD's tokens against its published keys, keep people signed in with a sealed HttpOnly cookie, refresh properly, hear back-channel logout. No dependencies.

Readme

@amend-id/server

Sign in with Amend iD, on your server. No dependencies. Node 22, Deno, Bun and the edge runtimes: everything it needs is fetch, Request/Response and Web Crypto.

The recommended shape for an app that has a server: the OAuth client lives there, the browser holds only your own HttpOnly cookie, and no token ever sits in a page.

import { createAmendIdServer, withCookies } from "@amend-id/server";

const amend = createAmendIdServer({
  issuer: "https://id.amend.id",
  clientId: "amend_app_…", // from Apps you make on the hub, or your published identity's https:// address
  redirectUri: "https://your.app/auth/amend-id",
  scope: "openid email profile",
  secret: process.env.SESSION_SECRET, // at least 32 bytes: openssl rand -base64 32
});

// GET /auth/start → send the person to Amend iD
export const start = (request) => amend.beginSignIn(request, { returnTo: "/dashboard" });

// GET /auth/amend-id → finish: the ID token is checked against Amend iD's keys, the session cookie set
export const callback = async (request) => (await amend.finishSignIn(request)).response;

// Anywhere: who is signed in? Put `setCookie` on your response every time (a refresh rotates it).
export const me = async (request) => {
  const { session, refused, setCookie } = await amend.getSession(request);
  if (!session) return withCookies(new Response(null, { status: refused === "cross_site" ? 403 : 401 }), setCookie);
  return withCookies(Response.json({ id: session.person.id, name: session.person.name }), setCookie);
};

// POST /auth/signout → the refresh token given back, the cookie forgotten
export const signOut = (request) => amend.signOut(request, { returnTo: "/" });

// POST /auth/logout → Amend iD tells you a sign-in ended elsewhere (Back-Channel Logout)
export const backchannel = (request) => amend.handleBackchannelLogout(request);

Register https://your.app/auth/logout as your app's sign-out address (on the hub: the optional "Sign-out address"; in a published identity or self-registration: backchannel_logout_uri with backchannel_logout_session_required: true). Then, the moment the person signs out everywhere, ends that session on the hub, disconnects your app or signs out of Amend iD in that browser, your server hears it and the next getSession says signed_out_elsewhere.

What it does, so that you do not have to:

  • The sign-in: discovery, PKCE, state and nonce sealed in a ten-minute cookie, the redirect back finished, the ID token verified (ES256 against Amend iD's published keys, issuer, audience, nonce, time) before anyone is called signed in, returnTo kept on your own site.
  • The session: sealed (AES-256-GCM under a key derived from your secret) in an HttpOnly, SameSite cookie, or — give it a store — a session id in the cookie and the session in your store (a table, Redis, memorySessionStore() for one process).
  • Refresh, properly: the access token is refreshed before it runs out, under a lock, the store read again first (another instance may have refreshed), a refused refresh ending the session only when the token it holds is over too.
  • Cross-site requests: anything but GET/HEAD/OPTIONS must come from your own site (Sec-Fetch-Site, else Origin), or getSession answers refused: "cross_site" and signOut answers 403.
  • Sign-out: the refresh token given back at Amend iD's revoke address (that ends that sign-in's tokens, never the person's consent), the cookies cleared, and with everywhere: true a redirect through Amend iD's own sign-out page.
  • The verifier, on its own too: createVerifier({ issuer }) checks access tokens for your API (verifyAccessToken(token, { audience }) — who, which permissions, under what consent (grant_id), from which sign-in (sid), how they signed in (amr)), ID tokens, and logout tokens.

The rules it follows are written out at https://id.amend.id/developers, "Keeping people signed in, properly".

Two APIs from one sign-in (0.2.0)

An app whose server reaches a second API as the person — Build's server reaches Amend Cloud beside its own API — names it in resources (RFC 8707). It is asked for at sign-in next to resource, a token for it is fetched with the refresh token and kept in the session, renewed with the session's own token under the same lock, and handed out by accessTokenFor. No second sign-in, no token in a page; ask for the permissions that API checks in scope.

const amend = createAmendIdServer({
  issuer: "https://id.amend.id",
  clientId: "build",
  redirectUri: "https://build.amend.id/auth/callback",
  scope: "openid email profile build:read build:write cloud:build",
  resource: "https://build.amend.id/api", // your own API: the session's token
  resources: ["https://cloud.amend.id"], // the other API this sign-in reaches
  secret: process.env.SESSION_SECRET,
});

// A request to Amend Cloud, as the person. Put `setCookie` on your response as with getSession.
export const myNotes = async (request) => {
  const { token, refused, setCookie } = await amend.accessTokenFor(request, "https://cloud.amend.id");
  if (!token) return withCookies(Response.json({ error: refused }), setCookie); // no_such_resource · no_refresh_token · refresh_refused · the session's own reasons
  const r = await fetch("https://cloud.amend.id/v1/me", { headers: { authorization: `Bearer ${token}` } });
  return withCookies(Response.json(await r.json()), setCookie);
};

A refusal for one API leaves the sign-in itself untouched: refresh_refused for a resource means this sign-in never asked for it (sign the person in again); the session's own token keeps working. Never fetch a second API's token around the library — the refresh token rotates on every use, and two users of it race and end the sign-in.