Nest Authbeta

Hooks

Every hook exported from `@ackplus/nest-auth-react`.

Pick the narrowest hook for the job — components that subscribe to less re-render less.

useNestAuth(): AuthContextValue

The full kitchen sink. Returns state, derived booleans, the AuthClient instance, and every auth action.

const {
  status, sessionData, session, error,
  isLoading, isAuthenticated, isLoadingSessionData,
  client,
  login, signup, logout, logoutAll, refresh,
  forgotPassword, resetPassword, changePassword,
  verifyEmail, sendEmailVerification,
  verifyPhone, sendPhoneVerification,
  send2fa, verify2fa,
  setupTotp, verifyTotpSetup, getMfaStatus,
  listTotpDevices, removeTotpDevice, toggleMfa,
  generateRecoveryCode, resetMfa,
  switchTenant, setTenantId, getTenantId,
  setMode, getMode,
  passwordlessSend,
  verifySession, getSessionData,
} = useNestAuth();

Every action is an async wrapper around the underlying client method that updates context state on success/failure. Use it when you need both state and actions in one component.

useUser(): ISessionUserData | null

Just the user.

const user = useUser();
if (!user) return null;
return <div>Hello {user.email}</div>;

useSession(): ClientSession | null

The session metadata: id, userId, tenantId, expiresAt, createdAt. Not the user — for that, use useUser().

useAccessToken(): string | null

Reads the current access token. Returns null in cookie mode (the token is HttpOnly and never visible to JS).

Use this for one-off integrations that need the raw token — websockets, third-party SDKs that take a JWT. For your own API calls, the AuthClient already attaches it.

useAuthHeaderFn() / useAuthHeaderFnSync()

Both return a stable function (the reference doesn't change on token refresh, so the consumer does not re-render) that yields the current auth headers. Reach for these instead of useAccessToken() + useEffect when decorating requests in a fetch wrapper or a form submit handler.

function SaveButton() {
  const getAuthHeaders = useAuthHeaderFn(); // () => Promise<Record<string, string>>
 
  const submit = async (data: unknown) => {
    await fetch('/api/submit', {
      method: 'POST',
      headers: { ...(await getAuthHeaders()), 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
  };
 
  return <button onClick={() => submit({})}>Save</button>;
}

useAuthHeaderFnSync() is the synchronous variant — it returns () => Record<string, string> reading from the in-memory token mirror (returns {} if it's empty: no login yet or warm-up pending). Use it where you can't await — e.g. an axios.interceptors.request callback — though client.attachToAxios() is usually cleaner for that. Both accept an optional GetAuthHeadersOptions.

useAuthStatus()

Authentication status with derived booleans:

const { status, isLoading, isAuthenticated, isUnauthenticated } = useAuthStatus();
 
// status: 'loading' | 'authenticated' | 'unauthenticated'

isLoading is true on first mount until the provider has resolved the user. isUnauthenticated is the negation of isAuthenticated — convenient when you want to render a sign-in CTA.

useHasRole(role, matchAll?): boolean

const isAdmin = useHasRole('admin');
const canEdit = useHasRole(['admin', 'editor']);              // ANY (default)
const isAdminAndOwner = useHasRole(['admin', 'owner'], true); // ALL

Returns false for unauthenticated users.

useHasPermission(permission, matchAll?): boolean

Same shape as useHasRole, but checks against permissions instead.

const canRead = useHasPermission('orders.read');
const canEdit = useHasPermission(['orders.read', 'orders.write'], true);

Re-render behavior

Each hook subscribes to its own slice of context. useUser() re-renders when sessionData changes; useHasRole('admin') re-renders when the user's roles change. Components that only need a boolean don't re-render on token refresh.

Async-action hook (low-level)

If you want an action without the full context, you can read the client off the provider once:

const { client } = useNestAuth();
 
const handleSave = async () => {
  await client.changePassword({ currentPassword, newPassword });
};

You can also reach client from anywhere in the tree — there's no need to keep it as state.

useClientConfig() / useMultiAccountEnabled()

Read the backend's public client config (tenant mode, enabled auth methods, registration/MFA options, and whether multi-account is on) so the UI can adapt without hardcoding it.

const { config, isLoading } = useClientConfig();
// e.g. only show the social buttons the backend actually has, or:
const multi = useMultiAccountEnabled();            // boolean | null (null while loading)
if (multi) return <AccountSwitcher />;

Multi-account hooks

For logging into several accounts on one client and switching the active one. These live under an <AccountSwitcherProvider> (separate from AuthProvider).

const {
  accounts,        // AccountSnapshot[]
  activeAccount,   // the active snapshot, or null
  activeAccountId, // string | null
  addAccount,      // (dto, options?) => Promise<AccountSnapshot> — log in a new account, keep the others
  completeMfa,     // (error, verifyDto, options?) => Promise<AccountSnapshot> — finish an MFA-gated add
  switchAccount,   // (accountId) => Promise<AccountSnapshot> — repoint active (no server call)
  removeAccount,   // (accountId) => Promise<void> — sign that account out
  reset,           // () => Promise<void> — remove ALL accounts + wipe their storage
  setAccountMeta,  // (accountId, meta) => Promise<AccountSnapshot> — rename / label for the switcher
  manager,         // the underlying IAccountSwitcher (escape hatch for advanced flows)
} = useAccountSwitcher();
 
const accounts = useAccounts();         // just the list
const active = useActiveAccount();      // just the active account

AccountSnapshot

Each entry in accounts (and activeAccount) is an AccountSnapshot:

FieldTypeNotes
accountIdstringStable logical key — userId, or userId:tenantId when tenant-scoped
userIdstring?
tenantIdstring?
emailstring?
labelstring?Display label (best-effort from name/email, or your override)
tenantNamestring?App-supplied tenant/property name for the switcher UI
isActivebooleanWhether this is the active account

tenantName is app-supplied — the server session carries no tenant display name. Pass it via addAccount/completeMfa options ({ meta: { tenantName } }) or stamp it later with setAccountMeta. It lets a switcher show "Green Valley" vs "Sunrise" instead of an identical shared-owner email on every row.

Naming accounts — setAccountMeta

const { setAccountMeta } = useAccountSwitcher();
await setAccountMeta(accountId, { label: 'Work', tenantName: 'Green Valley' });

AccountMeta is { label?: string; tenantName?: string } — both optional, merged over the existing snapshot.

MFA on add — completeMfa

addAccount throws AccountMfaRequiredError when the login needs a second factor. Catch it, show your OTP UI, then hand the error and the verify DTO to completeMfa:

import { AccountMfaRequiredError } from '@ackplus/nest-auth-client';
import { NestAuthMFAMethodEnum } from '@ackplus/nest-auth-contracts';
 
const { addAccount, completeMfa } = useAccountSwitcher();
const [pending, setPending] = useState<AccountMfaRequiredError | null>(null);
 
async function add(dto) {
  try {
    await addAccount(dto, { meta: { tenantName: 'Green Valley' } });
  } catch (err) {
    if (err instanceof AccountMfaRequiredError) setPending(err); // show OTP UI
    else throw err;
  }
}
 
async function submitOtp(otp: string) {
  if (!pending) return;
  // verifyDto: { method, otp, trustDevice? }
  await completeMfa(pending, { method: NestAuthMFAMethodEnum.TOTP, otp });
  setPending(null);
}

One-shot: verify2fa consumes the code. If verification succeeds but the commit step throws, do not call completeMfa again (the code is spent) — recover with manager.commitAccount(pending.client, meta).

See the Multi-account login & switching recipe for the full walkthrough (header and cookie modes).