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

@ezstart/auth-sdk

v2.1.1

Published

EZAuth React SDK for authentication

Readme

@ezstart/auth-sdk

Drop-in authentication SDK for React + Express apps with email/password, OAuth, 2FA, sessions, RBAC, and API key management.

Install

npm install @ezstart/auth-sdk
# Peer deps for the components entry: react, react-dom, sonner, @ezstart/ui

Architecture note

auth-sdk is independent from pay-sdk: neither package imports the other, and each service keeps its own key registry. ezauth exposes RS256 tokens verifiable through JWKS/OIDC (planned, Phase R); shared API-key code moves to the neutral @ezstart/keys package (planned, Phase R). See .claude/design/decoupled-ezauth-ezpay.md in the monorepo.

Quickstart — React with components

The fastest path: wrap your app in <AuthProvider> and drop in pre-built UI.

'use client'
import { AuthProvider } from '@ezstart/auth-sdk'
import { LoginButton, UserMenu } from '@ezstart/auth-sdk/components'

export default function App({ children }: { children: React.ReactNode }) {
  return (
    <AuthProvider apiUrl="https://api.example.com" appName="myapp" authMode="httpOnly">
      <header>
        <UserMenu fallback={<LoginButton>Sign in</LoginButton>} />
      </header>
      {children}
    </AuthProvider>
  )
}

For a Next.js App Router setup with zero login flash on first paint, resolve the user server-side via getServerAuth() and pass it as initialUser:

// app/[locale]/layout.tsx — Server Component
import { headers } from 'next/headers'
import { getServerAuth } from '@ezstart/auth-sdk/server'
import { Providers } from '@/components/providers'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const cookieHeader = (await headers()).get('cookie') ?? undefined
  const initialUser = await getServerAuth({
    apiUrl: process.env.NEXT_PUBLIC_AUTH_API_URL!,
    cookieHeader,
  })
  return <Providers initialUser={initialUser}>{children}</Providers>
}
// components/providers.tsx — Client Component
'use client'
import { AuthProvider, type AuthUser } from '@ezstart/auth-sdk'

export function Providers({
  children,
  initialUser,
}: {
  children: React.ReactNode
  initialUser: AuthUser | null
}) {
  return (
    <AuthProvider
      apiUrl={process.env.NEXT_PUBLIC_AUTH_API_URL!}
      appName="myapp"
      authMode="httpOnly"
      initialUser={initialUser}
    >
      {children}
    </AuthProvider>
  )
}

See examples/nextjs-minimal for a complete starter.

Quickstart — React hooks only

If you want full UI control with the auth state hook (no @ezstart/ui peer dep):

'use client'
import { AuthProvider, useAuth } from '@ezstart/auth-sdk/react'

function Header() {
  const { user, isAuthenticated, login, logout } = useAuth()
  if (!isAuthenticated) {
    return <button onClick={() => login({ email, password })}>Sign in</button>
  }
  return (
    <>
      <span>Hello, {user.username}</span>
      <button onClick={logout}>Sign out</button>
    </>
  )
}

export default function App({ children }: { children: React.ReactNode }) {
  return (
    <AuthProvider apiUrl="https://api.example.com" appName="myapp" authMode="httpOnly">
      <Header />
      {children}
    </AuthProvider>
  )
}

Keeping the session in sync

'use client'
import { useAuth, useSessionRetry } from '@ezstart/auth-sdk/react'
import { apiCall } from '@ezstart/api-sdk'

function EnableTwoFactorDone() {
  const { revalidateSession } = useAuth()
  // After a change YOU made server-side, re-sync the store (and the other tabs).
  return <button onClick={() => revalidateSession({ fresh: true })}>Refresh my session</button>
}

function useAdminStats() {
  const withSession = useSessionRetry()
  return () =>
    withSession(() =>
      apiCall('/admin/stats', { appName: 'myapp', baseUrl: 'https://api.example.com' })
    )
}

Automatic triggers need no code: tab focus, guards before they deny, and the SDK 2FA / email flows. fresh: true also posts a signal-only USER_UPDATED on the existing BroadcastChannel so other tabs re-fetch (they never trust the payload).

Quickstart — Core only

For Vue, Svelte, vanilla JS, Node, or React Native — zero framework deps:

import { createCoreAuthClient } from '@ezstart/auth-sdk/core'

const client = createCoreAuthClient({
  apiUrl: 'https://api.example.com',
  appName: 'myapp',
})

const user = await client.loginWithCookie('[email protected]', 'password')
const me = await client.getCurrentUser()
await client.logout()

See examples/vanilla-standalone for a complete browser starter.

API

Core (@ezstart/auth-sdk/core)

  • createCoreAuthClient(config) — Factory for the framework-agnostic client. Returns login, logout, refresh, getCurrentUser, updateProfile, etc.

  • AuthError — Typed error class with code and status. The client now threads the server's error.code into AuthError.code so consumers can switch on it instead of matching error strings.

  • EMAIL_VERIFICATION_REQUIRED / isEmailVerificationRequiredError(err) — Constant + type guard for the 403 returned by the requireEmailVerified server gate on privileged routes. Use the guard to surface a "verify your email" prompt (<EmailVerificationBanner> / resend CTA) instead of a generic error.

  • TWO_FACTOR_REQUIRED / isTwoFactorRequiredError(err) — Constant + duck-typed guard ({ status: 403, code }, matches AuthError and @ezstart/api-sdk errors) for the 403 returned by the requireTwoFactor server gate when an admin has no enrolled 2FA. Use it to prompt enrollment.

    try {
      await apiCall('/admin/stats', { appName: 'myapp' })
    } catch (err) {
      if (isTwoFactorRequiredError(err)) promptEnrollment()
    }
  • TokenManager — Pluggable token persistence interface.

  • createLocalStorage() / createMemoryStorage() — Built-in storage backends.

React (@ezstart/auth-sdk/react)

  • <AuthProvider> — Context provider with auto-refresh. Owns the per-tree Zustand store. Accepts initialUser for SSR bootstrap (no login flash).

  • useAuth() — Main hook returning { user, isAuthenticated, login, logout, ... }.

  • useAuthStore(selector?) — Bound hook reading the per-Provider Zustand store.

  • useAuthStoreApi() — Returns the store instance for imperative getState() / setState() access inside the React tree.

  • useAuthStoreGetSnapshot() — Stable () => state reader for closures passed to non-React APIs.

  • useAuth().revalidateSession(options?) — Re-reads the session from the server (GET /me) and updates the store so rights changed server-side (2FA enabled/disabled, role granted/revoked, email verified) reach the client without a re-login, in both directions. Deduplicated, throttled via maxAgeMs, never rejects (returns { status, changed, previous, user }); a network/5xx failure keeps local state, a dead session (401 + failed silent refresh) signs out locally. Runs automatically on tab focus (>= 30 s apart), before <RequireTwoFactor> / <RequireEmailVerified> / <RequireRole> deny access, and after the SDK's own 2FA / verify-email / email-change flows. Call it with { fresh: true } after your own server-side change.

  • withSessionRetry(revalidate, request) / useSessionRetry() — wrap an API call: on 403 TWO_FACTOR_REQUIRED the session is revalidated once and the call retried once if the server confirms the user is now enrolled. Wired into the admin analytics / error-log hooks; wrap your own calls the same way.

  • useRevalidateSession() — Returns the Provider's revalidateSession function (same one as useAuth().revalidateSession), for components that only need to revalidate. Throws outside <AuthProvider>.

    const revalidateSession = useRevalidateSession()
    const { status, user } = await revalidateSession({ fresh: true })
  • useRevalidateBeforeDeny(shouldDeny) — Guard helper: while shouldDeny is true, revalidates the session once per mount and returns true until it settled, so a custom guard shows a loader instead of flashing "denied" on a stale flag.

    const checking = useRevalidateBeforeDeny(!hasAccess) // hasAccess: your own, possibly stale, flag
    if (checking) return <Loader />
  • createSessionRevalidator({ store, client, logger }) / DEFAULT_REVALIDATE_MAX_AGE_MS (30 000 ms) — Advanced, non-React factory behind revalidateSession (deduplicated, throttled, never rejects). <AuthProvider> already creates one; use the factory only for a custom store. Pass { maxAgeMs: DEFAULT_REVALIDATE_MAX_AGE_MS } to throttle your own automatic triggers.

    const revalidate = createSessionRevalidator({ store, client, logger })
    await revalidate({ maxAgeMs: DEFAULT_REVALIDATE_MAX_AGE_MS })
  • <RequireAuth> — Route protection wrapper. Auto-redirects to /{locale}/login?redirect_uri=... by default; accepts loginPath, redirectTo, or fallbackComponent.

  • <SignedIn> / <SignedOut> — Conditional rendering for partial UI (no redirect).

  • <AccessDenied> — Default fallback component for unauthorized access.

Components (@ezstart/auth-sdk/components)

Auth flows

  • <SignInForm>, <SignUpForm>, <QuickSignUpForm> — Login and registration forms with OAuth + 2FA support.
  • <ForgotPasswordForm>, <ResetPasswordForm> — Password recovery.
  • <TwoFactorPrompt>, <TwoFactorSettings> — TOTP enrollment and challenge.
  • <VerifyEmailFlow>, <EmailVerificationStatus> — Email verification flow + inline status.
  • <MagicLinkButton> — Passwordless email login.
  • <EmailChangeForm> — Change email with verification + cooldown.
  • <OAuthButtons>, <OAuthProvidersSection> — OAuth sign-in buttons + connected providers manager.
  • <LoginButton>, <RegisterButton> — Header CTAs with theme handoff (?theme= propagation).
  • <AuthCallbackPage> — OAuth/SSO callback handler page.

User account

  • <AccountModal> — Clerk-like account management modal.
  • <UserMenu>, <UserAvatar> — Header dropdown + avatar with initials fallback.
  • <UserSettings>, <UserDashboard> — Settings and dashboard pages.
  • <SessionsManager> — Active sessions list with revoke.
  • <DeleteAccountSection> — Soft-delete with confirmation + grace period.
  • <AuditLogSection> — User activity log.

Admin

  • <AuthAdminDashboard> — All-in-one admin console (Overview / Users / Applications / Settings tabs). Auto-scoped server-side via JWT (req.derivedScope): superadmin sees all tenants, app admin sees owned Applications, regular user sees own account.
  • <MaintenanceBanner> — Maintenance window status banner.

Developer (API keys)

  • <DeveloperPortal> — Full developer dashboard (keys CRUD + usage).
  • <ApiKeysTable>, <CreateKeyModal>, <KeyCreatedModal> — Key management primitives.
  • <UsageDetailsModal>, <UsageBadge> — Per-key usage drill-down.

Applications (multi-tenant)

  • <ApplicationsList>, <ApplicationCard> — Owner's Applications list.
  • <CreateApplicationModal> — New Application wizard.
  • <ApplicationDetailView> — Application settings + theme + keys.

Server (@ezstart/auth-sdk/server)

Server-only helpers (import 'server-only' guards). Safe to call from any Server Component or Route Handler.

  • getServerAuth({ apiUrl, cookieHeader }) — Resolve current user from the session cookie. Returns null for anonymous requests.
  • getServerApiKeys({ apiUrl, cookieHeader, applicationId? }) — List API keys for the current user, or for an Application (?applicationId=, owner / superadmin). Pass applicationId whenever the result seeds a scoped <DeveloperPortal applicationId> / <ApplicationDetailView>; a seed loaded for another scope shows the wrong list.
  • getServerApplication({ apiUrl, cookieHeader, id }) — Fetch a single Application.
  • getServerApplications({ apiUrl, cookieHeader }) — List Applications owned by the current user.
  • getServerAuditLog({ apiUrl, cookieHeader }) — Fetch audit log entries.
  • hasFeature(user, flag) — Server-side feature flag check.

Middleware (@ezstart/auth-sdk/middleware)

  • createAuthMiddleware(config) — Express middleware that accepts JWT cookie + Bearer + X-API-Key on the same routes. Bundles JWT verification, API-key lookup, monthly quota cache, scope policy, and best-effort tracking.
import { createAuthMiddleware } from '@ezstart/auth-sdk/server'

const authJwtOrKey = createAuthMiddleware({
  appName: 'myapp',
  jwtSecret: process.env.JWT_SECRET!,
  cookieName: 'access_token',
  getApiKeyModel,
  getApiKeyUsageModel,
  getAuthUserModel,
})

router.get('/me', authJwtOrKey(), controller)
router.get('/admin/users', authJwtOrKey({ requireKeyScope: 'admin' }), controller)

Create createAuthMiddleware once per application (module-level export). Quota reservations and the monthly usage cache live in the factory closure; one instance per request or route gives each its own counters and a monthly quota can be exceeded.

API keys (moved to @ezstart/keys)

Publish order: @ezstart/keys is a runtime dependency of this SDK and must be published first. Legacy ezk_* keys are no longer accepted (401).

Key-format helpers (generateRawApiKey, hashApiKey, detectKeyFormat, extractKeyPrefix), createApiKeyMiddleware, createApiKeySchema and createApiKeyUsageSchema now live in @ezstart/keys. The previous exports from @ezstart/auth-sdk, /core and /server still work but are deprecated and will be removed in the next major version:

// before
import { createApiKeyMiddleware } from '@ezstart/auth-sdk/server'
// after
import { createApiKeyMiddleware } from '@ezstart/keys/server'

RBAC (@ezstart/auth-sdk/rbac)

  • hasRole(user, role) / hasGlobalRole(user, role) — Role checks.
  • requireRole(role), requireAdmin(), requireSuperadmin() — Express middleware factories.
  • <RequireRole> — React guard component.

i18n — texts prop pattern

The components layer is agnostic of any i18n library. All user-facing strings are accepted via a texts?: Partial<...Texts> prop with English defaults baked in. Locale is auto-detected from the URL pathname (/fr/login → 'fr').

import { SignInForm } from '@ezstart/auth-sdk/components'
;<SignInForm
  appName="myapp"
  texts={{
    emailOrUsername: 'Email or username',
    password: 'Password',
    submit: 'Continue',
  }}
/>

Bundled defaults: en, fr, vi. Pass locale="fr" to force a specific language without overriding texts.

Hosted vs Embedded auth UX

Two patterns supported:

Pattern A — Hosted login (redirect to a hosted EZAuth-style page): drop <LoginButton> + <AuthCallbackPage>. Zero auth UI code, central security, cross-app SSO trivial.

Pattern B — Embedded forms (stay on your domain): build your own /login, /register, etc. pages and drop in <SignInForm>, <SignUpForm>, etc. 100% your branding, no redirect.

You can mix both in the same app.

Configuration safety

<AuthProvider> fails fast with AuthError({ code: 'CONFIG_ERROR' }) instead of silently falling back to localhost when run off-localhost. Throws when:

  1. No URL signals (off-localhost, no apiUrl, no publishableKey, no firstParty: true).
  2. publishableKey without apiUrl off-localhost.
  3. firstParty: true without an explicit appName off-localhost.
  4. webUrl resolves to localhost off-localhost (usually means NEXT_PUBLIC_AUTH_WEB_URL env is missing).

Localhost (localhost, *.localhost, 127.0.0.1, 0.0.0.0, ::1) keeps permissive defaults so zero-config dev still works.

Roadmap

@ezstart/auth-sdk ships a complete MVP for email/password + OAuth Google + 2FA + sessions + RBAC + API keys management.

  • Magic Link (passwordless email login) — shipped.
  • Passkey / WebAuthn — planned.
  • Additional OAuth providers (GitHub, Discord, Microsoft, Apple) — planned.
  • GDPR data export — planned.

Migration from 1.x

2.0.0 removes the Application webhook UI (WebhookSecretSection, useRegenerateWebhookSecret, the "Webhooks" tab of ApplicationDetailView, tabWebhooks and the webhook* text keys). Webhooks are an ezpay feature. Remove those keys from any texts override; nothing else changes. Publish order: @ezstart/api-contracts 2.0.0, then @ezstart/auth-sdk 2.0.0.

Related