auth-client quickstart
See also Browser quickstart for the role-oriented walkthrough.
Install
pnpm add @vendidit/auth-clientpnpm add preact # or react, solid-js, vueConfigure
import { createAuthClient } from '@vendidit/auth-client';
const auth = createAuthClient({ apiBaseUrl: 'https://auth.vendidit.com/api/v1', appCode: 'marketplace-buyer', bootstrap: 'auto',});Optional config knobs:
createAuthClient({ apiBaseUrl: '...', appCode: '...', bootstrap: 'auto', ports: { tokenStore: new MemoryTokenStore(), // SSR-safe transport: new FetchTransport({ retry: { maxAttempts: 3 } }), storage: new MemoryStorage(), broadcast: new NoOpBroadcast(), logger: new NoOpLogger(), },});Login
import { RequiresTwoFactorError } from '@vendidit/auth-client';
try { await auth.loginWithPassword({ email, password });} catch (err) { if (err instanceof RequiresTwoFactorError) { const code = await prompt2FA(); await auth.loginWithPassword({ email, password, twoFactorCode: code }); }}SSO
const { auth_url } = await auth.startSso({ provider: 'google', redirectUrl: window.location.origin + '/auth/sso/callback',});window.location.href = auth_url;
// On the callback route:await auth.completeSso({ code: new URLSearchParams(location.search).get('code')!, state: new URLSearchParams(location.search).get('state')!, provider: 'google',});Make authenticated requests
const res = await auth.authenticatedRequest({ method: 'GET', url: '/api/orders',});The SDK injects the bearer, retries once on 401 (under a refresh mutex), and surfaces typed errors. The wrapper also coordinates with other tabs via BroadcastChannel.
Subscribe to lifecycle events
const off = auth.on('logged_in', (user) => track('login', { id: user.id }));auth.on('logged_out', () => router.push('/login'));auth.on('token_refreshed', () => log('still alive'));auth.on('session_expired', () => router.push('/login?expired=1'));
off(); // unsubscribeDrop-in flows
import { CompleteLoginFlow } from '@vendidit/auth-client/preact/ui/flows';
<CompleteLoginFlow onSuccess={() => navigate('/')} onSwitchToRegister={() => navigate('/register')}/>Every flow is composed from atoms + forms in the same package. Drop down to forms when you need to customize; drop down to atoms when you need full control.