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

@glydi/passkey-server

v0.1.0

Published

Lightweight, framework-agnostic WebAuthn challenge + verification handlers for Glide. Built on @simplewebauthn/server.

Readme

@glydi/passkey-server

Lightweight, framework-agnostic WebAuthn challenge + verification handlers for Glide. Built on @simplewebauthn/server.

Install

Glide is not yet published to npm. Install from a packed tarball or via pnpm link.

Tarball (recommended):

{
  "dependencies": {
    "@glydi/passkey-server": "file:../glide/dist-packs/glydi-passkey-server-0.1.0.tgz",
    "@glydi/passkey-core":   "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
  },
  "pnpm": {
    "overrides": {
      "@glydi/passkey-core": "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
    }
  }
}

The pnpm.overrides entry for @glydi/passkey-core prevents pnpm from trying to fetch the transitive peer from the npm registry. See docs/DISTRIBUTION.md for full tarball and pnpm link instructions.

Forthcoming: npm install @glydi/passkey-server will be the public form once the package is published. It is not yet available on npm.

Minimal Usage

The correct pattern for Next.js App Router is lazy initialization — wrapping createGlideServer and createPasskeyRouteHandler inside functions that construct on first call rather than at module scope. This is required because createInMemoryStore() throws under NODE_ENV=production, and next build evaluates module-level code with NODE_ENV=production — so eager construction breaks the build.

lib/glide.ts — lazy server initializer:

import { createGlideServer, createInMemoryStore } from "@glydi/passkey-server";

const RP_ID = process.env.GLIDE_RP_ID ?? "localhost";
const ORIGIN = process.env.GLIDE_ORIGIN ?? "http://localhost:3000";

let _glide: ReturnType<typeof createGlideServer> | null = null;

export function getGlide(): ReturnType<typeof createGlideServer> {
  if (_glide === null) {
    _glide = createGlideServer({
      rpName: "My App",
      rpID: RP_ID,
      origin: ORIGIN,
      store: createInMemoryStore(),
    });
  }
  return _glide;
}

app/api/passkey/[action]/route.ts — lazy route handler:

import { createPasskeyRouteHandler } from "@glydi/passkey-server";
import { getGlide } from "../../../../lib/glide";
import {
  getOrCreateSessionId,
  getSessionUserId,
  startUserSession,
} from "../../../../lib/session";

let handler: ((request: Request) => Promise<Response>) | null = null;

export function POST(request: Request): Promise<Response> {
  if (handler === null) {
    handler = createPasskeyRouteHandler({
      server: getGlide(),
      getSessionId: () => getOrCreateSessionId(),
      getUserId: () => getSessionUserId(),
      onAuthSuccess: (user) => startUserSession(user.id),
    });
  }
  return handler(request);
}

The handler is a Web-standard (request: Request) => Promise<Response> — it mounts directly as a Next.js App Router POST export. It dispatches to the correct ceremony handler by reading the last URL segment: register-begin, register-finish, authenticate-begin, authenticate-finish.

See the Quickstart for the full Next.js wiring including session helpers and the client button.

API

createGlideServer(config)

Creates the four WebAuthn ceremony handlers. Returns an object with:

  • registerBegin(ctx: BeginContext): Promise<PublicKeyCredentialCreationOptionsJSON>
  • registerFinish(ctx: FinishContext): Promise<GlideAuthResult>
  • authenticateBegin(ctx: BeginContext): Promise<PublicKeyCredentialRequestOptionsJSON>
  • authenticateFinish(ctx: FinishContext): Promise<GlideAuthResult>

GlideServerConfig fields:

| Field | Type | Required | Description | |-------|------|----------|-------------| | rpName | string | yes | Human-readable Relying Party name shown in some UIs. | | rpID | string | yes | Registrable domain, e.g. "localhost" or "example.com". | | origin | string \| string[] | yes | Exact expected origin(s), e.g. "http://localhost:3000". | | store | GlideStore | yes | Storage implementation (challenges, credentials, users). | | challengeTtlMs | number | no | Challenge TTL in ms. Default 120000 (2 min). | | userVerification | "required" \| "preferred" \| "discouraged" | no | WebAuthn user verification. Default "preferred". |

createPasskeyRouteHandler(config)

Creates a single Web-standard request handler that dispatches all four passkey routes. Returns (request: Request) => Promise<Response>.

PasskeyRouteHandlerConfig fields:

| Field | Type | Required | Description | |-------|------|----------|-------------| | server | ReturnType<typeof createGlideServer> | yes | The Glide server instance. | | getSessionId | (request: Request) => Promise<string> | yes | Returns the pre-auth session id (from an HttpOnly cookie). | | getUserId | (request: Request) => Promise<string \| undefined> | no | Returns the authenticated user id for add-a-device flows. Must only return the currently authenticated user. | | onAuthSuccess | (user: GlideAuthResult["user"], request: Request) => Promise<void> | no | Called after successful authentication to mint your session. |

Response codes: 405 (non-POST), 404 (unknown action), 400 (GlideServerError), 500 (unexpected).

createInMemoryStore()

Creates an in-memory GlideStore for development and testing. Returns a GlideStore implementing challenges, credentials, and users sub-stores.

Dev only: createInMemoryStore() loses all data on process restart.

Production guard: Under NODE_ENV=production, createInMemoryStore() throws unless GLIDE_ALLOW_INMEMORY="1" is set. This prevents accidentally running with an ephemeral store in production.

For production, implement a persistent store against the GlideStore interface — see STORE.md for the contract and invariants, and apps/demo/lib/sqlite-store.ts for a SQLite reference implementation.

assertSecureSecret(envVar, value)

function assertSecureSecret(envVar: string, value: string | undefined): asserts value is string

Validates that a secret environment variable is safe to use. Call this lazily inside your getSecret() function (at request time, not at module scope) so it does not fire during next build.

  • Throws if value is undefined, an empty string, or equals the dev placeholder "dev-only-insecure-secret-change-me".
  • Warns (does not throw) if value.length < 32.

Exported types

GlideServerConfig, BeginContext, FinishContext, GlideAuthResult, GlideServerError, GlideStore, ChallengeStore, CredentialStore, UserStore, StoredCredential, GlideUserRecord, AuthenticatorTransportFuture, PasskeyRouteHandlerConfig

Security

HARD-01: GLIDE_SESSION_SECRET (required)

The session module must sign user session cookies with a strong secret. Set this in your .env.local before starting the server:

GLIDE_SESSION_SECRET=$(openssl rand -base64 32)

assertSecureSecret("GLIDE_SESSION_SECRET", raw) is called at request time when the session is first signed or verified. If the value is unset, empty, or equals the dev placeholder, it throws immediately — the first passkey attempt will fail with an internal server error. Secrets shorter than 32 characters produce a warning.

See SECURITY.md for the full token-storage and session rules.

HARD-02: In-memory store is dev only

createInMemoryStore() is blocked under NODE_ENV=production (unless GLIDE_ALLOW_INMEMORY="1" is explicitly set). For production, replace it with a persistent GlideStore implementation. See STORE.md for the interface contract and the store skeleton.

getUserId callback security

The optional getUserId(request) callback is the add-a-device seam. It must only return the id of the currently authenticated user — derived from a tamper-evident session (HttpOnly cookie or signed token), never from an unsigned query parameter. Returning any other user's id allows an attacker to attach their passkey to a victim's account.

Links