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

@dloizides/auth-web

v1.16.0

Published

Themeable, branded auth UI for the dloizides.com portfolio. Native LoginForm / ForgotPasswordForm / ResetPasswordForm components, headless hooks, the same-origin BffAuthClient, and a role-based post-login router. Built on @dloizides/auth-client; talks onl

Readme

@dloizides/auth-web

Themeable, branded auth UI for the dloizides.com portfolio — the frontend half of the unified-auth plan.

Every app gets a native, branded login experience: the login / forgot / reset forms live inside the app's own frontend; the user is never redirected to Keycloak's hosted login UI. All credential exchange happens server-side in a per-app BFF (bff-katalogos, bff-erevna, ...). This package talks only to a same-origin /bff/*no secrets, no token handling, no Keycloak calls in the browser.

Built on @dloizides/auth-client, which provides the lower-level BffAuthClient.

Install

npm install @dloizides/auth-web

Peer dependencies: react, react-native (optional), @tanstack/react-query, @dloizides/auth-client (>=3.0.0).

Two ways to consume it

1. Ready-made themeable components

import {
  AuthThemeProvider,
  LoginForm,
  createBffAuthClient,
  resolvePostLoginRoute,
} from '@dloizides/auth-web';

import { katalogosAuthTheme } from './theme';      // your AuthTheme token bag
import { roleRoutes } from './roleRoutes';          // your RoleRouteTable
import { authLabels } from './authLabels';          // your localised labels

const client = createBffAuthClient();               // same-origin /bff/*

function LoginScreen() {
  const router = useRouter();
  return (
    <AuthThemeProvider theme={katalogosAuthTheme}>
      <LoginForm
        client={client}
        labels={authLabels.login}
        onForgotPassword={() => router.push('/forgot-password')}
        onSuccess={(user) => {
          const route = resolvePostLoginRoute(user, roleRoutes);
          router.replace(route ?? '/no-access');
        }}
      />
    </AuthThemeProvider>
  );
}

<ForgotPasswordForm> and <ResetPasswordForm> follow the same shape.

Email-OTP login — <OtpForm>

A native, branded "sign in with a code" surface — the user is never bounced to Keycloak's hosted UI. It is a two-step form: step 1 collects the email and asks the BFF to email a one-time code; step 2 collects the code, verifies it, and offers "resend code" / "use a different email".

import { OtpForm, createBffAuthClient, resolvePostLoginRoute } from '@dloizides/auth-web';

const client = createBffAuthClient();

function OtpLoginScreen() {
  const router = useRouter();
  return (
    <OtpForm
      client={client}
      labels={authLabels.otp}
      onSuccess={(user) => {
        const route = resolvePostLoginRoute(user, roleRoutes);
        router.replace(route ?? '/no-access');
      }}
    />
  );
}

<OtpForm> POSTs to the same-origin /bff/otp/request and /bff/otp/verify endpoints (added in Bff.AspNetCore). The BFF runs the OTP direct-grant against Keycloak server-side; the browser receives only the httpOnly session cookie.

Event-PIN login — <PinForm>

A native, branded "sign in with your event PIN" surface for operational staff (door / DJ / media on Kefi). It is a single-step form: a PIN field + a "sign in" button. The eventExternalId is a prop — the event context comes from the route/page, never typed by the user. The (event, pin) pair alone identifies the staff member; no username/password ever leaves the browser.

import { PinForm, createBffAuthClient, resolvePostLoginRoute } from '@dloizides/auth-web';

const client = createBffAuthClient();

function PinLoginScreen({ eventExternalId }: { eventExternalId: string }) {
  const router = useRouter();
  return (
    <PinForm
      client={client}
      eventExternalId={eventExternalId}
      labels={authLabels.pin}
      onSuccess={(user) => {
        const route = resolvePostLoginRoute(user, roleRoutes);
        router.replace(route ?? '/no-access');
      }}
    />
  );
}

<PinForm> POSTs to the same-origin /bff/pin/login endpoint (added in Bff.AspNetCore). The BFF runs the event-scoped PIN direct-grant against Keycloak server-side; the browser receives only the httpOnly session cookie.

2. Headless hooks (custom layout)

import { useBffAuth, createBffAuthClient } from '@dloizides/auth-web';

const client = createBffAuthClient();

function CustomLogin() {
  const { login, isSubmitting, error } = useBffAuth({ client, probeOnMount: false });
  // ...render your own form, call login({ username, password })
}

For a custom OTP layout, useOtpLogin exposes the two-step machine:

import { useOtpLogin, OtpLoginStep, createBffAuthClient } from '@dloizides/auth-web';

const client = createBffAuthClient();

function CustomOtpLogin() {
  const otp = useOtpLogin({ client });
  // step 1: otp.requestCode(email)  → otp.step becomes OtpLoginStep.EnterCode
  // step 2: otp.verifyCode(code)    → resolves to the signed-in BffUser
  //         otp.resend() / otp.reset() for the step-2 affordances
}

For a custom PIN layout, usePinLogin exposes the single-step flow:

import { usePinLogin, createBffAuthClient } from '@dloizides/auth-web';

const client = createBffAuthClient();

function CustomPinLogin({ eventExternalId }: { eventExternalId: string }) {
  const pin = usePinLogin({ client, eventExternalId });
  // pin.submit(pinValue) → resolves to the signed-in BffUser
  // pin.reset()          → clears the error
}

Theming

The package owns no brand. Each app maps its own theme system onto the flat AuthTheme token bag (colors, radii, spacing, typography) and supplies it via <AuthThemeProvider> or a theme prop on an individual component. Precedence: prop → context → defaultAuthTheme.

import { defaultAuthTheme, type AuthTheme } from '@dloizides/auth-web';

export const katalogosAuthTheme: AuthTheme = {
  ...defaultAuthTheme,
  colors: { ...defaultAuthTheme.colors, primary: '#c2410c' },
};

Because all three forms share one useAuthStyles token-to-style mapping, re-theming <LoginForm> automatically re-themes the others.

Public demo credentials — <DemoCredentialsHint>

For a product with an OPEN demo account whose credentials are already published (typically on the marketing site). It shows the username + password on the login card and offers a one-tap fill, so a non-technical evaluator never copy-pastes a password between two tabs.

It fills fields, nothing more. The login then travels the ordinary path — same BFF, same Keycloak, same session cookie, same roles. There is no auth bypass and no credential inside this package: the host supplies the pair, and the demo account's blast radius is whatever the IdP grants it.

The demo pair comes from the BFF (GET /bff/configconfig.demo), not a client const — the BFF is the only thing that knows whether a deployment has a demo configured, and a bundled const would ship credentials into every customer's self-hosted build. useBffLoginConfig already fetches that config, so it also carries demo. useDemoCredentials owns the hint↔form wiring:

import {
  AuthScreen, DemoCredentialsHint, LoginForm,
  useBffLoginConfig, useDemoCredentials,
} from '@dloizides/auth-web';

function LoginScreen() {
  const { config } = useBffLoginConfig(bffAuthClient);
  // Credentials arrive ASYNC — prefillOnMount honours a late arrival (see below).
  const demo = useDemoCredentials({ credentials: config.demo ?? undefined, prefillOnMount: true });

  return (
    <AuthScreen theme={authTheme}>
      <DemoCredentialsHint
        {...demo.hintProps}
        labels={authLabels.demo}
        testIdPrefix="zygos"
        theme={authTheme}
      />
      <LoginForm
        {...demo.loginFormProps}
        chromeless
        client={bffAuthClient}
        labels={authLabels.login}
        theme={authTheme}
        onSuccess={handleSignedIn}
      />
    </AuthScreen>
  );
}

hintProps carries { credentials, onUse }; loginFormProps carries { initialUsername, initialPassword, prefillNonce, prefillIntent }. A module-const demo works too — just pass it as credentials instead of config.demo.

Requires @dloizides/auth-client >= 4.2.0, which surfaces demo on BffLoginConfig (mapped from the wire's publishedUsername / publishedPassword). config.demo is DemoCredentials | null.

Async credentials: prefillOnMount honours a late arrival

Because the pair arrives a render or two after mount, prefillOnMount cannot lean on useState's initial value (React reads it on the first render only). The hook seeds the form when config.demo resolves to a usable pair — but as an opportunistic seed: <LoginForm> applies it only while both fields are still empty, so a late /bff/config response never overwrites a username a real account holder was typing meanwhile. Tapping Use these credentials is an explicit seed and always applies. Seeding is gated by value, so a react-query refetch returning an equal-valued object does not re-seed. useDemoCredentials chooses the intent for you via the prefillIntent prop; you only touch PrefillIntent if you drive <LoginForm> without the hook.

prefillOnMount (default false) chooses the trade:

| | Fields on arrival | To sign in as the demo user | Cost | |---|---|---|---| | false | empty | tap Use these credentials, then Sign in | — | | true | filled | tap Sign in | a real account holder must clear two fields first |

Use true only on a genuinely public demo surface. The panel renders either way, so a visitor can always see why the fields are populated.

It fails closed

No credentials — or a half-configured pair (blank username, blank password, or a field that is undefined at runtime because an env var was never set) — renders nothing at all: zero DOM. A misconfigured demo can never publish an empty credentials panel. resolveDemoCredentials is exported if you need the same decision elsewhere.

The prefill is a seed, not a leash

initialUsername / initialPassword choose the fields' starting value and then get out of the way: every field stays editable and clearable, so a real account holder can always wipe the demo values and sign in with their own. This is why prefillNonce exists — React ignores a useState initial argument on re-render, so changing initial* alone does nothing once mounted, and a "fill the form" button wired without the nonce would leave the form stubbornly empty. useDemoCredentials manages it for you; drive it yourself only if you are not using the hook.

Internationalisation

@dloizides/auth-web ships no i18n framework. Every user-facing string is supplied through a typed labels prop. Apps pass strings already localised with their own FM() / t(). Each label bag is partial — unspecified keys fall back to the English DEFAULT_* constants.

Role-based post-login routing

import { resolvePostLoginRoute, type RoleRouteTable } from '@dloizides/auth-web';

const roleRoutes: RoleRouteTable = {
  routes: [
    { role: 'superUser', route: '/admin/super' },
    { role: 'admin',     route: '/admin' },
    { role: 'user',      route: '/dashboard' },
  ],
  fallback: '/no-access',
};

// The first table entry whose role the user holds wins — list most privileged first.
const route = resolvePostLoginRoute(user, roleRoutes);

API surface

| Export | Kind | |--------|------| | LoginForm, ForgotPasswordForm, ResetPasswordForm, OtpForm, PinForm | Components | | AuthScreen, DemoCredentialsHint, PreferredMethodHint | Composition + login-surface panels | | useDemoCredentials, resolveDemoCredentials, DemoCredentials, PrefillIntent | Public demo credentials | | AuthThemeProvider, useAuthTheme, defaultAuthTheme, AuthTheme | Theming | | DEFAULT_LOGIN_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PIN_LABELS | Label bags | | useBffAuth, useBffForgotPassword, useBffResetPassword, useResetPasswordForm, useOtpLogin, usePinLogin | Headless hooks | | OtpLoginStep | OTP step enum | | createBffAuthClient, BffAuthClient (re-export) | Client | | resolvePostLoginRoute, collectUserRoles, RoleRouteTable | Router | | validatePasswordPolicy, isPasswordValid, PasswordPolicyError | Password policy | | AuthTestIds, withTestIdPrefix | Test IDs |

License

MIT