@drmhse/authos-react 0.1.3 → 0.1.4

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,388 @@
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';
70
-
71
- function RegisterPage() {
72
- return (
73
- <SignUp
74
- onSuccess={() => console.log('Registered!')}
75
- onError={(err) => console.error(err)}
76
- />
77
- );
78
- }
146
+ <SignUp
147
+ onSuccess={() => console.log('Check your email!')}
148
+ onError={(error) => console.error(error)}
149
+ orgSlug="my-org" // Optional: pre-fill organization
150
+ />
79
151
  ```
80
152
 
81
- **Props:** Same as `SignIn`
153
+ ### SignedIn / SignedOut
154
+
155
+ Conditional rendering based on authentication state. Inspired by Clerk's API.
156
+
157
+ ```tsx
158
+ <SignedIn>
159
+ {/* Only shown when user is logged in */}
160
+ <UserButton />
161
+ </SignedIn>
162
+
163
+ <SignedOut>
164
+ {/* Only shown when user is logged out */}
165
+ <SignIn />
166
+ </SignedOut>
167
+ ```
82
168
 
83
169
  ### UserButton
84
170
 
85
- User menu button that shows avatar/name and dropdown with profile/signout.
171
+ User menu button with avatar initials and dropdown with profile/signout.
86
172
 
87
173
  ```tsx
88
- import { UserButton } from '@drmhse/authos-react';
89
-
90
- function Header() {
91
- return <UserButton />;
92
- }
174
+ <UserButton
175
+ showEmail={true}
176
+ onLogout={() => router.push('/')}
177
+ />
93
178
  ```
94
179
 
95
180
  ### OrganizationSwitcher
96
181
 
97
182
  Dropdown to switch between organizations (for multi-tenant users).
98
183
 
99
- ```tsx
100
- import { OrganizationSwitcher } from '@drmhse/authos-react';
184
+ When switching organizations, the SDK automatically issues new JWT tokens with the new organization context, enabling seamless multi-tenant switching without re-authentication.
101
185
 
102
- function Sidebar() {
103
- return <OrganizationSwitcher />;
104
- }
186
+ ```tsx
187
+ <OrganizationSwitcher
188
+ onSwitch={(org) => console.log('Switched to:', org.name)}
189
+ />
105
190
  ```
106
191
 
107
192
  ### Protect
108
193
 
109
- Conditional rendering based on user permissions.
194
+ Conditional rendering based on user permissions or roles.
110
195
 
111
196
  ```tsx
112
- import { Protect } from '@drmhse/authos-react';
197
+ <Protect permission="admin:access" fallback={<p>Access denied</p>}>
198
+ <AdminDashboard />
199
+ </Protect>
113
200
 
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
- }
201
+ <Protect role="owner">
202
+ <DangerZone />
203
+ </Protect>
124
204
  ```
125
205
 
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 |
206
+ ### OAuthButton
207
+
208
+ Individual OAuth provider button. Requires `org` and `service` in provider config.
209
+
210
+ ```tsx
211
+ <OAuthButton provider="github" />
212
+ <OAuthButton provider="google">Continue with Google</OAuthButton>
213
+ ```
132
214
 
133
215
  ## Hooks
134
216
 
135
217
  ### useAuthOS
136
218
 
137
- Access the AuthOS client directly.
219
+ Access the AuthOS client and auth state.
138
220
 
139
221
  ```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>;
222
+ const { client, config, isAuthenticated, isLoading } = useAuthOS();
147
223
 
148
- const handleLogout = async () => {
149
- await client.auth.logout();
150
- };
151
-
152
- return (
153
- <div>
154
- <button onClick={handleLogout}>Logout</button>
155
- </div>
156
- );
157
- }
224
+ // Use the client directly
225
+ await client.auth.logout();
158
226
  ```
159
227
 
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
228
  ### useUser
168
229
 
169
230
  Get the current user's profile.
170
231
 
171
232
  ```tsx
172
- import { useUser } from '@drmhse/authos-react';
233
+ const { user, isLoading } = useUser();
173
234
 
174
- function UserProfile() {
175
- const { user, isLoading } = useUser();
235
+ if (isLoading) return <Spinner />;
236
+ if (!user) return <SignIn />;
176
237
 
177
- if (isLoading) return <div>Loading...</div>;
178
- return <div>Welcome, {user?.email}</div>;
179
- }
238
+ return <p>Welcome, {user.email}</p>;
180
239
  ```
181
240
 
182
- **Returns:**
183
- | Property | Type | Description |
184
- |----------|------|-------------|
185
- | `user` | `UserProfile \| null` | Current user profile |
186
- | `isLoading` | `boolean` | True while checking auth state |
187
-
188
241
  ### useOrganization
189
242
 
190
- Get the current organization and list of organizations.
243
+ Get and switch between organizations.
191
244
 
192
245
  ```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
- }
246
+ const {
247
+ currentOrganization,
248
+ organizations,
249
+ switchOrganization,
250
+ isSwitching
251
+ } = useOrganization();
252
+
253
+ // Switch to a different org
254
+ await switchOrganization('other-org-slug');
209
255
  ```
210
256
 
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
257
+ ### usePermission / useAnyPermission / useAllPermissions
220
258
 
221
259
  Check user permissions.
222
260
 
223
261
  ```tsx
224
- import { usePermission } from '@drmhse/authos-react';
225
-
226
- function AdminButton() {
227
- const canAccessAdmin = usePermission('admin:access');
262
+ const canAccessAdmin = usePermission('admin:access');
263
+ const canReadOrWrite = useAnyPermission(['read:data', 'write:data']);
264
+ const hasFullAccess = useAllPermissions(['read:data', 'write:data', 'delete:data']);
228
265
 
229
- if (!canAccessAdmin) return null;
230
-
231
- return <button>Admin Panel</button>;
232
- }
266
+ if (!canAccessAdmin) return <AccessDenied />;
233
267
  ```
234
268
 
235
269
  ## Next.js Integration
236
270
 
237
- For Next.js App Router, use the middleware and server utilities:
271
+ ### App Router
238
272
 
239
273
  ```tsx
240
- // middleware.ts
241
- import { authMiddleware } from '@drmhse/authos-react/nextjs';
274
+ // app/layout.tsx
275
+ import { AuthOSProvider } from '@drmhse/authos-react';
276
+ import { cookies } from 'next/headers';
277
+
278
+ export default async function RootLayout({ children }) {
279
+ const cookieStore = cookies();
280
+ const token = cookieStore.get('authos_token')?.value;
281
+
282
+ return (
283
+ <html>
284
+ <body>
285
+ <AuthOSProvider
286
+ config={{ baseURL: process.env.NEXT_PUBLIC_SSO_URL! }}
287
+ initialSessionToken={token}
288
+ >
289
+ {children}
290
+ </AuthOSProvider>
291
+ </body>
292
+ </html>
293
+ );
294
+ }
295
+ ```
296
+
297
+ ### Middleware (Next.js)
242
298
 
243
- export default authMiddleware();
299
+ ```ts
300
+ // middleware.ts
301
+ import { authMiddleware, getJwksUrl } from '@drmhse/authos-react/nextjs';
302
+
303
+ export default authMiddleware({
304
+ jwksUrl: getJwksUrl(process.env.NEXT_PUBLIC_SSO_URL!),
305
+ protectedRoutes: ['/dashboard/*', '/settings/*'],
306
+ publicRoutes: ['/', '/about', '/pricing'],
307
+ signInUrl: '/signin',
308
+ });
309
+
310
+ export const config = {
311
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
312
+ };
244
313
  ```
245
314
 
315
+ ### Server Components
316
+
246
317
  ```tsx
247
318
  // app/dashboard/page.tsx
248
319
  import { currentUser } from '@drmhse/authos-react/nextjs';
249
320
 
250
321
  export default async function Dashboard() {
251
322
  const user = await currentUser();
252
-
323
+
253
324
  if (!user) {
254
- return <div>Please log in</div>;
325
+ redirect('/login');
255
326
  }
256
327
 
257
328
  return <div>Welcome, {user.email}</div>;
258
329
  }
259
330
  ```
260
331
 
261
- ## API Reference
332
+ ## Configuration Reference
262
333
 
263
334
  ### AuthOSProvider Props
264
335
 
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 |
336
+ | Prop | Type | Required | Description |
337
+ |------|------|----------|-------------|
338
+ | `config.baseURL` | `string` | | AuthOS API URL |
339
+ | `config.org` | `string` | For OAuth | Organization slug for OAuth flows |
340
+ | `config.service` | `string` | For OAuth | Service slug for OAuth flows |
341
+ | `config.redirectUri` | `string` | - | OAuth redirect URI (defaults to origin + '/callback') |
342
+ | `config.afterSignInUrl` | `string` | - | Redirect URL after sign-in |
343
+ | `config.afterSignUpUrl` | `string` | - | Redirect URL after sign-up |
344
+ | `config.storage` | `TokenStorage` | - | Custom token storage |
345
+ | `client` | `SsoClient` | - | Use existing client instance |
346
+ | `initialSessionToken` | `string` | - | SSR token for hydration |
347
+
348
+ ## Styling
349
+
350
+ All components use data attributes for styling hooks. Use CSS selectors:
351
+
352
+ ```css
353
+ [data-authos-signin] { /* Container */ }
354
+ [data-authos-field="email"] { /* Email field wrapper */ }
355
+ [data-authos-field="password"] { /* Password field wrapper */ }
356
+ [data-authos-submit] { /* Submit button */ }
357
+ [data-authos-error] { /* Error message */ }
358
+ [data-authos-oauth] { /* OAuth button */ }
359
+ [data-authos-oauth][data-provider="github"] { /* GitHub button */ }
360
+ [data-authos-divider] { /* "or" divider */ }
361
+ ```
271
362
 
272
- ### SsoClient
363
+ ## Security Considerations
273
364
 
274
- The underlying client from `@drmhse/sso-sdk`. See [SDK docs](https://www.npmjs.com/package/@drmhse/sso-sdk) for full API.
365
+ ### Token Storage
275
366
 
276
- ```tsx
277
- const { client } = useAuthOS();
367
+ The SDK offers multiple storage options:
278
368
 
279
- // Authentication
280
- await client.auth.login({ email, password });
281
- await client.auth.logout();
282
- await client.auth.register({ email, password, org_slug });
369
+ | Storage | Environment | Security Notes |
370
+ |---------|-------------|----------------|
371
+ | `BrowserStorage` | Client-side | Uses localStorage, accessible to JS |
372
+ | `CookieStorage` | SSR (Next.js) | Non-httpOnly cookies, accessible to JS |
373
+ | `MemoryStorage` | SSR/Testing | In-memory, lost on page refresh |
283
374
 
284
- // User
285
- await client.user.getProfile();
286
- await client.user.updateProfile({ name });
287
- await client.user.changePassword({ old, new });
375
+ > [!WARNING]
376
+ > `CookieStorage` and `BrowserStorage` store tokens accessible to JavaScript.
377
+ > For maximum security in production, consider:
378
+ > - Using httpOnly cookies set by your backend
379
+ > - Implementing a Backend-for-Frontend (BFF) pattern
380
+ > - Ensuring proper CSP headers to mitigate XSS
288
381
 
289
- // Organizations
290
- await client.organizations.list();
291
- await client.organizations.get(slug);
382
+ ### JWT Verification
292
383
 
293
- // And more...
294
- ```
384
+ The Next.js middleware and Node adapter provide independent JWT verification:
385
+ - **Next.js middleware**: Uses Web Crypto API (`crypto.subtle`) for Edge Runtime
386
+ - **Node adapter**: Uses Node.js `crypto` module
387
+
388
+ This duplication is intentional due to runtime differences. Both implementations
389
+ verify signatures using your JWKS endpoint.
295
390
 
296
391
  ## License
297
392