@drmhse/authos-react 0.1.2 → 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,156 +5,224 @@
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>
17
52
  ```
18
53
 
19
- ## Quick Start
54
+ ### Multi-Tenant Access (Organizations)
55
+
56
+ For tenant applications with OAuth support, add `org` and `service`:
20
57
 
21
- Wrap your app with `AuthOSProvider`, then use the hooks and components throughout your app.
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:
22
69
 
23
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>
79
+ ```
80
+
81
+ ### Using an Existing Client
82
+
83
+ For advanced use cases, you can pass a pre-configured `SsoClient`:
84
+
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();
222
+ const { client, config, isAuthenticated, isLoading } = useAuthOS();
144
223
 
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
- };
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
228
  ### useUser
@@ -162,115 +230,163 @@ function Profile() {
162
230
  Get the current user's profile.
163
231
 
164
232
  ```tsx
165
- import { useUser } from '@drmhse/authos-react';
233
+ const { user, isLoading } = useUser();
166
234
 
167
- function UserProfile() {
168
- const { user, isLoading } = useUser();
235
+ if (isLoading) return <Spinner />;
236
+ if (!user) return <SignIn />;
169
237
 
170
- if (isLoading) return <div>Loading...</div>;
171
- return <div>Welcome, {user?.email}</div>;
172
- }
238
+ return <p>Welcome, {user.email}</p>;
173
239
  ```
174
240
 
175
241
  ### useOrganization
176
242
 
177
- Get the current organization and list of organizations.
243
+ Get and switch between organizations.
178
244
 
179
245
  ```tsx
180
- import { useOrganization } from '@drmhse/authos-react';
181
-
182
- function OrgInfo() {
183
- const { currentOrganization, organizations } = useOrganization();
184
-
185
- return (
186
- <div>
187
- <h3>{currentOrganization?.name}</h3>
188
- <ul>
189
- {organizations.map((org) => (
190
- <li key={org.id}>{org.name}</li>
191
- ))}
192
- </ul>
193
- </div>
194
- );
195
- }
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');
196
255
  ```
197
256
 
198
- ### usePermission, useAnyPermission, useAllPermissions
257
+ ### usePermission / useAnyPermission / useAllPermissions
199
258
 
200
259
  Check user permissions.
201
260
 
202
261
  ```tsx
203
- import { usePermission } from '@drmhse/authos-react';
204
-
205
- function AdminButton() {
206
- const canAccessAdmin = usePermission('admin:access');
207
-
208
- if (!canAccessAdmin) return null;
262
+ const canAccessAdmin = usePermission('admin:access');
263
+ const canReadOrWrite = useAnyPermission(['read:data', 'write:data']);
264
+ const hasFullAccess = useAllPermissions(['read:data', 'write:data', 'delete:data']);
209
265
 
210
- return <button>Admin Panel</button>;
211
- }
266
+ if (!canAccessAdmin) return <AccessDenied />;
212
267
  ```
213
268
 
214
269
  ## Next.js Integration
215
270
 
216
- For Next.js App Router, use the middleware and server utilities:
271
+ ### App Router
217
272
 
218
273
  ```tsx
219
- // middleware.ts
220
- import { authMiddleware } from '@drmhse/authos-react/nextjs';
274
+ // app/layout.tsx
275
+ import { AuthOSProvider } from '@drmhse/authos-react';
276
+ import { cookies } from 'next/headers';
221
277
 
222
- export default authMiddleware();
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
+ }
223
295
  ```
224
296
 
297
+ ### Middleware (Next.js)
298
+
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
+ };
313
+ ```
314
+
315
+ ### Server Components
316
+
225
317
  ```tsx
226
318
  // app/dashboard/page.tsx
227
319
  import { currentUser } from '@drmhse/authos-react/nextjs';
228
320
 
229
321
  export default async function Dashboard() {
230
322
  const user = await currentUser();
231
-
323
+
232
324
  if (!user) {
233
- return <div>Please log in</div>;
325
+ redirect('/login');
234
326
  }
235
327
 
236
328
  return <div>Welcome, {user.email}</div>;
237
329
  }
238
330
  ```
239
331
 
240
- ## API Reference
332
+ ## Configuration Reference
241
333
 
242
334
  ### AuthOSProvider Props
243
335
 
244
- | Prop | Type | Default | Description |
245
- |------|------|---------|-------------|
246
- | `config.baseURL` | `string` | **required** | AuthOS API URL |
247
- | `config.storage` | `TokenStorage` | `localStorage` | Custom token storage |
248
- | `config.autoRefresh` | `boolean` | `true` | Auto-refresh expired tokens |
249
- | `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
+ ```
250
362
 
251
- ### SsoClient
363
+ ## Security Considerations
252
364
 
253
- The underlying client from `@drmhse/sso-sdk`. See [SDK docs](https://www.npmjs.com/package/@drmhse/sso-sdk) for full API.
365
+ ### Token Storage
254
366
 
255
- ```tsx
256
- const { client } = useAuthOS();
367
+ The SDK offers multiple storage options:
257
368
 
258
- // Authentication
259
- await client.auth.login({ email, password });
260
- await client.auth.logout();
261
- 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 |
262
374
 
263
- // User
264
- await client.user.getProfile();
265
- await client.user.updateProfile({ name });
266
- 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
267
381
 
268
- // Organizations
269
- await client.organizations.list();
270
- await client.organizations.get(slug);
382
+ ### JWT Verification
271
383
 
272
- // And more...
273
- ```
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.
274
390
 
275
391
  ## License
276
392