@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 +278 -167
- package/dist/index.d.mts +266 -16
- package/dist/index.d.ts +266 -16
- package/dist/index.js +288 -48
- package/dist/index.mjs +285 -50
- package/dist/nextjs.d.mts +30 -1
- package/dist/nextjs.d.ts +30 -1
- package/dist/nextjs.js +11 -0
- package/dist/nextjs.mjs +10 -1
- package/package.json +2 -2
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
|
-
##
|
|
8
|
+
## Quick Start (5 minutes)
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
npm install @drmhse/authos-react
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
81
|
+
### Using an Existing Client
|
|
20
82
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
Complete sign-in form with email/password authentication and optional OAuth buttons.
|
|
44
118
|
|
|
45
119
|
```tsx
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
161
|
+
### PasskeySignIn
|
|
82
162
|
|
|
83
|
-
|
|
163
|
+
Sign-in component for Passkeys (WebAuthn).
|
|
84
164
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
213
|
+
<Protect permission="admin:access" fallback={<p>Access denied</p>}>
|
|
214
|
+
<AdminDashboard />
|
|
215
|
+
</Protect>
|
|
113
216
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
235
|
+
Access the AuthOS client and auth state.
|
|
138
236
|
|
|
139
237
|
```tsx
|
|
140
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
249
|
+
const { user, isLoading } = useUser();
|
|
173
250
|
|
|
174
|
-
|
|
175
|
-
|
|
251
|
+
if (isLoading) return <Spinner />;
|
|
252
|
+
if (!user) return <SignIn />;
|
|
176
253
|
|
|
177
|
-
|
|
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
|
|
259
|
+
Get and switch between organizations.
|
|
191
260
|
|
|
192
261
|
```tsx
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
287
|
+
### App Router
|
|
238
288
|
|
|
239
289
|
```tsx
|
|
240
|
-
//
|
|
241
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
341
|
+
redirect('/login');
|
|
255
342
|
}
|
|
256
343
|
|
|
257
344
|
return <div>Welcome, {user.email}</div>;
|
|
258
345
|
}
|
|
259
346
|
```
|
|
260
347
|
|
|
261
|
-
##
|
|
348
|
+
## Configuration Reference
|
|
262
349
|
|
|
263
350
|
### AuthOSProvider Props
|
|
264
351
|
|
|
265
|
-
| Prop | Type |
|
|
266
|
-
|
|
267
|
-
| `config.baseURL` | `string` |
|
|
268
|
-
| `config.
|
|
269
|
-
| `config.
|
|
270
|
-
| `config.
|
|
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
|
-
|
|
379
|
+
## Security Considerations
|
|
273
380
|
|
|
274
|
-
|
|
381
|
+
### Token Storage
|
|
275
382
|
|
|
276
|
-
|
|
277
|
-
const { client } = useAuthOS();
|
|
383
|
+
The SDK offers multiple storage options:
|
|
278
384
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
await client.organizations.list();
|
|
291
|
-
await client.organizations.get(slug);
|
|
398
|
+
### JWT Verification
|
|
292
399
|
|
|
293
|
-
|
|
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
|
|