@agentuity/auth 0.0.96
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/AGENTS.md +594 -0
- package/README.md +360 -0
- package/dist/clerk/client.d.ts +42 -0
- package/dist/clerk/client.d.ts.map +1 -0
- package/dist/clerk/client.js +65 -0
- package/dist/clerk/client.js.map +1 -0
- package/dist/clerk/index.d.ts +37 -0
- package/dist/clerk/index.d.ts.map +1 -0
- package/dist/clerk/index.js +35 -0
- package/dist/clerk/index.js.map +1 -0
- package/dist/clerk/server.d.ts +56 -0
- package/dist/clerk/server.d.ts.map +1 -0
- package/dist/clerk/server.js +110 -0
- package/dist/clerk/server.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/clerk/client.tsx +86 -0
- package/src/clerk/index.ts +37 -0
- package/src/clerk/server.ts +168 -0
- package/src/index.ts +18 -0
- package/src/types.ts +38 -0
- package/test/clerk-client.test.tsx +21 -0
- package/test/clerk-server.test.ts +34 -0
- package/tsconfig.json +12 -0
- package/tsconfig.test.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
# Agent Guidelines for @agentuity/auth
|
|
2
|
+
|
|
3
|
+
## Package Overview
|
|
4
|
+
|
|
5
|
+
Authentication helpers for popular identity providers. Provides client-side React components and server-side Hono middleware for seamless auth integration with Agentuity applications.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
- **Build**: `bun run build` (generates TypeScript declarations)
|
|
10
|
+
- **Typecheck**: `bun run typecheck` (runs TypeScript type checking)
|
|
11
|
+
- **Clean**: `bun run clean` (removes dist/)
|
|
12
|
+
- **Test**: `bun test` (runs all tests)
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
- **Runtime**: Dual-target (browser for client, Bun/Node for server)
|
|
17
|
+
- **Build target**: TypeScript declarations only (source distribution)
|
|
18
|
+
- **Dependencies**: `@agentuity/react` (client), `@agentuity/runtime` (server)
|
|
19
|
+
- **Peer dependencies**: Provider SDKs (Clerk, WorkOS, etc.) are optional peers
|
|
20
|
+
|
|
21
|
+
## Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
├── index.ts # Core exports (types only)
|
|
26
|
+
├── types.ts # Shared TypeScript interfaces
|
|
27
|
+
├── clerk/
|
|
28
|
+
│ ├── index.ts # Re-exports client and server
|
|
29
|
+
│ ├── client.tsx # AgentuityClerk React component
|
|
30
|
+
│ └── server.ts # createMiddleware for Hono
|
|
31
|
+
└── <provider>/ # Future providers (workos, auth0, etc.)
|
|
32
|
+
├── index.ts
|
|
33
|
+
├── client.tsx
|
|
34
|
+
└── server.ts
|
|
35
|
+
test/
|
|
36
|
+
├── clerk-client.test.tsx # Client component tests
|
|
37
|
+
└── clerk-server.test.ts # Server middleware tests
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Code Style
|
|
41
|
+
|
|
42
|
+
- **React components** - Follow React hooks conventions (client.tsx files)
|
|
43
|
+
- **TypeScript generics** - Heavy use of generics for type safety (`AgentuityAuthUser<T>`, `AgentuityAuth<TUser, TRaw>`)
|
|
44
|
+
- **Provider isolation** - Each provider in separate directory with own exports
|
|
45
|
+
- **Tree shaking** - Import paths like `@agentuity/auth/clerk` enable tree shaking
|
|
46
|
+
|
|
47
|
+
## Important Conventions
|
|
48
|
+
|
|
49
|
+
### Provider Structure
|
|
50
|
+
|
|
51
|
+
Each provider follows a consistent structure:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
<provider>/
|
|
55
|
+
├── index.ts # Re-exports client and server, main entry point
|
|
56
|
+
├── client.tsx # React component for client-side auth
|
|
57
|
+
└── server.ts # Hono middleware for server-side validation
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Type Safety
|
|
61
|
+
|
|
62
|
+
Providers use generic types for full type safety:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Generic user type with provider-specific raw object
|
|
66
|
+
export interface AgentuityAuthUser<T = unknown> {
|
|
67
|
+
id: string;
|
|
68
|
+
name?: string;
|
|
69
|
+
email?: string;
|
|
70
|
+
raw: T; // Fully typed provider-specific user object
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Generic auth interface with user and payload types
|
|
74
|
+
export interface AgentuityAuth<TUser = unknown, TRaw = unknown> {
|
|
75
|
+
requireUser(): Promise<AgentuityAuthUser<TUser>>;
|
|
76
|
+
getToken(): Promise<string | null>;
|
|
77
|
+
raw: TRaw; // Fully typed provider-specific auth object (JWT payload, session, etc.)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Hono Module Augmentation
|
|
82
|
+
|
|
83
|
+
Each provider must augment Hono's types:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// src/clerk/server.ts
|
|
87
|
+
declare module 'hono' {
|
|
88
|
+
interface ContextVariableMap {
|
|
89
|
+
auth: AgentuityAuth<User, ClerkJWTPayload>;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This provides full type safety when accessing `c.var.auth` in routes.
|
|
95
|
+
|
|
96
|
+
### Environment Variables
|
|
97
|
+
|
|
98
|
+
Providers should support these patterns:
|
|
99
|
+
|
|
100
|
+
- **Public keys**: `AGENTUITY_PUBLIC_<PROVIDER>_<KEY>` (bundled into frontend)
|
|
101
|
+
- **Secret keys**: `<PROVIDER>_SECRET_KEY` or `<PROVIDER>_<KEY>` (server-only)
|
|
102
|
+
- **Fallback**: Also check standard provider env var names for compatibility
|
|
103
|
+
|
|
104
|
+
Example for Clerk:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const publishableKey =
|
|
108
|
+
options.publishableKey ||
|
|
109
|
+
process.env.AGENTUITY_PUBLIC_CLERK_PUBLISHABLE_KEY ||
|
|
110
|
+
process.env.CLERK_PUBLISHABLE_KEY;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Error Handling
|
|
114
|
+
|
|
115
|
+
- **Missing env vars**: Log clear error messages with setup instructions
|
|
116
|
+
- **Invalid tokens**: Return 401 with generic error message (don't leak token details)
|
|
117
|
+
- **Provider errors**: Catch and wrap with generic 401 response
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
if (!secretKey) {
|
|
121
|
+
console.error(
|
|
122
|
+
'[Clerk Auth] CLERK_SECRET_KEY is not set. Add it to your .env file or pass secretKey option to createMiddleware()'
|
|
123
|
+
);
|
|
124
|
+
throw new Error('Clerk secret key is required (set CLERK_SECRET_KEY or pass secretKey option)');
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Testing
|
|
129
|
+
|
|
130
|
+
### Test Organization
|
|
131
|
+
|
|
132
|
+
Follow SDK testing standards:
|
|
133
|
+
|
|
134
|
+
- All tests in `test/` folder
|
|
135
|
+
- Client tests: `test/<provider>-client.test.tsx`
|
|
136
|
+
- Server tests: `test/<provider>-server.test.ts`
|
|
137
|
+
|
|
138
|
+
### Testing Strategy
|
|
139
|
+
|
|
140
|
+
1. **Client tests**: Verify component exports and prop interfaces (DOM testing is complex, keep simple)
|
|
141
|
+
2. **Server tests**: Test middleware behavior (401 responses, env var validation)
|
|
142
|
+
3. **Integration tests**: Use templates for end-to-end testing
|
|
143
|
+
|
|
144
|
+
### Example Test Pattern
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { describe, test, expect } from 'bun:test';
|
|
148
|
+
import { createMiddleware } from '../src/clerk/server';
|
|
149
|
+
|
|
150
|
+
describe('Clerk server middleware', () => {
|
|
151
|
+
test('returns 401 when Authorization header is missing', async () => {
|
|
152
|
+
const app = new Hono();
|
|
153
|
+
app.use('/protected', createMiddleware());
|
|
154
|
+
app.get('/protected', (c) => c.json({ success: true }));
|
|
155
|
+
|
|
156
|
+
const res = await app.request('/protected', { method: 'GET' });
|
|
157
|
+
expect(res.status).toBe(401);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Adding a New Provider
|
|
163
|
+
|
|
164
|
+
### 1. Create Provider Directory
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
mkdir -p src/workos
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 2. Implement Client Component
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// src/workos/client.tsx
|
|
174
|
+
import React, { useEffect } from 'react';
|
|
175
|
+
import { useAgentuity } from '@agentuity/react';
|
|
176
|
+
import type { useAuth as WorkOSUseAuth } from '@workos/react';
|
|
177
|
+
|
|
178
|
+
export interface AgentuityWorkOSProps {
|
|
179
|
+
children: React.ReactNode;
|
|
180
|
+
useAuth: typeof WorkOSUseAuth;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function AgentuityWorkOS({ children, useAuth }: AgentuityWorkOSProps) {
|
|
184
|
+
const { getToken, isLoaded } = useAuth();
|
|
185
|
+
const { setAuthHeader, setAuthLoading } = useAgentuity();
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (!isLoaded || !setAuthHeader || !setAuthLoading) {
|
|
189
|
+
setAuthLoading?.(true);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const fetchToken = async () => {
|
|
194
|
+
try {
|
|
195
|
+
setAuthLoading(true);
|
|
196
|
+
const token = await getToken();
|
|
197
|
+
setAuthHeader(token ? `Bearer ${token}` : null);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('Failed to get WorkOS token:', error);
|
|
200
|
+
setAuthHeader(null);
|
|
201
|
+
} finally {
|
|
202
|
+
setAuthLoading(false);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
fetchToken();
|
|
207
|
+
}, [getToken, isLoaded, setAuthHeader, setAuthLoading]);
|
|
208
|
+
|
|
209
|
+
return <>{children}</>;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 3. Implement Server Middleware
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// src/workos/server.ts
|
|
217
|
+
import type { MiddlewareHandler } from 'hono';
|
|
218
|
+
import { WorkOS } from '@workos-inc/node';
|
|
219
|
+
import type { AgentuityAuth, AgentuityAuthUser } from '../types';
|
|
220
|
+
|
|
221
|
+
export interface WorkOSMiddlewareOptions {
|
|
222
|
+
apiKey?: string;
|
|
223
|
+
clientId?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function createMiddleware(options: WorkOSMiddlewareOptions = {}): MiddlewareHandler {
|
|
227
|
+
const apiKey = options.apiKey || process.env.WORKOS_API_KEY;
|
|
228
|
+
const clientId = options.clientId || process.env.WORKOS_CLIENT_ID;
|
|
229
|
+
|
|
230
|
+
if (!apiKey) {
|
|
231
|
+
console.error('[WorkOS Auth] WORKOS_API_KEY is not set');
|
|
232
|
+
throw new Error('WorkOS API key is required');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const workos = new WorkOS(apiKey);
|
|
236
|
+
|
|
237
|
+
return async (c, next) => {
|
|
238
|
+
const authHeader = c.req.header('Authorization');
|
|
239
|
+
|
|
240
|
+
if (!authHeader) {
|
|
241
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const token = authHeader.replace(/^Bearer\s+/i, '');
|
|
246
|
+
|
|
247
|
+
// Verify token with WorkOS
|
|
248
|
+
const { user } = await workos.userManagement.authenticateWithSessionCookie(token);
|
|
249
|
+
|
|
250
|
+
const auth: AgentuityAuth<typeof user, unknown> = {
|
|
251
|
+
async requireUser() {
|
|
252
|
+
return {
|
|
253
|
+
id: user.id,
|
|
254
|
+
email: user.email,
|
|
255
|
+
name: `${user.firstName} ${user.lastName}`.trim(),
|
|
256
|
+
raw: user,
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
async getToken() {
|
|
260
|
+
return token;
|
|
261
|
+
},
|
|
262
|
+
raw: {}, // WorkOS session data
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
c.set('auth', auth);
|
|
266
|
+
await next();
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('WorkOS auth error:', error);
|
|
269
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
declare module 'hono' {
|
|
275
|
+
interface ContextVariableMap {
|
|
276
|
+
auth: AgentuityAuth<any, unknown>;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 4. Create Index File
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// src/workos/index.ts
|
|
285
|
+
export { AgentuityWorkOS } from './client';
|
|
286
|
+
export type { AgentuityWorkOSProps } from './client';
|
|
287
|
+
export { createMiddleware } from './server';
|
|
288
|
+
export type { WorkOSMiddlewareOptions } from './server';
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 5. Update Package Exports
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
// package.json
|
|
295
|
+
{
|
|
296
|
+
"exports": {
|
|
297
|
+
".": "./src/index.ts",
|
|
298
|
+
"./clerk": "./src/clerk/index.ts",
|
|
299
|
+
"./workos": "./src/workos/index.ts"
|
|
300
|
+
},
|
|
301
|
+
"peerDependencies": {
|
|
302
|
+
"@workos-inc/node": "^7.0.0"
|
|
303
|
+
},
|
|
304
|
+
"peerDependenciesMeta": {
|
|
305
|
+
"@workos-inc/node": { "optional": true }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 6. Write Tests
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// test/workos-server.test.ts
|
|
314
|
+
import { describe, test, expect } from 'bun:test';
|
|
315
|
+
import { createMiddleware } from '../src/workos/server';
|
|
316
|
+
|
|
317
|
+
describe('WorkOS server middleware', () => {
|
|
318
|
+
test('throws error when WORKOS_API_KEY is missing', () => {
|
|
319
|
+
delete process.env.WORKOS_API_KEY;
|
|
320
|
+
expect(() => createMiddleware()).toThrow('WorkOS API key is required');
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 7. Create Template (Optional)
|
|
326
|
+
|
|
327
|
+
Create a template in `templates/workos/` following the same pattern as `templates/clerk/`.
|
|
328
|
+
|
|
329
|
+
## Integration with @agentuity/react
|
|
330
|
+
|
|
331
|
+
The auth package integrates with `@agentuity/react` through context:
|
|
332
|
+
|
|
333
|
+
### Context Updates
|
|
334
|
+
|
|
335
|
+
Auth providers set these context values:
|
|
336
|
+
|
|
337
|
+
- `authHeader` - The full Authorization header value (e.g., `"Bearer token123"`)
|
|
338
|
+
- `authLoading` - Whether auth is still initializing
|
|
339
|
+
|
|
340
|
+
### Hook Integration
|
|
341
|
+
|
|
342
|
+
`useAPI` and `useWebsocket` automatically use these values:
|
|
343
|
+
|
|
344
|
+
- **useAPI**: Adds `Authorization` header to all HTTP requests
|
|
345
|
+
- **useWebsocket**: Adds `token` query parameter (WebSocket can't send custom headers)
|
|
346
|
+
|
|
347
|
+
Both hooks include `context.authHeader` in their dependency arrays, so they react to auth changes.
|
|
348
|
+
|
|
349
|
+
## Common Patterns
|
|
350
|
+
|
|
351
|
+
### Conditional Rendering Based on Auth
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { useAgentuity } from '@agentuity/react';
|
|
355
|
+
|
|
356
|
+
function ProtectedComponent() {
|
|
357
|
+
const { isAuthenticated, authLoading } = useAgentuity();
|
|
358
|
+
|
|
359
|
+
if (authLoading) {
|
|
360
|
+
return <div>Loading...</div>;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!isAuthenticated) {
|
|
364
|
+
return <SignInButton />;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return <div>Protected content</div>;
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Optional Auth Routes
|
|
372
|
+
|
|
373
|
+
Most routes should be protected, but for optional auth:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// Don't use middleware, check auth manually
|
|
377
|
+
router.get('/public-or-personalized', async (c) => {
|
|
378
|
+
// Check if auth was set by global middleware
|
|
379
|
+
const authHeader = c.req.header('Authorization');
|
|
380
|
+
|
|
381
|
+
if (authHeader) {
|
|
382
|
+
try {
|
|
383
|
+
const user = await c.var.auth?.requireUser();
|
|
384
|
+
return c.json({ personalized: true, userId: user?.id });
|
|
385
|
+
} catch {
|
|
386
|
+
// Auth failed, treat as public
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return c.json({ personalized: false });
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Access Token Directly
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
router.get('/token-info', createMiddleware(), async (c) => {
|
|
398
|
+
const token = await c.var.auth.getToken();
|
|
399
|
+
// Use token to call external APIs
|
|
400
|
+
const response = await fetch('https://external-api.com/data', {
|
|
401
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
402
|
+
});
|
|
403
|
+
return c.json(await response.json());
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Technical Details
|
|
408
|
+
|
|
409
|
+
### Why Two Generic Parameters?
|
|
410
|
+
|
|
411
|
+
`AgentuityAuth<TUser, TRaw>` has two generics to provide type safety for:
|
|
412
|
+
|
|
413
|
+
1. **TUser**: The provider's user object type (e.g., Clerk's `User`)
|
|
414
|
+
2. **TRaw**: The provider's auth/session object type (e.g., JWT payload, session data)
|
|
415
|
+
|
|
416
|
+
This allows:
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const user = await c.var.auth.requireUser();
|
|
420
|
+
user.raw; // Fully typed as Clerk User
|
|
421
|
+
|
|
422
|
+
const payload = c.var.auth.raw;
|
|
423
|
+
payload.sub; // Fully typed as ClerkJWTPayload
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Token Refresh Strategy
|
|
427
|
+
|
|
428
|
+
Each provider handles token refresh differently:
|
|
429
|
+
|
|
430
|
+
- **Clerk**: Provider handles expiry internally, we poll periodically (default: 60s)
|
|
431
|
+
- **WorkOS**: Session cookies with expiry, refresh on demand
|
|
432
|
+
- **Auth0**: Refresh tokens, exchange on expiry
|
|
433
|
+
|
|
434
|
+
The `refreshInterval` prop allows customization per provider.
|
|
435
|
+
|
|
436
|
+
### WebSocket Authentication
|
|
437
|
+
|
|
438
|
+
WebSockets don't support custom headers, so we pass tokens via query parameter:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// In @agentuity/react/src/websocket.ts
|
|
442
|
+
if (context.authHeader) {
|
|
443
|
+
const token = context.authHeader.replace(/^Bearer\s+/i, '');
|
|
444
|
+
queryParams.set('token', token);
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Server middleware should extract from query param for WebSocket routes.
|
|
449
|
+
|
|
450
|
+
## Maintenance
|
|
451
|
+
|
|
452
|
+
### When Adding Provider Support
|
|
453
|
+
|
|
454
|
+
1. Research provider's auth flow (JWT, session, OAuth, etc.)
|
|
455
|
+
2. Identify token extraction method (header, cookie, etc.)
|
|
456
|
+
3. Implement client component (token fetching)
|
|
457
|
+
4. Implement server middleware (token validation)
|
|
458
|
+
5. Add type augmentation for Hono
|
|
459
|
+
6. Write basic tests
|
|
460
|
+
7. Create template if commonly used
|
|
461
|
+
8. Update README with usage example
|
|
462
|
+
|
|
463
|
+
### When Updating Dependencies
|
|
464
|
+
|
|
465
|
+
Check compatibility:
|
|
466
|
+
|
|
467
|
+
- Provider SDK major version changes
|
|
468
|
+
- Hono type system changes
|
|
469
|
+
- React version compatibility
|
|
470
|
+
|
|
471
|
+
### Common Issues
|
|
472
|
+
|
|
473
|
+
**"Module not found" errors**: Ensure export paths are correct in package.json
|
|
474
|
+
|
|
475
|
+
**Type errors in routes**: Verify Hono module augmentation is present
|
|
476
|
+
|
|
477
|
+
**401 errors**: Check:
|
|
478
|
+
|
|
479
|
+
1. Environment variables set correctly
|
|
480
|
+
2. Token format matches provider expectations
|
|
481
|
+
3. Provider SDK configured correctly
|
|
482
|
+
|
|
483
|
+
## Code Conventions
|
|
484
|
+
|
|
485
|
+
### Naming
|
|
486
|
+
|
|
487
|
+
- Client components: `Agentuity<Provider>` (e.g., `AgentuityClerk`)
|
|
488
|
+
- Server functions: `createMiddleware()` (consistent across providers)
|
|
489
|
+
- Props interfaces: `Agentuity<Provider>Props`
|
|
490
|
+
- Options interfaces: `<Provider>MiddlewareOptions`
|
|
491
|
+
|
|
492
|
+
### Error Messages
|
|
493
|
+
|
|
494
|
+
Include setup instructions in error messages:
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
if (!secretKey) {
|
|
498
|
+
console.error(
|
|
499
|
+
'[Provider Auth] PROVIDER_SECRET_KEY is not set. Add it to your .env file or pass secretKey option'
|
|
500
|
+
);
|
|
501
|
+
throw new Error('Provider secret key is required');
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Exports
|
|
506
|
+
|
|
507
|
+
Each provider exports from its index.ts:
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
// src/clerk/index.ts
|
|
511
|
+
export { AgentuityClerk } from './client';
|
|
512
|
+
export type { AgentuityClerkProps } from './client';
|
|
513
|
+
export { createMiddleware } from './server';
|
|
514
|
+
export type { ClerkMiddlewareOptions, ClerkJWTPayload } from './server';
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Main package exports only core types:
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
// src/index.ts
|
|
521
|
+
export type { AgentuityAuthUser, AgentuityAuth } from './types';
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Publishing Checklist
|
|
525
|
+
|
|
526
|
+
1. Run `bun run build` to generate type declarations
|
|
527
|
+
2. Run `bun run typecheck` to verify no type errors
|
|
528
|
+
3. Run `bun test` to ensure tests pass
|
|
529
|
+
4. Verify peer dependencies are correctly marked as optional
|
|
530
|
+
5. Must publish **after** `@agentuity/react` and `@agentuity/runtime`
|
|
531
|
+
6. Update templates if provider support changes
|
|
532
|
+
|
|
533
|
+
## Security Considerations
|
|
534
|
+
|
|
535
|
+
### Never Log Secrets
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
// ❌ WRONG
|
|
539
|
+
console.log('Token:', token);
|
|
540
|
+
console.log('Auth header:', authHeader);
|
|
541
|
+
|
|
542
|
+
// ✅ CORRECT
|
|
543
|
+
console.log('Auth header present:', !!authHeader);
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Validate on Every Request
|
|
547
|
+
|
|
548
|
+
Don't cache validation results - providers may revoke tokens:
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
// Middleware validates on every request
|
|
552
|
+
return async (c, next) => {
|
|
553
|
+
const token = extractToken(c);
|
|
554
|
+
const payload = await verifyToken(token); // Fresh validation
|
|
555
|
+
c.set('auth', createAuthObject(payload));
|
|
556
|
+
await next();
|
|
557
|
+
};
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Use Provider SDKs
|
|
561
|
+
|
|
562
|
+
Always use the official provider SDK for validation:
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// ✅ CORRECT
|
|
566
|
+
import { verifyToken } from '@clerk/backend';
|
|
567
|
+
await verifyToken(token, { secretKey });
|
|
568
|
+
|
|
569
|
+
// ❌ WRONG (don't manually verify JWTs)
|
|
570
|
+
const decoded = jwt.verify(token, secretKey);
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## FAQ
|
|
574
|
+
|
|
575
|
+
**Q: Why not use provider middleware directly?**
|
|
576
|
+
A: Provider middleware is often framework-specific (Express, Next.js). We provide a thin Hono wrapper with a generic interface.
|
|
577
|
+
|
|
578
|
+
**Q: Can I use multiple providers?**
|
|
579
|
+
A: Not simultaneously. Choose one provider per app.
|
|
580
|
+
|
|
581
|
+
**Q: How do I handle refresh tokens?**
|
|
582
|
+
A: Most providers handle this internally. We refresh periodically to get the latest token.
|
|
583
|
+
|
|
584
|
+
**Q: What about server-side session cookies?**
|
|
585
|
+
A: Some providers (WorkOS, Auth0) use cookies. Extract from `c.req.header('Cookie')` instead of `Authorization`.
|
|
586
|
+
|
|
587
|
+
**Q: How do I test without real provider accounts?**
|
|
588
|
+
A: Mock the provider SDK functions. See test files for examples.
|
|
589
|
+
|
|
590
|
+
## Related Documentation
|
|
591
|
+
|
|
592
|
+
- [SDK AGENTS.md](../../AGENTS.md) - Monorepo development guidelines
|
|
593
|
+
- [README.md](./README.md) - User-facing documentation
|
|
594
|
+
- [Clerk Template](../../templates/clerk/) - Complete working example
|