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

@molecule/app-auth

v1.1.0

Published

Client-side authentication interface for molecule.dev

Readme

@molecule/app-auth

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Client-side authentication interface for molecule.dev.

Provides a unified auth API that works across different auth strategies (JWT, session, OAuth, etc.): createJWTAuthClient builds a fetch-based client; setClient/getClient bond it; login/logout/register/ isAuthenticated/getUser delegate to the bonded client. Framework bindings (e.g. AuthProvider/useAuth in @molecule/app-react) wrap the same client.

Quick Start

import { createJWTAuthClient, setClient } from '@molecule/app-auth'

const client = createJWTAuthClient({ baseURL: '/api' })
setClient(client)
await client.initialize() // restore the session from the httpOnly cookie

// anywhere in the app
import { getUser, isAuthenticated, login, logout } from '@molecule/app-auth'
await login({ email, password })
if (isAuthenticated()) console.log(getUser()?.email)

Type

core

Installation

npm install @molecule/app-auth @molecule/app-bond @molecule/app-i18n @molecule/app-logger

API

Interfaces

AuthClient

Auth client interface that all auth bond packages must implement. Provides login/logout/register flows, token management, profile updates, and auth state subscription.

interface AuthClient<T = UserProfile> {
  /**
   * Returns the current authentication state snapshot.
   */
  getState(): AuthState<T>

  /**
   * Returns whether the user is currently authenticated.
   */
  isAuthenticated(): boolean

  /**
   * Gets the current user.
   */
  getUser(): T | null

  /**
   * Updates the cached user object (state + persistent storage) without
   * hitting the network. Intended for local refreshes after a per-app
   * mutation (e.g., the user just PATCHed their own profile and the
   * server returned the canonical row). Does NOT change tokens.
   */
  setUser(user: T | null): void

  /**
   * Gets the current access token.
   */
  getAccessToken(): string | null

  /**
   * Stores the access token in the configured token storage adapter (in-memory
   * by default). Use this to seed the token after an out-of-band exchange (e.g.
   * the OAuth code→token redirect) instead of writing to `localStorage` directly,
   * which would violate the in-memory-default storage contract and make the bearer
   * token JS-readable (XSS-exfiltratable). Pass `null` to clear it.
   */
  setAccessToken(token: string | null): void

  /**
   * Gets the refresh token.
   */
  getRefreshToken(): string | null

  /**
   * Logs in with credentials.
   */
  login(credentials: LoginCredentials): Promise<AuthResult<T>>

  /**
   * Logs out the current user.
   */
  logout(): Promise<void>

  /**
   * Registers a new user.
   */
  register(data: RegisterData): Promise<AuthResult<T>>

  /**
   * Refreshes the access token.
   */
  refresh(): Promise<AuthResult<T>>

  /**
   * Requests a password reset.
   */
  requestPasswordReset(data: PasswordResetRequest): Promise<void>

  /**
   * Confirms a password reset.
   */
  confirmPasswordReset(data: PasswordResetConfirm): Promise<void>

  /**
   * Updates the current user's profile.
   */
  updateProfile(data: Partial<T>): Promise<T>

  /**
   * Changes the current user's password.
   */
  changePassword(oldPassword: string, newPassword: string): Promise<void>

  /**
   * Initializes auth state (e.g., from stored tokens).
   */
  initialize(): Promise<void>

  /**
   * Subscribes to auth state changes.
   */
  subscribe(callback: (state: AuthState<T>) => void): () => void

  /**
   * Subscribes to auth state changes (alias for subscribe).
   */
  onAuthChange(callback: (state: AuthState<T>) => void): () => void

  /**
   * Gets the current access token (alias for getAccessToken).
   */
  getToken?(): string | null

  /**
   * Adds an auth event listener.
   */
  addEventListener(listener: AuthEventListener): () => void

  /**
   * Destroys the auth client.
   */
  destroy(): void
}

AuthClientConfig

Configuration options for creating an auth client, including API endpoints, storage strategy, token refresh, and OAuth providers.

interface AuthClientConfig {
  /**
   * API base URL.
   */
  baseURL?: string

  /**
   * Login endpoint.
   */
  loginEndpoint?: string

  /**
   * Logout endpoint.
   */
  logoutEndpoint?: string

  /**
   * Register endpoint.
   */
  registerEndpoint?: string

  /**
   * Refresh endpoint.
   */
  refreshEndpoint?: string

  /**
   * User profile endpoint.
   */
  profileEndpoint?: string

  /**
   * Current-user endpoint for session restore (default `/users/me`). Called by
   * `initialize()` with credentials to re-establish the session from the
   * httpOnly cookie after a full page load — the only way to stay logged in when
   * the bearer token is held in memory (the secure default; a localStorage copy
   * is XSS-exfiltratable). Must return the user (as `user` or `props`).
   */
  currentUserEndpoint?: string

  /**
   * Storage key prefix.
   */
  storagePrefix?: string

  /**
   * Token storage type or custom storage adapter.
   * - 'memory': In-memory storage (lost on page refresh)
   * - StorageAdapter: Custom storage implementation (e.g., from `@molecule/app-storage`)
   *
   * @example
   * ```typescript
   * // Use in-memory storage
   * storage: 'memory'
   *
   * // Use custom storage from `@molecule/app-storage`
   * import { getProvider } from '@molecule/app-storage'
   * const storageProvider = getProvider()
   * storage: {
   *   getItem: (key) => storageProvider.get(key),
   *   setItem: (key, value) => storageProvider.set(key, value),
   *   removeItem: (key) => storageProvider.remove(key),
   * }
   * ```
   */
  storage?: 'memory' | StorageAdapter

  /**
   * Decide whether a user returned by the cookie-restore probe
   * (`currentUserEndpoint`) constitutes a signed-in CLIENT session. Return
   * `false` to reject the restore: the client stays unauthenticated and the
   * `mol_auth` presence hint is cleared so later loads skip the probe (the
   * httpOnly session cookie is untouched and keeps authenticating API calls).
   * Use this when the server also issues sessions that must never appear
   * signed-in client-side — e.g. an anonymous/guest session whose user row
   * (`isAnonymous`) would otherwise hydrate as a logged-in user. Defaults to
   * accepting every restored user.
   * @param user - The user returned by the restore probe.
   * @returns Whether to establish the client session from this user.
   */
  shouldRestoreUser?: (user: UserProfile) => boolean

  /**
   * Auto refresh tokens before expiry.
   */
  autoRefresh?: boolean

  /**
   * Refresh tokens this many seconds before expiry.
   */
  refreshBuffer?: number

  /**
   * Forgot password / password reset request endpoint.
   */
  forgotPasswordEndpoint?: string

  /**
   * Password reset confirmation endpoint.
   */
  resetPasswordEndpoint?: string

  /**
   * Change password endpoint.
   */
  changePasswordEndpoint?: string

  /**
   * Available OAuth providers (e.g., ['github', 'google', 'gitlab']).
   */
  oauthProviders?: string[]

  /**
   * OAuth endpoint path (default: '/oauth').
   */
  oauthEndpoint?: string
}

AuthResult

Successful authentication result containing the user, access/refresh tokens, and expiration.

interface AuthResult<T = UserProfile> {
  /**
   * Authenticated user.
   */
  user: T

  /**
   * Access token (for token-based auth).
   */
  accessToken?: string

  /**
   * Refresh token (for token-based auth).
   */
  refreshToken?: string

  /**
   * Token expiration time (Unix timestamp).
   */
  expiresAt?: number

  /**
   * Set (with HTTP 206) when the account has two-factor auth enabled and the
   * login did not include a valid `twoFactorToken`. No tokens or user are
   * issued until login is retried with the TOTP — callers must check this
   * flag BEFORE treating the result as a successful authentication.
   */
  twoFactorRequired?: boolean
}

AuthState

Reactive authentication state snapshot (initialized, authenticated, user, loading, and error).

interface AuthState<T = UserProfile> {
  /**
   * Whether auth state has been initialized.
   */
  initialized: boolean

  /**
   * Whether the user is authenticated.
   */
  authenticated: boolean

  /**
   * Current user (if authenticated).
   */
  user: T | null

  /**
   * Whether an auth operation is in progress.
   */
  loading: boolean

  /**
   * Last auth error (if any).
   */
  error: string | null
}

LoginCredentials

Email/password login credentials with optional "remember me" flag.

interface LoginCredentials {
  /**
   * Email or username.
   */
  email: string

  /**
   * Password.
   */
  password: string

  /**
   * Whether to remember the session.
   */
  remember?: boolean

  /**
   * Time-based one-time password (TOTP) from the user's authenticator app.
   * Required when the account has two-factor auth enabled — the server
   * answers `{ twoFactorRequired: true }` (HTTP 206) until a valid token
   * is supplied.
   */
  twoFactorToken?: string
}

PasswordResetConfirm

Password reset confirmation data.

interface PasswordResetConfirm {
  /**
   * Reset token.
   */
  token: string

  /**
   * New password.
   */
  password: string
}

PasswordResetRequest

Password reset request data.

interface PasswordResetRequest {
  /**
   * Email address.
   */
  email: string
}

RegisterData

New user registration payload (email, password, optional name and metadata).

interface RegisterData {
  /**
   * Email address.
   */
  email: string

  /**
   * Password.
   */
  password: string

  /**
   * Display name.
   */
  name?: string

  /**
   * Additional registration fields.
   */
  metadata?: Record<string, unknown>
}

StorageAdapter

Minimal key-value storage adapter used by the auth client for persisting tokens and user data.

interface StorageAdapter {
  getItem(key: string): string | null
  setItem(key: string, value: string): void
  removeItem(key: string): void
}

TokenStorage

Internal token storage interface for reading and writing access/refresh tokens and user profile data.

interface TokenStorage {
  getAccessToken(): string | null
  setAccessToken(token: string | null): void
  getRefreshToken(): string | null
  setRefreshToken(token: string | null): void
  getUser<T = UserProfile>(): T | null
  setUser<T = UserProfile>(user: T | null): void
  clear(): void
}

UserProfile

User profile information.

interface UserProfile {
  /**
   * Unique user identifier.
   */
  id: string

  /**
   * User's email address.
   */
  email?: string

  /**
   * User's display name.
   */
  name?: string

  /**
   * User's avatar/profile image URL.
   */
  avatar?: string

  /**
   * User's roles or permissions.
   */
  roles?: string[]

  /**
   * Additional metadata.
   */
  metadata?: Record<string, unknown>
}

Types

AuthEvent

Auth event types.

type AuthEvent =
  | { type: 'login'; user: UserProfile }
  | { type: 'logout' }
  | { type: 'register'; user: UserProfile }
  | { type: 'refresh' }
  | { type: 'error'; error: string }

AuthEventListener

Callback invoked when an authentication event occurs (login, logout, etc.).

type AuthEventListener = (event: AuthEvent) => void

Functions

createJWTAuthClient(config)

Creates a simple JWT-based auth client.

This is a basic implementation that can be extended or replaced with more sophisticated auth providers.

function createJWTAuthClient(config?: AuthClientConfig): AuthClient<T>
  • config — Auth client configuration including endpoints, storage, and refresh settings.

Returns: A fully configured AuthClient instance.

createTokenStorage(storage, prefix)

Creates a token storage implementation backed by either in-memory storage or a custom StorageAdapter.

function createTokenStorage(storage?: 'memory' | StorageAdapter, prefix?: string): TokenStorage
  • storage'memory' for in-memory storage (lost on refresh), or a StorageAdapter for persistent storage.
  • prefix — Key prefix for storage items (default: 'molecule:auth:').

Returns: A TokenStorage instance.

getClient()

Retrieves the bonded auth client, throwing if none is configured.

function getClient(): AuthClient<T>

Returns: The bonded auth client.

getTokenExpiration(token)

Returns the expiration timestamp of a JWT token in milliseconds.

function getTokenExpiration(token: string): number | null
  • token — The raw JWT string.

Returns: The expiration time in ms since epoch, or null if the token has no exp claim.

getUser()

Returns the current user profile, or null if not authenticated.

function getUser(): T | null

Returns: The user profile, or null if not authenticated.

hasClient()

Checks whether an auth client is currently bonded.

function hasClient(): boolean

Returns: true if an auth client is bonded.

isAuthenticated()

Checks if the current user is authenticated.

function isAuthenticated(): boolean

Returns: true if the user has an active session.

isTokenExpired(token, bufferSeconds)

Checks if a JWT token is expired (or will expire within the buffer window).

function isTokenExpired(token: string, bufferSeconds?: number): boolean
  • token — The raw JWT string.
  • bufferSeconds — Seconds before actual expiry to consider the token expired (default: 0).

Returns: true if the token is expired or lacks an exp claim.

login(credentials)

Logs in with the given credentials (email/username + password).

function login(credentials: LoginCredentials): Promise<AuthResult<UserProfile>>
  • credentials — Email/username and password.

Returns: The auth result containing tokens and user profile.

logout()

Logs out the current user, clearing tokens and session state.

function logout(): Promise<void>

Returns: A promise that resolves when logout completes.

parseJWT(token)

Parses a JWT token payload without verification (base64-decodes the payload segment).

function parseJWT(token: string): T | null
  • token — The raw JWT string (header.payload.signature).

Returns: The decoded payload object, or null if parsing fails.

register(data)

Registers a new user account.

function register(data: RegisterData): Promise<AuthResult<UserProfile>>
  • data — Registration data (email, password, name, etc.).

Returns: The auth result containing tokens and user profile.

setClient(client)

Registers an auth client as the active singleton. Called by bond packages during application startup.

function setClient(client: AuthClient<UserProfile>): void
  • client — The auth client implementation to bond.

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-bond ^1.0.1
  • @molecule/app-i18n ^1.0.1
  • @molecule/app-logger ^1.0.1

Runtime Dependencies

  • @molecule/app-bond

  • @molecule/app-i18n

  • @molecule/app-logger

  • A page reload "logging the user out" is the config default, not a bug. Token storage defaults to 'memory' (the secure default — a localStorage bearer token is XSS-exfiltratable). Staying signed in across reloads works by calling client.initialize() once at startup: it re-fetches currentUserEndpoint (default /users/me) with the httpOnly cookie the API set at login. Wire initialize(); do NOT "fix" reload-logout by copying tokens into localStorage.

  • Endpoint defaults must match your API's real routes (/auth/login, /auth/register, /auth/refresh, /users/logout, /users/me, …). Align them via createJWTAuthClient({ baseURL, ...endpoints }) — never by hand-editing fetch calls in components.

  • Client-side auth state is UX only — the server enforces authorization on every request; hiding a screen is not protection.

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • [ ] Logging in with VALID credentials on the real login screen resolves login() and flips AuthState to authenticated: true with getUser() returning the actual UserProfile — the UI shows that user's own email / name / avatar, never a placeholder or a different account.
  • [ ] A WRONG password shows a visible error (AuthState error is set and the promise rejects) and establishes NO session: isAuthenticated() stays false and no protected screen becomes reachable.
  • [ ] Registering through the sign-up form calls register(), creates the account, and lands authenticated (authenticated: true, user populated) on the app's post-signup screen — not back on the login form. If the flow shows an email-verification / "check your inbox" step, it must be genuinely backed (this client's register() returns an authenticated session directly and exposes no verify method) — never a dead screen the auth contract cannot advance past.
  • [ ] Logout calls logout() and clears the session (authenticated: false, user: null, tokens cleared); afterward every protected screen redirects back to login.
  • [ ] Visiting a protected route while logged OUT redirects to login (the guard reads isAuthenticated()), and after authenticating you land back on the originally-requested screen rather than a generic home.
  • [ ] The session survives a FULL page reload: after login, hard-reload → initialize() restores it from the httpOnly cookie (via currentUserEndpoint, default /users/me) and you stay signed in — you are NOT bounced to login. (The in-memory bearer token is dropped by design; the cookie restore is what keeps you in — do not "fix" this by writing the token to localStorage.)
  • [ ] Password reset round-trips: requestPasswordReset({ email }) then confirmPasswordReset({ token, password }) with the emailed token, after which login() with the NEW password succeeds and the OLD password no longer works.
  • [ ] Authorization holds: a signed-in user only ever reads/edits their OWN record (getUser() is the caller's profile, never another user's); the bearer token lives in memory only — it is never written to localStorage / sessionStorage where another script could read it; and an expired/invalid token fails closed — the app re-authenticates (refresh() or logout()), never silently serving another user's data.

Translations

Translation strings are provided by @molecule/app-locales-auth.