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

@edwinlovett/ignite-auth

v1.4.0

Published

Consumer SDK for the Ignite Toolbox unified worker — identity + brand context. Core is framework-agnostic; React hooks are an opt-in /react subpath.

Readme

@edwinlovett/ignite-auth

Consumer SDK for the Ignite Toolbox unified worker — identity + brand context.

The core client is framework-agnostic. React hooks ship from a separate @edwinlovett/ignite-auth/react subpath so non-React consumers don't pay the cost of pulling React into their bundle.

Renamed from @lovett/[email protected]. Hook names are preserved 1:1 — the workspace migration is import-path-only. The only behavioural changes are the default gateway origin (https://api.ignitetoolbelt.com) and the cookie name (ignite_session / ignite_refresh). See ADR-055 §D4.

Install

npm install @edwinlovett/ignite-auth

Quick start (vanilla)

import { createAuthClient } from "@edwinlovett/ignite-auth";

const auth = createAuthClient({
  // Optional — defaults to https://api.ignitetoolbelt.com.
  // Override during the C7 cutover window if you're pointing at a
  // preview deployment.
  gateway: "https://api.ignitetoolbelt.com",
});

const session = await auth.getSession();
if (!session) {
  auth.redirectToLogin({ redirect: window.location.href });
} else {
  console.log("signed in as", session.user.email);
}

// Subscribe to changes (token refresh, sign-out, etc.)
const unsubscribe = auth.subscribe((s) => console.log("session →", s));

// Force refresh on demand
await auth.refresh();

// Sign out
await auth.signOut();

React

import { AuthProvider, useSession, useUser } from "@edwinlovett/ignite-auth/react";
import { createAuthClient } from "@edwinlovett/ignite-auth";

const auth = createAuthClient({ gateway: "https://api.ignitetoolbelt.com" });

export function App() {
  return (
    <AuthProvider client={auth}>
      <Inner />
    </AuthProvider>
  );
}

function Inner() {
  const { user, loading, signOut } = useSession();

  if (loading) return null;
  if (!user) return <a href="/login">Sign in</a>;
  return (
    <div>
      Hello, {user.email}{" "}
      <button onClick={() => signOut()}>Sign out</button>
    </div>
  );
}

AuthProvider puts the client on context. useSession exposes user, expiresAt, loading, signOut. useUser is shorthand for useSession().user.

Brand-aware hooks (ADR-041)

import {
  useActiveBrand,
  useAvailableBrands,
  useAvailableGroups,
  useTaxonomies,
} from "@edwinlovett/ignite-auth/react";

function BrandPicker({ slug }: { slug?: string }) {
  const brand = useActiveBrand(slug);     // current brand (URL-driven)
  const brands = useAvailableBrands();    // every brand the user can reach
  const groups = useAvailableGroups();    // picker grouping metadata
  const taxonomies = useTaxonomies();     // tenant-scoped tag dictionary
  // ...
}

The deprecated useActiveClient alias (ADR-041 D14) is also exported and emits a dev-only console warning — migrate to useActiveBrand at your leisure.

API

createAuthClient(opts) → AuthClient

| Option | Type | Default | | |---|---|---|---| | gateway | string | https://api.ignitetoolbelt.com | base origin of the unified worker; trailing slash stripped | | fetch | typeof fetch | global fetch | inject for tests/instrumentation | | refreshLeadSeconds | number | 60 | refresh this many seconds before token expiry |

AuthClient

  • getSession() → Promise<Session | null> — first call hits the gateway; subsequent calls return cached value
  • peekSession() → Session | null — synchronous, may be null pre-load
  • isAuthenticated() → boolean
  • subscribe(listener) → unsubscribe
  • redirectToLogin({ redirect? }) — full-page redirect to /login
  • refresh() → Promise<Session | null>
  • signOut() → Promise<void> — calls /auth/logout; the worker clears the ignite_session / ignite_refresh cookies
  • getAvailableBrands() → AvailableBrand[]
  • getAvailableGroups() → JwtBrandGroup[]
  • getActiveBrand() → AvailableBrand | null — landing preference
  • findBrandBySlug(slug) → AvailableBrand | null
  • switchBrand(brandId) → Promise<Session | null>
  • grantSelfBrandAccess(brandId, role?) → Promise<Session | null>
  • listTaxonomies() → Promise<TaxonomyWithValues[]> — memoised per session

How it works

The SDK assumes a session cookie on the parent domain (e.g. .ignitetoolbelt.com). It calls GET /auth/session to read the current session, attempts POST /auth/refresh once on a 401, and schedules a proactive refresh ~60s before expiry. There's no localStorage — HttpOnly cookies (ignite_session / ignite_refresh) are the source of truth; the SDK only mirrors them in memory.

Cross-origin requirements

For SSO across sibling subdomains the unified worker is configured with:

  • COOKIE_DOMAIN=.ignitetoolbelt.com (or your equivalent)
  • ALLOWED_ORIGINS including each consumer's exact origin

The SDK sends credentials: 'include' on every gateway call.


Ecosystem apps: SSO + offline verification (ADR-134)

Two extra subpaths, for apps that are not the Ignite workspace. They're independent: /sso is dependency-free and isomorphic (~1 KB); /verify is server-only and the sole importer of jose. Import only what you need.

The login page (/react) — don't build your own

<IgniteLoginPage /> is the canonical Ignite sign-in screen. Render it and you get the SSO flow, the error semantics, and the layout — consistent with every other Ignite app.

import { IgniteLoginPage } from '@edwinlovett/ignite-auth/react'

<IgniteLoginPage productName="Meta Ads Audit" redirect={location.origin + '/dashboard'} />

No CSS import, no design-system dependency. Theme it by mapping your tokens onto its CSS custom properties:

<IgniteLoginPage style={{ '--ia-accent': '#2563eb', '--ia-radius': '10px' } as CSSProperties} />

--ia-accent, --ia-accent-fg, --ia-fg, --ia-muted, --ia-border, --ia-card, --ia-page, --ia-radius, --ia-danger. Plus className (root) and per-part classes (.ia-card, .ia-sso, .ia-input, .ia-submit). Dark mode follows prefers-color-scheme unless you override.

The email field is hidden by default — the passwordless path currently admits nobody (the corporate domains are SSO-only). emailFallback re-enables it when the client tier lands.

@edwinlovett/ignite-auth/sso — start the Microsoft sign-in flow

SSO is a full-page navigation (the start route 302s to Microsoft), never a fetch. Build the URL here so your app can't drift from the worker's contract.

import { ssoStartUrl } from '@edwinlovett/ignite-auth/sso'

window.location.assign(
  ssoStartUrl({ redirect: window.location.origin + '/dashboard' }),
)

redirect must be an absolute URL on a host the worker allows (ALLOWED_REDIRECT_HOSTS) — pass window.location.origin + path, not a bare path. Pass ext: true from a Chrome-extension popup to land on a "signed in, close this window" page instead of redirecting.

React apps with no design system can use the batteries-included button:

import { SignInWithMicrosoft } from '@edwinlovett/ignite-auth/react'

<SignInWithMicrosoft />                       // returns to the current page
<SignInWithMicrosoft redirect="https://app.ignitetoolbelt.com/home" />

Signing out (/sso) — clearing your own cookie is NOT enough

The most common ecosystem-app bug: you clear your session cookie, but the shared ignite_session survives on .ignitetoolbelt.com, your next request verifies it, and the user is instantly signed back in. Sign-out becomes a no-op that looks like it worked.

You also can't fix it by expiring ignite_session yourself — that drops the cookie from the browser but does not revoke the session in D1 (the refresh token stays live). Only the gateway can revoke.

import { logoutUrl, logout } from '@edwinlovett/ignite-auth/sso'

// Link / redirect (server-rendered friendly, no CORS needed):
<a href={logoutUrl({ redirect: 'https://myapp.ignitetoolbelt.com/' })}>Sign out</a>

// Or a credentialed POST, to stay on the page (SPA):
await logout()

Both revoke the session server-side and clear ignite_session + ignite_refresh. Afterwards, drop your own app cookie/state too.

Microsoft session: federated defaults to false — the user leaves Ignite but stays signed into their work Microsoft account (so "Sign in with Microsoft" re-auths with no prompt). Pass logoutUrl({ federated: true }) to also end the Entra session — for shared machines, or an explicit "sign out everywhere".

@edwinlovett/ignite-auth/verify — trust the cookie, server-side

Any app on *.ignitetoolbelt.com automatically receives the ignite_session cookie (it's scoped to the registrable domain). This verifies it is real — signature, algorithm, issuer, audience, expiry — against the gateway's public JWKS. No secret, no per-request call to us.

import { createSessionVerifier } from '@edwinlovett/ignite-auth/verify'

const auth = createSessionVerifier()   // defaults to the prod gateway's JWKS

// Cloudflare Workers / Hono
const session = await auth.verifyRequest(request)      // or c.req.raw
// Express / Vercel
const session = await auth.verifyRequest(req)
// → { userId, tenantId, orgId, role, sessionId, lastActiveBrandId, expiresAt }

verify/verifyRequest throw SessionVerificationError (with a .reason) so a forgotten try/catch fails closed. Use tryVerify/tryVerifyRequest for a null-on-failure variant.

alg is pinned to EdDSA, so alg: none and HS256-with-public-key confusion are rejected outright. The JWKS is fetched once and cached (and refetched on an unknown kid, so key rotation just works).

Two limits — read these

  1. Revocation is invisible to an offline verifier. The worker checks D1 revoked_at on every call and revokes instantly; this cannot. It will accept a revoked-but-unexpired token for up to the 15-minute access TTL. That short TTL is the bound. For sensitive or destructive operations, also make an online check (GET /auth/session).
  2. This is authentication, not authorization. A valid token proves the bearer is a signed-in Ignite user. It does not prove they may use your app — a shared SSO cookie serves every app on the domain by design. Gate your app separately.

Not a security control: publishing this package privately would gate who can download the code, never who can authenticate. The SSO endpoints are public browser redirects by necessity, and a browser SDK cannot hold a secret. The real controls are the ones above: signature, alg, iss, aud, exp.

License

UNLICENSED — distribution + use by Ignite Toolbox consumers only.