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

@moon-x/node-sdk

v0.3.0

Published

MoonX server-side SDK for Node.js. Verify MoonX-issued access + identity tokens with zero runtime dependencies.

Downloads

395

Readme

@moon-x/node-sdk

MoonX server-side SDK for Node.js. Verify MoonX-issued access and identity tokens with zero runtime dependencies.

Works in Node 18+, Cloudflare Workers, Vercel Edge, Bun, and Deno — anywhere globalThis.crypto.subtle and fetch are available.

Install

npm i @moon-x/node-sdk
# or
pnpm add @moon-x/node-sdk

Quick start

import { MoonXClient } from "@moon-x/node-sdk";

const moonx = new MoonXClient({
  publishableKey: process.env.MOONX_PUBLISHABLE_KEY!,
  issuer: process.env.MOONX_AUTH_ISSUER, // optional, defaults to https://api.moonx-dev.com
});

// In a Next.js route / Express handler / etc.
const session = await moonx.auth.verifySession({
  accessToken: req.headers["x-moonx-access-token"]!,
  identityToken: req.headers["x-moonx-identity-token"],
});

// session.access.sub        — user id
// session.access.sid        — session id
// session.identity?.email   — OIDC profile claim (present when identity token forwarded)
// session.identity?.name
// session.identity?.picture

What it does

When a MoonX-authenticated user calls your backend, the request carries one or both of:

| Header | Token | Carries | |---|---|---| | X-MoonX-Access-Token | Access token | Minimal authorization claims (sub, sid, aud, iss, exp) | | X-MoonX-Identity-Token | Identity token | OIDC profile claims (email, name, picture, etc.) |

verifySession() runs the full canonical check on both:

  1. Pin audience to your app id (rejects tokens from other MoonX apps even if cryptographically valid).
  2. Pin issuer to your configured MoonX deployment.
  3. Fetch the per-app public JWKS from MoonX (cached 5 min by default).
  4. Verify the ES256 signature against the fetched key.
  5. Check the token hasn't expired.
  6. (Identity token path) Verify the identity token's sub matches the access token's sub — closes the swap-in-someone-else's-identity-token attack.

No shared secret with MoonX is required. The verification runs entirely against the public JWKS.

API

new MoonXClient(config)

Construct once per process. Holds in-memory caches; reuse across requests.

| Field | Type | Required | Default | |---|---|---|---| | publishableKey | string | Yes | — | | secretKey | string | No (v0.1 unused) | — | | issuer | string | No | https://api.moonx-dev.com | | baseUrl | string | No | Same as issuer | | jwksTtlMs | number | No | 300_000 (5 min) | | appResolveTtlMs | number | No | 3_600_000 (1 hour) | | fetch | typeof fetch | No | globalThis.fetch |

The publishable key (moon_pk_*) is the same one your iframe / browser SDK uses. It's safe to put in any env var, but in node-sdk it typically reads from a server-only env var alongside the secret key. The secret key (moon_sk_*) field is reserved for v0.2 (data + swaps modules).

client.auth.verifyAccessToken(token)

Returns Promise<AccessTokenClaims>. Throws a MoonXError subclass on any failure.

client.auth.verifyIdentityToken(token)

Returns Promise<IdentityTokenClaims>. Throws a MoonXError subclass on any failure.

client.auth.verifySession({ accessToken, identityToken? })

Returns Promise<{ access: AccessTokenClaims, identity: IdentityTokenClaims | null }>. Identity is null when the caller didn't forward an identity token.

Canonical entry point — use this from request handlers. Cross-checks the two tokens' subjects when both are present.

Errors

All thrown errors extend MoonXError, which carries a .code property for programmatic handling:

import {
  MoonXError,
  ExpiredTokenError,
  InvalidTokenError,
  AudienceMismatchError,
  SubjectMismatchError,
  // ...
} from "@moon-x/node-sdk";

try {
  await moonx.auth.verifySession({ accessToken, identityToken });
} catch (err) {
  if (err instanceof ExpiredTokenError) {
    return new Response("session expired", { status: 401 });
  }
  if (err instanceof MoonXError) {
    return new Response(err.message, { status: 401 });
  }
  throw err;
}

Error codes

| Class | .code | Meaning | |---|---|---| | MalformedTokenError | malformed_token | Not a well-formed JWT | | InvalidTokenError | invalid_signature | Signature didn't verify against the JWKS | | ExpiredTokenError | expired_token | exp claim is in the past | | AudienceMismatchError | audience_mismatch | Token's aud ≠ this client's app id | | IssuerMismatchError | issuer_mismatch | Token's iss ≠ configured issuer | | SubjectMismatchError | subject_mismatch | Identity token's sub ≠ access token's sub | | KidMismatchError | kid_mismatch | Token's kid ≠ current JWKS key id (likely rotation) | | UnsupportedAlgorithmError | unsupported_algorithm | Token uses something other than ES256 | | JwksFetchError | jwks_fetch_failed | Couldn't fetch the JWKS from MoonX | | AppResolutionError | app_resolution_failed | Publishable-key → app-id lookup failed | | ConfigurationError | configuration_error | Bad MoonXClient config (e.g. swapped pk/sk) |

Type imports

AccessTokenClaims, IdentityTokenClaims, BaseTokenClaims, and VerifiedSession are also exported from @moon-x/core/types — the single source of truth shared with the browser + RN SDKs. Either import path works:

// From node-sdk (convenience re-export):
import type { AccessTokenClaims } from "@moon-x/node-sdk";

// From core directly (matches the browser SDK call sites):
import type { AccessTokenClaims } from "@moon-x/core/types";

Why no JWT library?

Auth providers ship server SDKs partly to abstract away jose-style JWT libraries, but every dependency is a supply-chain surface. This SDK uses only Web Crypto (globalThis.crypto.subtle) and fetch, both Node 18+ standard library. Result: a single npm install line on your audit.

Roadmap

  • v0.1 (current) — auth: verifyAccessToken, verifyIdentityToken, verifySession.
  • v0.2 (pending MX-124) — adds client.tokens.*, client.wallets.*, client.swaps.* once the backend's secret-key middleware lands.