Nest Authbeta

Utilities

Standalone helpers exported from `@ackplus/nest-auth-client`.

A handful of pure functions — useful inside or outside of AuthClient.

JWT helpers

decodeJwt(token) => DecodedJwt | null

Non-verifying decoder. Reads the payload of a JWT string. Returns null for malformed tokens.

import { decodeJwt } from '@ackplus/nest-auth-client';
 
const payload = decodeJwt(accessToken);
console.log(payload?.userId, payload?.exp);

DecodedJwt has the standard claims (sub, exp, iat) plus the library's (userId, sessionId, tenantId).

isTokenExpired(token, thresholdSeconds?) => boolean | null

null for invalid input. thresholdSeconds optional — isTokenExpired(token, 60) returns true 60 seconds before actual expiry.

getTokenExpirationDate(token) => Date | null / getTokenTimeToExpiry(token) => number | null

Convenience around the same data.

getUserIdFromToken(token) => string | null

Reads userId, sub, or user_id claim — whichever is present.

Role / permission helpers

import {
  hasRole,
  hasPermission,
  hasAnyAccess,
  hasAllAccess,
} from '@ackplus/nest-auth-client';
 
const user = await client.getSessionUserData();
 
hasRole(user, 'admin');                                 // boolean
hasRole(user, ['admin', 'editor']);                     // ANY (default)
hasRole(user, ['admin', 'editor'], true);               // ALL
 
hasPermission(user, 'orders.read');
hasPermission(user, ['orders.read', 'orders.write'], true);
 
hasAnyAccess(user, { roles: ['admin'], permissions: ['orders.read'] });
hasAllAccess(user, { roles: ['admin'], permissions: ['orders.read'] });

hasAnyAccess returns true if the user has any of the listed roles or any of the listed permissions. hasAllAccess requires all.

Refresh primitives

These are exported because some apps want to instrument them — typically you don't touch them.

  • RefreshQueue — prevents N concurrent 401s from triggering N refresh calls.
  • RetryTracker — ensures every original request retries at most once.

See Sessions & Tokens for how they fit together.

Event emitter

Re-exported from @ackplus/nest-auth-client for advanced cases:

  • EventEmitter — generic typed emitter.
  • createAuthEventEmitter() — returns an emitter typed for AuthEvents.

Account display metadata

The multi-account managers (AccountManager / CookieAccountManager) hold app-supplied display data the server session can't provide — a label and a human-readable tenant/property name — so a switcher UI can show "Green Valley" vs "Sunrise" instead of an identical shared-owner email on every row.

interface AccountMeta {
  label?: string;        // override the best-effort name/email label
  tenantName?: string;   // human-readable tenant/property name for the UI
}
 
interface AddAccountOptions extends RequestOptions {
  meta?: AccountMeta;    // stamp display meta on the new account
}

Stamp it at login time, or update it later once the app resolves the name:

// At add time:
await accounts.addAccount(
  { providerName: 'email', credentials: { email, password } },
  { meta: { tenantName: 'Green Valley' } },
);
 
// Later — passing undefined for a key leaves it unchanged:
await accounts.setAccountMeta(accountId, { tenantName: 'Green Valley' });

Both addAccount(dto, { meta }) and setAccountMeta(accountId, meta) return an AccountSnapshot. The snapshot carries the stamped name as tenantName:

interface AccountSnapshot {
  accountId: string;
  userId?: string;
  tenantId?: string;
  email?: string;
  label?: string;
  tenantName?: string;   // app-supplied; not derived from the session
  isActive: boolean;
}

Cookie-mode caveat: in CookieAccountManager the meta overlay is in-memory and session-scoped — the server's /auth/accounts payload doesn't carry tenantName, and the overlay isn't persisted, so it is lost on a full page reload. Re-stamp it via setAccountMeta after ready() if you need it to survive reloads. In header-mode AccountManager the meta is persisted with the account index, so it survives reloads. A meta-less re-login of the same account also keeps the previously-stamped name.

See Multi-account login & switching for the full flow.

Identifier normalization

For consistent lookups across email/phone shapes, the server exports normalizedEmail and normalizedPhone. They're not on the client SDK — keep normalization on the server side so the source of truth is the database, not the form.

If you need the same normalization on the client (e.g. to deduplicate before sending), copy the rule into your form layer; don't try to import the server function.