@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 +263 -168
- package/dist/index.d.mts +263 -15
- package/dist/index.d.ts +263 -15
- package/dist/index.js +277 -43
- package/dist/index.mjs +274 -45
- 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,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
|
-
##
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
|
171
|
+
User menu button with avatar initials and dropdown with profile/signout.
|
|
86
172
|
|
|
87
173
|
```tsx
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
197
|
+
<Protect permission="admin:access" fallback={<p>Access denied</p>}>
|
|
198
|
+
<AdminDashboard />
|
|
199
|
+
</Protect>
|
|
113
200
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
219
|
+
Access the AuthOS client and auth state.
|
|
138
220
|
|
|
139
221
|
```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>;
|
|
222
|
+
const { client, config, isAuthenticated, isLoading } = useAuthOS();
|
|
147
223
|
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
233
|
+
const { user, isLoading } = useUser();
|
|
173
234
|
|
|
174
|
-
|
|
175
|
-
|
|
235
|
+
if (isLoading) return <Spinner />;
|
|
236
|
+
if (!user) return <SignIn />;
|
|
176
237
|
|
|
177
|
-
|
|
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
|
|
243
|
+
Get and switch between organizations.
|
|
191
244
|
|
|
192
245
|
```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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
+
### App Router
|
|
238
272
|
|
|
239
273
|
```tsx
|
|
240
|
-
//
|
|
241
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
325
|
+
redirect('/login');
|
|
255
326
|
}
|
|
256
327
|
|
|
257
328
|
return <div>Welcome, {user.email}</div>;
|
|
258
329
|
}
|
|
259
330
|
```
|
|
260
331
|
|
|
261
|
-
##
|
|
332
|
+
## Configuration Reference
|
|
262
333
|
|
|
263
334
|
### AuthOSProvider Props
|
|
264
335
|
|
|
265
|
-
| Prop | Type |
|
|
266
|
-
|
|
267
|
-
| `config.baseURL` | `string` |
|
|
268
|
-
| `config.
|
|
269
|
-
| `config.
|
|
270
|
-
| `config.
|
|
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
|
-
|
|
363
|
+
## Security Considerations
|
|
273
364
|
|
|
274
|
-
|
|
365
|
+
### Token Storage
|
|
275
366
|
|
|
276
|
-
|
|
277
|
-
const { client } = useAuthOS();
|
|
367
|
+
The SDK offers multiple storage options:
|
|
278
368
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
await client.organizations.list();
|
|
291
|
-
await client.organizations.get(slug);
|
|
382
|
+
### JWT Verification
|
|
292
383
|
|
|
293
|
-
|
|
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
|
|