@authon/react 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,786 +2,219 @@
2
2
 
3
3
  # @authon/react
4
4
 
5
- React components and hooks for [Authon](https://authon.dev) authentication.
5
+ > Drop-in React authentication with hooks and components — self-hosted Clerk alternative, Auth0 alternative, open-source auth
6
6
 
7
- ## Install
8
-
9
- ```bash
10
- npm install @authon/react @authon/js
11
- # or
12
- pnpm add @authon/react @authon/js
13
- ```
14
-
15
- Requires `react >= 18.0.0`.
16
-
17
- ## Setup
18
-
19
- Wrap your app with `<AuthonProvider>` at the root level:
20
-
21
- ```tsx
22
- // src/main.tsx
23
- import { AuthonProvider } from '@authon/react';
24
-
25
- function App() {
26
- return (
27
- <AuthonProvider publishableKey="pk_live_...">
28
- <Router />
29
- </AuthonProvider>
30
- );
31
- }
32
- ```
33
-
34
- Get your publishable key from the [Authon Dashboard](https://authon.dev/dashboard).
35
-
36
- ---
7
+ [![npm version](https://img.shields.io/npm/v/@authon/react?color=6d28d9)](https://www.npmjs.com/package/@authon/react)
8
+ [![License](https://img.shields.io/badge/license-MIT-blue)](../../LICENSE)
37
9
 
38
- ## Components
10
+ ## Prerequisites
39
11
 
40
- ### `<AuthonProvider>`
12
+ Before installing the SDK, create an Authon project and get your API keys:
41
13
 
42
- Initializes the Authon client and provides auth context to your entire component tree. Must wrap all other Authon components and hooks.
43
-
44
- ```tsx
45
- import { AuthonProvider } from '@authon/react';
46
-
47
- <AuthonProvider
48
- publishableKey="pk_live_..."
49
- config={{
50
- apiUrl: 'https://api.authon.dev',
51
- theme: 'auto',
52
- locale: 'en',
53
- appearance: {
54
- primaryColorStart: '#7c3aed',
55
- primaryColorEnd: '#4f46e5',
56
- borderRadius: 12,
57
- },
58
- }}
59
- >
60
- {children}
61
- </AuthonProvider>
62
- ```
14
+ 1. **Create a project** at [Authon Dashboard](https://authon.dev/dashboard/overview)
15
+ - Click "Create Project" and enter your app name
16
+ - Select the authentication methods you want (Email/Password, OAuth providers, etc.)
63
17
 
64
- | Prop | Type | Description |
65
- |------|------|-------------|
66
- | `publishableKey` | `string` | Your project's publishable key |
67
- | `config` | `AuthonConfig` (optional) | Additional client configuration |
18
+ 2. **Get your API keys** from Project Settings → API Keys
19
+ - **Publishable Key** (`pk_live_...` or `pk_test_...`) — safe to use in client-side code
20
+ - **Secret Key** (`sk_live_...` or `sk_test_...`) server-side only, never expose to clients
68
21
 
69
- ---
22
+ 3. **Configure OAuth providers** (optional) in Project Settings → OAuth
23
+ - Add Google, Apple, GitHub, etc. with their respective Client ID and Secret
24
+ - Set the redirect URL to `https://api.authon.dev/v1/auth/oauth/redirect`
70
25
 
71
- ### `<SignIn>`
26
+ > **Test vs Live keys:** Use `pk_test_...` during development. Switch to `pk_live_...` before deploying to production. Test keys use a sandbox environment with no rate limits.
72
27
 
73
- Opens the sign-in modal or renders an embedded sign-in form.
74
-
75
- ```tsx
76
- import { SignIn } from '@authon/react';
77
-
78
- // Popup mode (default) — opens the modal immediately on mount
79
- <SignIn mode="popup" />
28
+ ## Install
80
29
 
81
- // Embedded mode — renders a container div for the hosted form
82
- <SignIn mode="embedded" />
30
+ ```bash
31
+ npm install @authon/react
83
32
  ```
84
33
 
85
- | Prop | Type | Default | Description |
86
- |------|------|---------|-------------|
87
- | `mode` | `'popup' \| 'embedded'` | `'popup'` | Display mode |
88
- | `redirectUrl` | `string` (optional) | — | URL to redirect after sign-in |
89
-
90
- ---
91
-
92
- ### `<SignUp>`
93
-
94
- Opens the sign-up modal or renders an embedded sign-up form.
34
+ ## Quick Start
95
35
 
96
36
  ```tsx
97
- import { SignUp } from '@authon/react';
98
-
99
- // Popup mode (default)
100
- <SignUp mode="popup" />
101
-
102
- // Embedded mode
103
- <SignUp mode="embedded" />
104
- ```
105
-
106
- | Prop | Type | Default | Description |
107
- |------|------|---------|-------------|
108
- | `mode` | `'popup' \| 'embedded'` | `'popup'` | Display mode |
109
-
110
- ---
37
+ // src/main.tsx complete working file
38
+ import React from 'react';
39
+ import ReactDOM from 'react-dom/client';
40
+ import { AuthonProvider, useAuthon, useUser, SignedIn, SignedOut, UserButton } from '@authon/react';
111
41
 
112
- ### `<UserButton>`
113
-
114
- Displays a user avatar button. When clicked, opens a dropdown with the user's name, email, and a sign-out option. When the user is signed out, renders a "Sign In" button instead.
115
-
116
- ```tsx
117
- import { UserButton } from '@authon/react';
42
+ function App() {
43
+ const { openSignIn, signOut } = useAuthon();
44
+ const { user } = useUser();
118
45
 
119
- function Navbar() {
120
46
  return (
121
- <nav>
122
- <UserButton />
123
- </nav>
47
+ <div>
48
+ <SignedOut>
49
+ <button onClick={() => openSignIn()}>Sign In</button>
50
+ </SignedOut>
51
+ <SignedIn>
52
+ <p>Welcome, {user?.email}</p>
53
+ <UserButton />
54
+ <button onClick={() => signOut()}>Sign Out</button>
55
+ </SignedIn>
56
+ </div>
124
57
  );
125
58
  }
126
- ```
127
-
128
- No props required.
129
-
130
- ---
131
-
132
- ### `<SignedIn>`
133
-
134
- Renders `children` only when a user is signed in. Renders nothing while auth is loading.
135
-
136
- ```tsx
137
- import { SignedIn } from '@authon/react';
138
-
139
- <SignedIn>
140
- <Dashboard />
141
- </SignedIn>
142
- ```
143
-
144
- ---
145
-
146
- ### `<SignedOut>`
147
-
148
- Renders `children` only when no user is signed in. Renders nothing while auth is loading.
149
-
150
- ```tsx
151
- import { SignedOut } from '@authon/react';
152
59
 
153
- <SignedOut>
154
- <a href="/sign-in">Sign In</a>
155
- </SignedOut>
60
+ ReactDOM.createRoot(document.getElementById('root')!).render(
61
+ <AuthonProvider
62
+ publishableKey="pk_live_YOUR_PUBLISHABLE_KEY"
63
+ config={{ apiUrl: 'https://your-authon-server.com' }}
64
+ >
65
+ <App />
66
+ </AuthonProvider>
67
+ );
156
68
  ```
157
69
 
158
- ---
159
-
160
- ### `<Protect>`
70
+ ## Common Tasks
161
71
 
162
- Guards content based on authentication status and an optional custom condition. Useful for role-based access control.
72
+ ### Add Google OAuth Login
163
73
 
164
74
  ```tsx
165
- import { Protect } from '@authon/react';
166
-
167
- // Require sign-in only
168
- <Protect fallback={<p>Please sign in to continue.</p>}>
169
- <PrivatePage />
170
- </Protect>
171
-
172
- // Require a specific role
173
- <Protect
174
- fallback={<p>Admin access required.</p>}
175
- condition={(user) => user.publicMetadata?.role === 'admin'}
176
- >
177
- <AdminPanel />
178
- </Protect>
179
- ```
180
-
181
- | Prop | Type | Description |
182
- |------|------|-------------|
183
- | `children` | `ReactNode` | Content to render when access is granted |
184
- | `fallback` | `ReactNode` (optional) | Content to render when access is denied |
185
- | `condition` | `(user: AuthonUser) => boolean` (optional) | Additional check beyond sign-in status |
186
-
187
- ---
188
-
189
- ### `<SocialButton>`
190
-
191
- A single OAuth provider button with built-in styles, icons, and loading state.
192
-
193
- ```tsx
194
- import { SocialButton } from '@authon/react';
75
+ import { useAuthon } from '@authon/react';
195
76
 
196
- function SocialLogin() {
77
+ function GoogleLoginButton() {
197
78
  const { client } = useAuthon();
198
-
199
79
  return (
200
- <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
201
- <SocialButton
202
- provider="google"
203
- onClick={async (provider) => {
204
- await client!.signInWithOAuth(provider);
205
- }}
206
- />
207
- <SocialButton
208
- provider="github"
209
- onClick={async (provider) => {
210
- await client!.signInWithOAuth(provider);
211
- }}
212
- compact
213
- size={48}
214
- />
215
- </div>
80
+ <button onClick={() => client?.signInWithOAuth('google')}>
81
+ Sign in with Google
82
+ </button>
216
83
  );
217
84
  }
218
85
  ```
219
86
 
220
- | Prop | Type | Default | Description |
221
- |------|------|---------|-------------|
222
- | `provider` | `OAuthProviderType` | — | OAuth provider identifier |
223
- | `onClick` | `(provider: OAuthProviderType) => void \| Promise<void>` | — | Click handler |
224
- | `loading` | `boolean` | `false` | Show spinner |
225
- | `disabled` | `boolean` | `false` | Disable button |
226
- | `label` | `string` (optional) | `"Continue with {Provider}"` | Override button label |
227
- | `compact` | `boolean` | `false` | Icon-only square button |
228
- | `className` | `string` (optional) | — | Custom class |
229
- | `style` | `CSSProperties` (optional) | — | Custom inline style |
230
- | `iconSize` | `number` (optional) | `20` (`24` in compact) | Icon size in px |
231
- | `borderRadius` | `number` | `10` | Border radius in px |
232
- | `height` | `number` | `48` | Button height in px (full mode) |
233
- | `size` | `number` | `48` | Button size in px (compact mode) |
234
-
235
- Supported `OAuthProviderType` values: `'google' | 'apple' | 'kakao' | 'naver' | 'facebook' | 'github' | 'discord' | 'x' | 'line' | 'microsoft'`
236
-
237
- ---
238
-
239
- ### `<SocialButtons>`
240
-
241
- Automatically fetches your project's enabled OAuth providers and renders a list of `<SocialButton>` components. Handles OAuth sign-in flow internally.
242
-
243
- ```tsx
244
- import { SocialButtons } from '@authon/react';
245
-
246
- // Vertical list (default)
247
- <SocialButtons
248
- onSuccess={() => console.log('Signed in!')}
249
- onError={(error) => console.error(error)}
250
- />
251
-
252
- // Compact icon row
253
- <SocialButtons
254
- compact
255
- gap={12}
256
- labels={{ google: 'Sign in with Google', kakao: '카카오로 로그인' }}
257
- buttonProps={{ borderRadius: 8 }}
258
- />
259
- ```
260
-
261
- | Prop | Type | Default | Description |
262
- |------|------|---------|-------------|
263
- | `onSuccess` | `() => void` (optional) | — | Called after successful OAuth sign-in |
264
- | `onError` | `(error: Error) => void` (optional) | — | Called on OAuth error |
265
- | `className` | `string` (optional) | — | Container class |
266
- | `style` | `CSSProperties` (optional) | — | Container style |
267
- | `gap` | `number` (optional) | `10` (`12` compact) | Gap between buttons in px |
268
- | `compact` | `boolean` | `false` | Render icon-only buttons in a row |
269
- | `labels` | `Partial<Record<OAuthProviderType, string>>` (optional) | — | Custom label per provider |
270
- | `buttonProps` | `Partial<SocialButtonProps>` (optional) | — | Props forwarded to each `<SocialButton>` |
271
-
272
- ---
273
-
274
- ## Hooks
275
-
276
- ### `useAuthon()`
277
-
278
- Returns the full auth context. Throws if used outside `<AuthonProvider>`.
87
+ ### Protect a Route
279
88
 
280
89
  ```tsx
281
- import { useAuthon } from '@authon/react';
282
-
283
- function ProfileButton() {
284
- const { isSignedIn, isLoading, user, signOut, openSignIn, openSignUp, getToken, client } = useAuthon();
285
-
286
- if (isLoading) return <span>Loading...</span>;
287
-
288
- if (!isSignedIn) {
289
- return <button onClick={() => openSignIn()}>Sign In</button>;
290
- }
90
+ import { Protect } from '@authon/react';
291
91
 
92
+ function Dashboard() {
292
93
  return (
293
- <div>
294
- <span>Hello, {user?.displayName}</span>
295
- <button onClick={() => signOut()}>Sign Out</button>
296
- </div>
94
+ <Protect fallback={<p>Please sign in to view this page.</p>}>
95
+ <h1>Dashboard</h1>
96
+ </Protect>
297
97
  );
298
98
  }
299
- ```
300
99
 
301
- **Return type:**
302
-
303
- ```ts
304
- interface AuthonContextValue {
305
- isSignedIn: boolean;
306
- isLoading: boolean;
307
- user: AuthonUser | null;
308
- signOut: () => Promise<void>;
309
- openSignIn: () => Promise<void>;
310
- openSignUp: () => Promise<void>;
311
- getToken: () => string | null;
312
- client: Authon | null;
100
+ // With role-based condition
101
+ function AdminPanel() {
102
+ return (
103
+ <Protect
104
+ condition={(user) => user.publicMetadata?.role === 'admin'}
105
+ fallback={<p>Admin access required.</p>}
106
+ >
107
+ <h1>Admin Panel</h1>
108
+ </Protect>
109
+ );
313
110
  }
314
111
  ```
315
112
 
316
- ---
317
-
318
- ### `useUser()`
319
-
320
- Shorthand hook that returns only the current user and loading state.
113
+ ### Get Current User
321
114
 
322
115
  ```tsx
323
116
  import { useUser } from '@authon/react';
324
117
 
325
- function WelcomeBanner() {
118
+ function Profile() {
326
119
  const { user, isLoading } = useUser();
327
-
328
120
  if (isLoading) return <p>Loading...</p>;
329
- if (!user) return null;
330
-
331
- return <h2>Welcome back, {user.displayName ?? user.email}!</h2>;
332
- }
333
- ```
334
-
335
- **Return type:**
336
-
337
- ```ts
338
- {
339
- user: AuthonUser | null;
340
- isLoading: boolean;
341
- }
342
- ```
343
-
344
- ---
345
-
346
- ### `useAuthonMfa()`
347
-
348
- Manages TOTP-based multi-factor authentication (Google Authenticator, Authy, etc.).
349
-
350
- ```tsx
351
- import { useAuthonMfa } from '@authon/react';
352
- import { useState } from 'react';
353
-
354
- function MfaSetupPage() {
355
- const { setupMfa, verifyMfaSetup, disableMfa, getMfaStatus, regenerateBackupCodes, isLoading, error } = useAuthonMfa();
356
- const [qrSvg, setQrSvg] = useState('');
357
- const [backupCodes, setBackupCodes] = useState<string[]>([]);
358
- const [code, setCode] = useState('');
359
-
360
- const handleSetup = async () => {
361
- const result = await setupMfa();
362
- if (result) {
363
- setQrSvg(result.qrCodeSvg);
364
- setBackupCodes(result.backupCodes);
365
- }
366
- };
367
-
368
- const handleVerify = async () => {
369
- const success = await verifyMfaSetup(code);
370
- if (success) alert('MFA enabled successfully!');
371
- };
372
-
373
- const handleCheckStatus = async () => {
374
- const status = await getMfaStatus();
375
- console.log('MFA enabled:', status?.enabled, 'Backup codes left:', status?.backupCodesRemaining);
376
- };
377
-
378
- return (
379
- <div>
380
- <button onClick={handleSetup} disabled={isLoading}>
381
- Enable MFA
382
- </button>
383
-
384
- {qrSvg && (
385
- <>
386
- <div dangerouslySetInnerHTML={{ __html: qrSvg }} />
387
- <p>Backup codes: {backupCodes.join(', ')}</p>
388
- <input
389
- value={code}
390
- onChange={(e) => setCode(e.target.value)}
391
- placeholder="Enter 6-digit code"
392
- />
393
- <button onClick={handleVerify} disabled={isLoading}>
394
- Verify & Enable
395
- </button>
396
- </>
397
- )}
398
-
399
- {error && <p style={{ color: 'red' }}>{error.message}</p>}
400
- </div>
401
- );
402
- }
403
- ```
404
-
405
- **MFA sign-in flow (verifying TOTP after password):**
406
-
407
- ```tsx
408
- import { useAuthon, useAuthonMfa } from '@authon/react';
409
- import { AuthonMfaRequiredError } from '@authon/js';
410
-
411
- function LoginForm() {
412
- const { client } = useAuthon();
413
- const { verifyMfa, isLoading } = useAuthonMfa();
414
- const [mfaToken, setMfaToken] = useState('');
415
- const [mfaStep, setMfaStep] = useState(false);
416
-
417
- const handleSignIn = async (email: string, password: string) => {
418
- try {
419
- await client!.signInWithEmail(email, password);
420
- // signed in — no MFA required
421
- } catch (err) {
422
- if (err instanceof AuthonMfaRequiredError) {
423
- setMfaToken(err.mfaToken);
424
- setMfaStep(true);
425
- }
426
- }
427
- };
428
-
429
- const handleMfa = async (code: string) => {
430
- const success = await verifyMfa(mfaToken, code);
431
- if (success) console.log('Signed in with MFA!');
432
- };
433
-
434
- // ...
435
- }
436
- ```
437
-
438
- **Return type:**
439
-
440
- ```ts
441
- interface UseAuthonMfaReturn {
442
- setupMfa: () => Promise<(MfaSetupResponse & { qrCodeSvg: string }) | null>;
443
- verifyMfaSetup: (code: string) => Promise<boolean>;
444
- verifyMfa: (mfaToken: string, code: string) => Promise<boolean>;
445
- disableMfa: (code: string) => Promise<boolean>;
446
- getMfaStatus: () => Promise<MfaStatus | null>;
447
- regenerateBackupCodes: (code: string) => Promise<string[] | null>;
448
- isLoading: boolean;
449
- error: Error | null;
450
- }
451
- ```
452
-
453
- ---
454
-
455
- ### `useAuthonPasskeys()`
456
-
457
- Manages WebAuthn passkey registration and authentication.
458
-
459
- ```tsx
460
- import { useAuthonPasskeys } from '@authon/react';
461
-
462
- function PasskeySettings() {
463
- const {
464
- registerPasskey,
465
- authenticateWithPasskey,
466
- listPasskeys,
467
- renamePasskey,
468
- revokePasskey,
469
- isLoading,
470
- error,
471
- } = useAuthonPasskeys();
472
-
473
- const handleRegister = async () => {
474
- const passkey = await registerPasskey('My MacBook');
475
- if (passkey) {
476
- console.log('Passkey registered:', passkey.id);
477
- }
478
- };
479
-
480
- const handleList = async () => {
481
- const passkeys = await listPasskeys();
482
- console.log('Registered passkeys:', passkeys);
483
- };
484
-
485
- const handleRevoke = async (id: string) => {
486
- const success = await revokePasskey(id);
487
- if (success) console.log('Passkey revoked');
488
- };
489
-
121
+ if (!user) return <p>Not signed in</p>;
490
122
  return (
491
123
  <div>
492
- <button onClick={handleRegister} disabled={isLoading}>
493
- Add Passkey
494
- </button>
495
- <button onClick={handleList} disabled={isLoading}>
496
- List Passkeys
497
- </button>
498
- {error && <p style={{ color: 'red' }}>{error.message}</p>}
124
+ <p>Email: {user.email}</p>
125
+ <p>Name: {user.displayName}</p>
499
126
  </div>
500
127
  );
501
128
  }
502
129
  ```
503
130
 
504
- **Return type:**
505
-
506
- ```ts
507
- interface UseAuthonPasskeysReturn {
508
- registerPasskey: (name?: string) => Promise<PasskeyCredential | null>;
509
- authenticateWithPasskey: (email?: string) => Promise<boolean>;
510
- listPasskeys: () => Promise<PasskeyCredential[] | null>;
511
- renamePasskey: (id: string, name: string) => Promise<PasskeyCredential | null>;
512
- revokePasskey: (id: string) => Promise<boolean>;
513
- isLoading: boolean;
514
- error: Error | null;
515
- }
516
- ```
517
-
518
- ---
519
-
520
- ### `useAuthonPasswordless()`
521
-
522
- Handles magic link and email OTP (one-time password) authentication flows.
131
+ ### Add Email/Password Auth
523
132
 
524
133
  ```tsx
525
- import { useAuthonPasswordless } from '@authon/react';
134
+ import { useAuthon } from '@authon/react';
526
135
  import { useState } from 'react';
527
136
 
528
- function PasswordlessLogin() {
529
- const { sendMagicLink, sendEmailOtp, verifyPasswordless, isLoading, error } = useAuthonPasswordless();
137
+ function EmailSignIn() {
138
+ const { client } = useAuthon();
530
139
  const [email, setEmail] = useState('');
531
- const [otpSent, setOtpSent] = useState(false);
532
- const [code, setCode] = useState('');
140
+ const [password, setPassword] = useState('');
533
141
 
534
- const handleSendOtp = async () => {
535
- const success = await sendEmailOtp(email);
536
- if (success) setOtpSent(true);
537
- };
538
-
539
- const handleVerifyOtp = async () => {
540
- const success = await verifyPasswordless({ email, code });
541
- if (success) console.log('Signed in!');
542
- };
543
-
544
- const handleMagicLink = async () => {
545
- const success = await sendMagicLink(email);
546
- if (success) alert('Check your email for a sign-in link!');
547
- };
548
-
549
- // Verify magic link token from URL
550
- const handleTokenVerify = async (token: string) => {
551
- const success = await verifyPasswordless({ token });
552
- if (success) console.log('Signed in via magic link!');
142
+ const handleSubmit = async (e: React.FormEvent) => {
143
+ e.preventDefault();
144
+ await client?.signInWithEmail(email, password);
553
145
  };
554
146
 
555
147
  return (
556
- <div>
557
- <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
558
- <button onClick={handleMagicLink} disabled={isLoading}>Send Magic Link</button>
559
- <button onClick={handleSendOtp} disabled={isLoading}>Send OTP</button>
560
-
561
- {otpSent && (
562
- <>
563
- <input value={code} onChange={(e) => setCode(e.target.value)} placeholder="Enter code" />
564
- <button onClick={handleVerifyOtp} disabled={isLoading}>Verify</button>
565
- </>
566
- )}
567
-
568
- {error && <p style={{ color: 'red' }}>{error.message}</p>}
569
- </div>
148
+ <form onSubmit={handleSubmit}>
149
+ <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
150
+ <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
151
+ <button type="submit">Sign In</button>
152
+ </form>
570
153
  );
571
154
  }
572
155
  ```
573
156
 
574
- **Return type:**
575
-
576
- ```ts
577
- interface UseAuthonPasswordlessReturn {
578
- sendMagicLink: (email: string) => Promise<boolean>;
579
- sendEmailOtp: (email: string) => Promise<boolean>;
580
- verifyPasswordless: (opts: { token?: string; email?: string; code?: string }) => Promise<boolean>;
581
- isLoading: boolean;
582
- error: Error | null;
583
- }
584
- ```
585
-
586
- ---
587
-
588
- ### `useAuthonWeb3()`
589
-
590
- Handles Web3 wallet authentication (Sign-In with Ethereum / Solana).
157
+ ### Handle Sign Out
591
158
 
592
159
  ```tsx
593
- import { useAuthonWeb3 } from '@authon/react';
594
-
595
- function Web3Login() {
596
- const { getNonce, verify, listWallets, linkWallet, unlinkWallet, isLoading, error } = useAuthonWeb3();
597
-
598
- const handleSignIn = async () => {
599
- const address = '0xYourAddress';
600
-
601
- // 1. Get a nonce to sign
602
- const nonceResponse = await getNonce(address, 'evm', 'metamask', 1);
603
- if (!nonceResponse) return;
604
-
605
- // 2. Sign the message with your wallet (e.g. MetaMask)
606
- const signature = await window.ethereum.request({
607
- method: 'personal_sign',
608
- params: [nonceResponse.message, address],
609
- });
610
-
611
- // 3. Verify the signature to sign in
612
- const success = await verify(nonceResponse.message, signature, address, 'evm', 'metamask');
613
- if (success) console.log('Signed in with wallet!');
614
- };
615
-
616
- const handleLinkWallet = async () => {
617
- const address = '0xYourAddress';
618
- const nonceResponse = await getNonce(address, 'evm', 'metamask');
619
- if (!nonceResponse) return;
620
-
621
- const signature = await window.ethereum.request({
622
- method: 'personal_sign',
623
- params: [nonceResponse.message, address],
624
- });
625
-
626
- const wallet = await linkWallet({
627
- address,
628
- chain: 'evm',
629
- walletType: 'metamask',
630
- message: nonceResponse.message,
631
- signature,
632
- });
633
-
634
- if (wallet) console.log('Wallet linked:', wallet.id);
635
- };
636
-
637
- const handleListWallets = async () => {
638
- const wallets = await listWallets();
639
- console.log('Linked wallets:', wallets);
640
- };
641
-
642
- return (
643
- <div>
644
- <button onClick={handleSignIn} disabled={isLoading}>Sign In with MetaMask</button>
645
- <button onClick={handleLinkWallet} disabled={isLoading}>Link Wallet</button>
646
- <button onClick={handleListWallets} disabled={isLoading}>List Wallets</button>
647
- {error && <p style={{ color: 'red' }}>{error.message}</p>}
648
- </div>
649
- );
650
- }
651
- ```
652
-
653
- **Return type:**
654
-
655
- ```ts
656
- interface UseAuthonWeb3Return {
657
- getNonce: (
658
- address: string,
659
- chain: Web3Chain,
660
- walletType: Web3WalletType,
661
- chainId?: number,
662
- ) => Promise<Web3NonceResponse | null>;
663
- verify: (
664
- message: string,
665
- signature: string,
666
- address: string,
667
- chain: Web3Chain,
668
- walletType: Web3WalletType,
669
- ) => Promise<boolean>;
670
- listWallets: () => Promise<Web3Wallet[] | null>;
671
- linkWallet: (params: LinkWalletParams) => Promise<Web3Wallet | null>;
672
- unlinkWallet: (walletId: string) => Promise<boolean>;
673
- isLoading: boolean;
674
- error: Error | null;
675
- }
676
-
677
- interface LinkWalletParams {
678
- address: string;
679
- chain: Web3Chain;
680
- walletType: Web3WalletType;
681
- chainId?: number;
682
- message: string;
683
- signature: string;
684
- }
685
-
686
- type Web3Chain = 'evm' | 'solana';
687
- type Web3WalletType = 'metamask' | 'pexus' | 'walletconnect' | 'coinbase' | 'phantom' | 'trust' | 'other';
688
- ```
689
-
690
- ---
691
-
692
- ### `useAuthonSessions()`
693
-
694
- Lists and revokes active user sessions.
695
-
696
- ```tsx
697
- import { useAuthonSessions } from '@authon/react';
698
- import { useEffect, useState } from 'react';
699
- import type { SessionInfo } from '@authon/shared';
700
-
701
- function SessionManager() {
702
- const { listSessions, revokeSession, isLoading, error } = useAuthonSessions();
703
- const [sessions, setSessions] = useState<SessionInfo[]>([]);
704
-
705
- useEffect(() => {
706
- listSessions().then((s) => {
707
- if (s) setSessions(s);
708
- });
709
- }, []);
710
-
711
- const handleRevoke = async (sessionId: string) => {
712
- const success = await revokeSession(sessionId);
713
- if (success) {
714
- setSessions((prev) => prev.filter((s) => s.id !== sessionId));
715
- }
716
- };
717
-
718
- if (isLoading) return <p>Loading sessions...</p>;
719
-
720
- return (
721
- <ul>
722
- {sessions.map((session) => (
723
- <li key={session.id}>
724
- <span>{session.userAgent ?? 'Unknown device'}</span>
725
- <span>{session.ipAddress}</span>
726
- <button onClick={() => handleRevoke(session.id)}>Revoke</button>
727
- </li>
728
- ))}
729
- {error && <p style={{ color: 'red' }}>{error.message}</p>}
730
- </ul>
731
- );
732
- }
733
- ```
734
-
735
- **Return type:**
736
-
737
- ```ts
738
- interface UseAuthonSessionsReturn {
739
- listSessions: () => Promise<SessionInfo[] | null>;
740
- revokeSession: (sessionId: string) => Promise<boolean>;
741
- isLoading: boolean;
742
- error: Error | null;
743
- }
744
- ```
745
-
746
- ---
747
-
748
- ## TypeScript Types
749
-
750
- Key types exported from `@authon/react` and `@authon/shared`:
751
-
752
- ```ts
753
- import type {
754
- AuthonContextValue,
755
- SocialButtonProps,
756
- SocialButtonsProps,
757
- UseAuthonMfaReturn,
758
- UseAuthonPasskeysReturn,
759
- UseAuthonPasswordlessReturn,
760
- UseAuthonWeb3Return,
761
- LinkWalletParams,
762
- UseAuthonSessionsReturn,
763
- } from '@authon/react';
764
-
765
- import type {
766
- AuthonUser,
767
- SessionInfo,
768
- PasskeyCredential,
769
- Web3Wallet,
770
- Web3NonceResponse,
771
- MfaSetupResponse,
772
- MfaStatus,
773
- OAuthProviderType,
774
- Web3Chain,
775
- Web3WalletType,
776
- } from '@authon/shared';
777
- ```
778
-
779
- ---
780
-
781
- ## Documentation
160
+ import { useAuthon } from '@authon/react';
782
161
 
783
- [authon.dev/docs](https://authon.dev/docs)
162
+ function SignOutButton() {
163
+ const { signOut } = useAuthon();
164
+ return <button onClick={() => signOut()}>Sign Out</button>;
165
+ }
166
+ ```
167
+
168
+ ## Environment Variables
169
+
170
+ | Variable | Required | Description |
171
+ |----------|----------|-------------|
172
+ | `AUTHON_API_URL` | Yes | Your Authon server URL |
173
+ | `AUTHON_PUBLISHABLE_KEY` | Yes | Project publishable key (`pk_live_...` or `pk_test_...`) |
174
+
175
+ ## API Reference
176
+
177
+ ### Components
178
+
179
+ | Component | Description |
180
+ |-----------|-------------|
181
+ | `<AuthonProvider>` | Context provider. Props: `publishableKey`, `config?` |
182
+ | `<SignIn>` | Pre-built sign-in form (`mode="popup"` or `"embedded"`) |
183
+ | `<SignUp>` | Pre-built sign-up form |
184
+ | `<UserButton>` | Avatar dropdown with user info and sign-out |
185
+ | `<UserProfile>` | Full user profile management |
186
+ | `<SignedIn>` | Renders children only when signed in |
187
+ | `<SignedOut>` | Renders children only when signed out |
188
+ | `<Protect>` | Conditional render with `fallback` and `condition` |
189
+ | `<SocialButton>` | Single OAuth provider button |
190
+ | `<SocialButtons>` | All enabled OAuth provider buttons |
191
+
192
+ ### Hooks
193
+
194
+ | Hook | Returns |
195
+ |------|---------|
196
+ | `useAuthon()` | `{ isSignedIn, isLoading, user, signOut, openSignIn, openSignUp, getToken, client }` |
197
+ | `useUser()` | `{ user, isLoading }` |
198
+ | `useAuthonMfa()` | MFA setup, verification, and management |
199
+ | `useAuthonPasskeys()` | Passkey registration and authentication |
200
+ | `useAuthonPasswordless()` | Magic link and email OTP |
201
+ | `useAuthonWeb3()` | Web3 wallet sign-in and management |
202
+ | `useAuthonSessions()` | List and revoke active sessions |
203
+ | `useOrganization()` | Active organization management |
204
+ | `useOrganizationList()` | List and switch organizations |
205
+
206
+ ## Comparison
207
+
208
+ | Feature | Authon | Clerk | Auth.js |
209
+ |---------|--------|-------|---------|
210
+ | Self-hosted | Yes | No | Partial |
211
+ | Pricing | Free | $25/mo+ | Free |
212
+ | OAuth providers | 10+ | 20+ | 80+ |
213
+ | ShadowDOM modal | Yes | No | No |
214
+ | MFA/Passkeys | Yes | Yes | Plugin |
215
+ | Web3 auth | Yes | No | No |
216
+ | Organizations | Yes | Yes | No |
784
217
 
785
218
  ## License
786
219
 
787
- [MIT](../../LICENSE)
220
+ MIT