Skip to content

auth-client quickstart

See also Browser quickstart for the role-oriented walkthrough.

Install

Terminal window
pnpm add @vendidit/auth-client
pnpm add preact # or react, solid-js, vue

Configure

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(); // unsubscribe

Drop-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.