npm.io
0.6.0 • Published 42m ago

@nodii/auth-sdk

Licence
MIT
Version
0.6.0
Deps
1
Size
963 kB
Vulns
0
Weekly
0

@nodii/auth-sdk

User-JWT verifier + REST issuer-client + Hono/Express middleware for services that consume nodii-auth. Verifies RS256 tokens against the JWKS published by nodii-auth at https://auth.<env>.nodii.co/.well-known/jwks.json.

bun add @nodii/auth-sdk
# or
npm install @nodii/auth-sdk

What you get

Sub-path Purpose
@nodii/auth-sdk/verify NodiiAuthVerifier — RS256-only JWT verifier
@nodii/auth-sdk/client NodiiAuthClient — typed REST client for nodii-auth's HTTP API
@nodii/auth-sdk/hono requireAuth() Hono middleware
@nodii/auth-sdk/express requireAuth() Express 4/5 middleware (for legacy services)
@nodii/auth-sdk/types NodiiClaims, LoginResult, AuthSdkError, error code unions

Verifier — Hono

import { Hono } from "hono";
import { NodiiAuthVerifier } from "@nodii/auth-sdk/verify";
import { requireAuth, type NodiiAuthEnv } from "@nodii/auth-sdk/hono";

const verifier = new NodiiAuthVerifier({
  issuer: "https://auth.prod.nodii.co",
  jwksUri: "https://auth.prod.nodii.co/.well-known/jwks.json",
});

const app = new Hono<NodiiAuthEnv>();
app.use("/api/*", requireAuth({ verifier }));
app.get("/api/me", (c) => c.json({ claims: c.get("nodiiClaims") }));

requireAuth reads from Authorization: Bearer <token> by default. Pass source: "cookie" + cookieName: "nodii_at" to read from a cookie instead, or source: "both" to fall back from header to cookie. Pass requiredPermissions: ["billing.write"] to gate the route — missing permissions return 403 missing_permission.

Verifier — Express 5 (legacy services)

import express from "express";
import { NodiiAuthVerifier } from "@nodii/auth-sdk/verify";
import { requireAuth, type NodiiAuthRequest } from "@nodii/auth-sdk/express";

const verifier = new NodiiAuthVerifier({
  issuer: "https://auth.prod.nodii.co",
  jwksUri: "https://auth.prod.nodii.co/.well-known/jwks.json",
});

const app = express();
app.use(requireAuth({ verifier }));
app.get("/api/me", (req: NodiiAuthRequest, res) => {
  res.json({ claims: req.nodiiClaims });
});

Both middlewares emit the same error envelope shape:

{ "error": true, "code": "missing_token" }

Stable error codes:

  • missing_token — no Authorization header / cookie present
  • invalid_token — malformed JWT
  • expired_tokenexp in the past
  • invalid_algorithm — alg ≠ RS256 (or alg=none)
  • invalid_signature — signature did not verify against the resolved key
  • missing_kid / no_matching_key — JWKS resolution failed
  • invalid_issueriss did not match the configured issuer
  • missing_principal_kind — token lacks the principal_kind claim
  • missing_permission — token's permissions did not include all required
  • network_error — JWKS fetch failed (timeout / connection refused)

All map to 401 except missing_permission, which is 403.

REST client

import { NodiiAuthClient, AuthSdkError } from "@nodii/auth-sdk/client";

const auth = new NodiiAuthClient({
  baseUrl: "https://auth.prod.nodii.co",
});

try {
  const result = await auth.login({
    email: "alice@example.com",
    password: "...",
    tenantId: "t-123",
  });
  if (result.kind === "challenge") {
    // Cognito sent back a challenge (NEW_PASSWORD_REQUIRED, MFA, …)
    // — pass result.challengeSession into the matching cognito.* call.
  } else {
    // result.token is the user JWT, valid for ~15min.
  }
} catch (e) {
  if (e instanceof AuthSdkError && e.code === "rest_error") {
    // Server returned a non-2xx envelope { error: true, code, message? }.
    // The issuer's envelope code is exposed structured for dispatch:
    if (e.restEnvelopeCode === "invalid_credentials") {
      // ...
    }
  }
}

Methods:

  • login() / refresh() / logout()
  • passwordReset.request() / passwordReset.confirm()
  • cognito.respondNewPassword() / cognito.respondMfa() / cognito.confirmForgotPassword()

Security guarantees

  • Algorithm allowlist is hard-pinned to ["RS256"]. alg=none, alg=HS256, and alg=RS384 are all rejected with invalid_algorithm.
  • kid is required on every incoming token. Tokens without kid, or with a kid not present in the JWKS, are rejected with missing_kid / no_matching_key.
  • Issuer is compared exact-string against the configured issuer.
  • The full adversarial test matrix lives in tests/verifier.test.ts — including alg=none, HS256-with-public-key (CVE-2018-7489 shape), RS384, tampered payloads, expired tokens, wrong issuer, and missing principal_kind.

Peer dependencies

hono (^4.0.0) and express (^5.0.0) are listed as optional peer dependencies — only install the one your service uses. The verifier and REST client have no framework dependency.

The package ships dual ESM + CJS builds, so legacy CommonJS services can require("@nodii/auth-sdk/express") without ERR_REQUIRE_ESM.

Express 4 is not currently in scope (no CI test pass). If you need v4 support, open an issue with the consumer service's repro.

Development

This package lives in the nodii-libs monorepo. From the repo root:

bun install
bun run --cwd packages/auth-sdk test
bun run --cwd packages/auth-sdk typecheck
bun run --cwd packages/auth-sdk build

Keywords