@drmhse/authos-react 0.1.3 → 0.1.5

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
@@ -5,293 +5,404 @@
5
5
 
6
6
  React adapter for [AuthOS](https://authos.dev) - the multi-tenant authentication platform. Provides React hooks, components, and Next.js integration.
7
7
 
8
- ## Installation
8
+ ## Quick Start (5 minutes)
9
9
 
10
10
  ```bash
11
11
  npm install @drmhse/authos-react
12
12
  ```
13
13
 
14
- Peer dependencies:
15
- ```bash
16
- npm install react react-dom
14
+ Wrap your app with `AuthOSProvider` and you're ready to go:
15
+
16
+ ```tsx
17
+ import { AuthOSProvider, SignIn, SignedIn, SignedOut, UserButton } from '@drmhse/authos-react';
18
+
19
+ function App() {
20
+ return (
21
+ <AuthOSProvider config={{ baseURL: 'https://sso.example.com' }}>
22
+ <SignedOut>
23
+ <SignIn onSuccess={(user) => console.log('Welcome', user.email)} />
24
+ </SignedOut>
25
+ <SignedIn>
26
+ <UserButton />
27
+ <p>You're signed in!</p>
28
+ </SignedIn>
29
+ </AuthOSProvider>
30
+ );
31
+ }
32
+ ```
33
+
34
+ That's it. You now have:
35
+ - Email/password authentication with MFA support
36
+ - Automatic session management
37
+ - User dropdown with logout
38
+ - Conditional rendering based on auth state
39
+
40
+ ## Usage Modes
41
+
42
+ AuthOS supports two usage modes:
43
+
44
+ ### Platform-Level Access
45
+
46
+ For platform owners and administrators, use without `org`/`service`:
47
+
48
+ ```tsx
49
+ <AuthOSProvider config={{ baseURL: 'https://sso.example.com' }}>
50
+ <SignIn />
51
+ </AuthOSProvider>
52
+ ```
53
+
54
+ ### Multi-Tenant Access (Organizations)
55
+
56
+ For tenant applications with OAuth support, add `org` and `service`:
57
+
58
+ ```tsx
59
+ <AuthOSProvider config={{
60
+ baseURL: 'https://sso.example.com',
61
+ org: 'acme-corp', // Organization slug
62
+ service: 'main-app', // Service slug
63
+ }}>
64
+ <SignIn providers={['github', 'google']} />
65
+ </AuthOSProvider>
66
+ ```
67
+
68
+ To use OAuth providers (GitHub, Google, Microsoft), you need to configure your organization and service in the provider:
69
+
70
+ ```tsx
71
+ <AuthOSProvider config={{
72
+ baseURL: 'https://sso.example.com',
73
+ org: 'my-org', // Your organization slug
74
+ service: 'my-app', // Your service slug
75
+ redirectUri: 'https://app.example.com/callback', // Optional
76
+ }}>
77
+ <SignIn providers={['github', 'google', 'microsoft']} />
78
+ </AuthOSProvider>
17
79
  ```
18
80
 
19
- ## Quick Start
81
+ ### Using an Existing Client
20
82
 
21
- Wrap your app with `AuthOSProvider`, then use the hooks and components throughout your app.
83
+ For advanced use cases, you can pass a pre-configured `SsoClient`:
22
84
 
23
85
  ```tsx
86
+ import { SsoClient } from '@drmhse/sso-sdk';
24
87
  import { AuthOSProvider } from '@drmhse/authos-react';
25
88
 
89
+ // Create client with custom configuration
90
+ const client = new SsoClient({
91
+ baseURL: 'https://sso.example.com',
92
+ storage: customStorage,
93
+ });
94
+
26
95
  function App() {
27
96
  return (
28
- <AuthOSProvider
29
- config={{
30
- baseURL: 'https://sso.example.com'
31
- }}
32
- >
33
- <YourApp />
97
+ <AuthOSProvider client={client}>
98
+ <SignIn />
34
99
  </AuthOSProvider>
35
100
  );
36
101
  }
102
+
103
+ Or use individual OAuth buttons:
104
+
105
+ ```tsx
106
+ import { OAuthButton } from '@drmhse/authos-react';
107
+
108
+ <OAuthButton provider="github">Sign in with GitHub</OAuthButton>
109
+ <OAuthButton provider="google" />
110
+ <OAuthButton provider="microsoft" />
37
111
  ```
38
112
 
39
113
  ## Components
40
114
 
41
115
  ### SignIn
42
116
 
43
- Pre-built sign-in form with email/password and OAuth provider buttons.
117
+ Complete sign-in form with email/password authentication and optional OAuth buttons.
44
118
 
45
119
  ```tsx
46
- import { SignIn } from '@drmhse/authos-react';
47
-
48
- function LoginPage() {
49
- return (
50
- <SignIn
51
- onSuccess={() => console.log('Logged in!')}
52
- onError={(err) => console.error(err)}
53
- />
54
- );
55
- }
120
+ <SignIn
121
+ onSuccess={(user) => console.log('Logged in:', user)}
122
+ onError={(error) => console.error(error)}
123
+ providers={['github', 'google']} // Optional OAuth buttons
124
+ showForgotPassword={true} // Show forgot password link
125
+ showSignUp={true} // Show sign up link
126
+ showDivider={true} // Show "or" divider between OAuth and email form
127
+ />
56
128
  ```
57
129
 
58
130
  **Props:**
59
- | Prop | Type | Description |
60
- |------|------|-------------|
61
- | `onSuccess` | `() => void` | Callback after successful login |
62
- | `onError` | `(error: Error) => void` | Callback on login error |
131
+ | Prop | Type | Default | Description |
132
+ |------|------|---------|-------------|
133
+ | `onSuccess` | `(user: UserProfile) => void` | - | Callback after successful login |
134
+ | `onError` | `(error: Error) => void` | - | Callback on login error |
135
+ | `providers` | `('github' \| 'google' \| 'microsoft')[] \| false` | `false` | OAuth providers to display |
136
+ | `showForgotPassword` | `boolean` | `true` | Show forgot password link |
137
+ | `showSignUp` | `boolean` | `true` | Show sign up link |
138
+ | `showDivider` | `boolean` | `true` | Show divider between OAuth and email form |
139
+ | `className` | `string` | - | Custom class name |
63
140
 
64
141
  ### SignUp
65
142
 
66
143
  Registration form for new users.
67
144
 
68
145
  ```tsx
69
- import { SignUp } from '@drmhse/authos-react';
146
+ <SignUp
147
+ onSuccess={() => console.log('Check your email!')}
148
+ onError={(error) => console.error(error)}
149
+ orgSlug="my-org" // Optional: pre-fill organization
150
+ />
151
+ ```
70
152
 
71
- function RegisterPage() {
72
- return (
73
- <SignUp
74
- onSuccess={() => console.log('Registered!')}
75
- onError={(err) => console.error(err)}
76
- />
77
- );
78
- }
153
+ ### MagicLinkSignIn
154
+
155
+ Sign-in component for Magic Links (passwordless).
156
+
157
+ ```tsx
158
+ <MagicLinkSignIn onSuccess={() => console.log('Magic link sent!')} />
79
159
  ```
80
160
 
81
- **Props:** Same as `SignIn`
161
+ ### PasskeySignIn
82
162
 
83
- ### UserButton
163
+ Sign-in component for Passkeys (WebAuthn).
84
164
 
85
- User menu button that shows avatar/name and dropdown with profile/signout.
165
+ ```tsx
166
+ <PasskeySignIn onSuccess={() => console.log('Authenticated!')} />
167
+ ```
168
+
169
+ ### SignedIn / SignedOut
170
+
171
+ Conditional rendering based on authentication state. Inspired by Clerk's API.
86
172
 
87
173
  ```tsx
88
- import { UserButton } from '@drmhse/authos-react';
174
+ <SignedIn>
175
+ {/* Only shown when user is logged in */}
176
+ <UserButton />
177
+ </SignedIn>
178
+
179
+ <SignedOut>
180
+ {/* Only shown when user is logged out */}
181
+ <SignIn />
182
+ </SignedOut>
183
+ ```
89
184
 
90
- function Header() {
91
- return <UserButton />;
92
- }
185
+ ### UserButton
186
+
187
+ User menu button with avatar initials and dropdown with profile/signout.
188
+
189
+ ```tsx
190
+ <UserButton
191
+ showEmail={true}
192
+ onLogout={() => router.push('/')}
193
+ />
93
194
  ```
94
195
 
95
196
  ### OrganizationSwitcher
96
197
 
97
198
  Dropdown to switch between organizations (for multi-tenant users).
98
199
 
99
- ```tsx
100
- import { OrganizationSwitcher } from '@drmhse/authos-react';
200
+ When switching organizations, the SDK automatically issues new JWT tokens with the new organization context, enabling seamless multi-tenant switching without re-authentication.
101
201
 
102
- function Sidebar() {
103
- return <OrganizationSwitcher />;
104
- }
202
+ ```tsx
203
+ <OrganizationSwitcher
204
+ onSwitch={(org) => console.log('Switched to:', org.name)}
205
+ />
105
206
  ```
106
207
 
107
208
  ### Protect
108
209
 
109
- Conditional rendering based on user permissions.
210
+ Conditional rendering based on user permissions or roles.
110
211
 
111
212
  ```tsx
112
- import { Protect } from '@drmhse/authos-react';
213
+ <Protect permission="admin:access" fallback={<p>Access denied</p>}>
214
+ <AdminDashboard />
215
+ </Protect>
113
216
 
114
- function AdminPanel() {
115
- return (
116
- <Protect
117
- permission="admin:access"
118
- fallback={<p>Access denied. Admins only.</p>}
119
- >
120
- <AdminDashboard />
121
- </Protect>
122
- );
123
- }
217
+ <Protect role="owner">
218
+ <DangerZone />
219
+ </Protect>
124
220
  ```
125
221
 
126
- **Props:**
127
- | Prop | Type | Description |
128
- |------|------|-------------|
129
- | `permission` | `string` | Required permission to access content |
130
- | `fallback` | `ReactNode` | Shown when user lacks permission |
131
- | `children` | `ReactNode` | Protected content |
222
+ ### OAuthButton
223
+
224
+ Individual OAuth provider button. Requires `org` and `service` in provider config.
225
+
226
+ ```tsx
227
+ <OAuthButton provider="github" />
228
+ <OAuthButton provider="google">Continue with Google</OAuthButton>
229
+ ```
132
230
 
133
231
  ## Hooks
134
232
 
135
233
  ### useAuthOS
136
234
 
137
- Access the AuthOS client directly.
235
+ Access the AuthOS client and auth state.
138
236
 
139
237
  ```tsx
140
- import { useAuthOS } from '@drmhse/authos-react';
141
-
142
- function Profile() {
143
- const { client, isAuthenticated, isLoading } = useAuthOS();
144
-
145
- if (isLoading) return <div>Loading...</div>;
146
- if (!isAuthenticated) return <div>Please log in</div>;
147
-
148
- const handleLogout = async () => {
149
- await client.auth.logout();
150
- };
238
+ const { client, config, isAuthenticated, isLoading } = useAuthOS();
151
239
 
152
- return (
153
- <div>
154
- <button onClick={handleLogout}>Logout</button>
155
- </div>
156
- );
157
- }
240
+ // Use the client directly
241
+ await client.auth.logout();
158
242
  ```
159
243
 
160
- **Returns:**
161
- | Property | Type | Description |
162
- |----------|------|-------------|
163
- | `client` | `SsoClient` | The AuthOS SDK client |
164
- | `isLoading` | `boolean` | True while checking auth state |
165
- | `isAuthenticated` | `boolean` | True if user is logged in |
166
-
167
244
  ### useUser
168
245
 
169
246
  Get the current user's profile.
170
247
 
171
248
  ```tsx
172
- import { useUser } from '@drmhse/authos-react';
249
+ const { user, isLoading } = useUser();
173
250
 
174
- function UserProfile() {
175
- const { user, isLoading } = useUser();
251
+ if (isLoading) return <Spinner />;
252
+ if (!user) return <SignIn />;
176
253
 
177
- if (isLoading) return <div>Loading...</div>;
178
- return <div>Welcome, {user?.email}</div>;
179
- }
254
+ return <p>Welcome, {user.email}</p>;
180
255
  ```
181
256
 
182
- **Returns:**
183
- | Property | Type | Description |
184
- |----------|------|-------------|
185
- | `user` | `UserProfile \| null` | Current user profile |
186
- | `isLoading` | `boolean` | True while checking auth state |
187
-
188
257
  ### useOrganization
189
258
 
190
- Get the current organization and list of organizations.
259
+ Get and switch between organizations.
191
260
 
192
261
  ```tsx
193
- import { useOrganization } from '@drmhse/authos-react';
194
-
195
- function OrgInfo() {
196
- const { currentOrganization, organizations } = useOrganization();
197
-
198
- return (
199
- <div>
200
- <h3>{currentOrganization?.name}</h3>
201
- <ul>
202
- {organizations.map((org) => (
203
- <li key={org.id}>{org.name}</li>
204
- ))}
205
- </ul>
206
- </div>
207
- );
208
- }
262
+ const {
263
+ currentOrganization,
264
+ organizations,
265
+ switchOrganization,
266
+ isSwitching
267
+ } = useOrganization();
268
+
269
+ // Switch to a different org
270
+ await switchOrganization('other-org-slug');
209
271
  ```
210
272
 
211
- **Returns:**
212
- | Property | Type | Description |
213
- |----------|------|-------------|
214
- | `currentOrganization` | `Organization \| null` | Current organization |
215
- | `organizations` | `Organization[]` | All user's organizations |
216
- | `switchOrganization` | `(slug: string) => Promise<void>` | Switch to a different org |
217
- | `isSwitching` | `boolean` | True while switching |
218
-
219
- ### usePermission, useAnyPermission, useAllPermissions
273
+ ### usePermission / useAnyPermission / useAllPermissions
220
274
 
221
275
  Check user permissions.
222
276
 
223
277
  ```tsx
224
- import { usePermission } from '@drmhse/authos-react';
278
+ const canAccessAdmin = usePermission('admin:access');
279
+ const canReadOrWrite = useAnyPermission(['read:data', 'write:data']);
280
+ const hasFullAccess = useAllPermissions(['read:data', 'write:data', 'delete:data']);
225
281
 
226
- function AdminButton() {
227
- const canAccessAdmin = usePermission('admin:access');
228
-
229
- if (!canAccessAdmin) return null;
230
-
231
- return <button>Admin Panel</button>;
232
- }
282
+ if (!canAccessAdmin) return <AccessDenied />;
233
283
  ```
234
284
 
235
285
  ## Next.js Integration
236
286
 
237
- For Next.js App Router, use the middleware and server utilities:
287
+ ### App Router
238
288
 
239
289
  ```tsx
240
- // middleware.ts
241
- import { authMiddleware } from '@drmhse/authos-react/nextjs';
290
+ // app/layout.tsx
291
+ import { AuthOSProvider } from '@drmhse/authos-react';
292
+ import { cookies } from 'next/headers';
293
+
294
+ export default async function RootLayout({ children }) {
295
+ const cookieStore = cookies();
296
+ const token = cookieStore.get('authos_token')?.value;
297
+
298
+ return (
299
+ <html>
300
+ <body>
301
+ <AuthOSProvider
302
+ config={{ baseURL: process.env.NEXT_PUBLIC_SSO_URL! }}
303
+ initialSessionToken={token}
304
+ >
305
+ {children}
306
+ </AuthOSProvider>
307
+ </body>
308
+ </html>
309
+ );
310
+ }
311
+ ```
242
312
 
243
- export default authMiddleware();
313
+ ### Middleware (Next.js)
314
+
315
+ ```ts
316
+ // middleware.ts
317
+ import { authMiddleware, getJwksUrl } from '@drmhse/authos-react/nextjs';
318
+
319
+ export default authMiddleware({
320
+ jwksUrl: getJwksUrl(process.env.NEXT_PUBLIC_SSO_URL!),
321
+ protectedRoutes: ['/dashboard/*', '/settings/*'],
322
+ publicRoutes: ['/', '/about', '/pricing'],
323
+ signInUrl: '/signin',
324
+ });
325
+
326
+ export const config = {
327
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
328
+ };
244
329
  ```
245
330
 
331
+ ### Server Components
332
+
246
333
  ```tsx
247
334
  // app/dashboard/page.tsx
248
335
  import { currentUser } from '@drmhse/authos-react/nextjs';
249
336
 
250
337
  export default async function Dashboard() {
251
338
  const user = await currentUser();
252
-
339
+
253
340
  if (!user) {
254
- return <div>Please log in</div>;
341
+ redirect('/login');
255
342
  }
256
343
 
257
344
  return <div>Welcome, {user.email}</div>;
258
345
  }
259
346
  ```
260
347
 
261
- ## API Reference
348
+ ## Configuration Reference
262
349
 
263
350
  ### AuthOSProvider Props
264
351
 
265
- | Prop | Type | Default | Description |
266
- |------|------|---------|-------------|
267
- | `config.baseURL` | `string` | **required** | AuthOS API URL (e.g., `https://sso.example.com`) |
268
- | `config.storage` | `TokenStorage` | `localStorage` | Custom token storage |
269
- | `config.autoRefresh` | `boolean` | `true` | Auto-refresh expired tokens |
270
- | `config.onAuthStateChange` | `(user) => void` | - | Callback when auth state changes |
352
+ | Prop | Type | Required | Description |
353
+ |------|------|----------|-------------|
354
+ | `config.baseURL` | `string` | | AuthOS API URL |
355
+ | `config.org` | `string` | For OAuth | Organization slug for OAuth flows |
356
+ | `config.service` | `string` | For OAuth | Service slug for OAuth flows |
357
+ | `config.redirectUri` | `string` | - | OAuth redirect URI (defaults to origin + '/callback') |
358
+ | `config.afterSignInUrl` | `string` | - | Redirect URL after sign-in |
359
+ | `config.afterSignUpUrl` | `string` | - | Redirect URL after sign-up |
360
+ | `config.storage` | `TokenStorage` | - | Custom token storage |
361
+ | `client` | `SsoClient` | - | Use existing client instance |
362
+ | `initialSessionToken` | `string` | - | SSR token for hydration |
363
+
364
+ ## Styling
365
+
366
+ All components use data attributes for styling hooks. Use CSS selectors:
367
+
368
+ ```css
369
+ [data-authos-signin] { /* Container */ }
370
+ [data-authos-field="email"] { /* Email field wrapper */ }
371
+ [data-authos-field="password"] { /* Password field wrapper */ }
372
+ [data-authos-submit] { /* Submit button */ }
373
+ [data-authos-error] { /* Error message */ }
374
+ [data-authos-oauth] { /* OAuth button */ }
375
+ [data-authos-oauth][data-provider="github"] { /* GitHub button */ }
376
+ [data-authos-divider] { /* "or" divider */ }
377
+ ```
271
378
 
272
- ### SsoClient
379
+ ## Security Considerations
273
380
 
274
- The underlying client from `@drmhse/sso-sdk`. See [SDK docs](https://www.npmjs.com/package/@drmhse/sso-sdk) for full API.
381
+ ### Token Storage
275
382
 
276
- ```tsx
277
- const { client } = useAuthOS();
383
+ The SDK offers multiple storage options:
278
384
 
279
- // Authentication
280
- await client.auth.login({ email, password });
281
- await client.auth.logout();
282
- await client.auth.register({ email, password, org_slug });
385
+ | Storage | Environment | Security Notes |
386
+ |---------|-------------|----------------|
387
+ | `BrowserStorage` | Client-side | Uses localStorage, accessible to JS |
388
+ | `CookieStorage` | SSR (Next.js) | Non-httpOnly cookies, accessible to JS |
389
+ | `MemoryStorage` | SSR/Testing | In-memory, lost on page refresh |
283
390
 
284
- // User
285
- await client.user.getProfile();
286
- await client.user.updateProfile({ name });
287
- await client.user.changePassword({ old, new });
391
+ > [!WARNING]
392
+ > `CookieStorage` and `BrowserStorage` store tokens accessible to JavaScript.
393
+ > For maximum security in production, consider:
394
+ > - Using httpOnly cookies set by your backend
395
+ > - Implementing a Backend-for-Frontend (BFF) pattern
396
+ > - Ensuring proper CSP headers to mitigate XSS
288
397
 
289
- // Organizations
290
- await client.organizations.list();
291
- await client.organizations.get(slug);
398
+ ### JWT Verification
292
399
 
293
- // And more...
294
- ```
400
+ The Next.js middleware and Node adapter provide independent JWT verification:
401
+ - **Next.js middleware**: Uses Web Crypto API (`crypto.subtle`) for Edge Runtime
402
+ - **Node adapter**: Uses Node.js `crypto` module
403
+
404
+ This duplication is intentional due to runtime differences. Both implementations
405
+ verify signatures using your JWKS endpoint.
295
406
 
296
407
  ## License
297
408