@agentuity/auth 0.0.101 → 0.0.103
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 +27 -558
- package/docs/adding-providers.md +261 -0
- package/package.json +6 -6
- package/tsconfig.tsbuildinfo +1 -1
package/AGENTS.md
CHANGED
|
@@ -2,593 +2,62 @@
|
|
|
2
2
|
|
|
3
3
|
## Package Overview
|
|
4
4
|
|
|
5
|
-
Authentication helpers for
|
|
5
|
+
Authentication helpers for identity providers (Clerk, WorkOS, etc.). Provides React components and Hono middleware.
|
|
6
6
|
|
|
7
7
|
## Commands
|
|
8
8
|
|
|
9
|
-
- **Build**: `bun run build`
|
|
10
|
-
- **Typecheck**: `bun run typecheck`
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
9
|
+
- **Build**: `bun run build`
|
|
10
|
+
- **Typecheck**: `bun run typecheck`
|
|
11
|
+
- **Test**: `bun test`
|
|
12
|
+
- **Clean**: `bun run clean`
|
|
13
13
|
|
|
14
14
|
## Architecture
|
|
15
15
|
|
|
16
16
|
- **Runtime**: Dual-target (browser for client, Bun/Node for server)
|
|
17
|
-
- **Build target**: TypeScript declarations only (source distribution)
|
|
18
17
|
- **Dependencies**: `@agentuity/react` (client), `@agentuity/runtime` (server)
|
|
19
|
-
- **Peer
|
|
18
|
+
- **Peer deps**: Provider SDKs are optional peers
|
|
20
19
|
|
|
21
20
|
## Structure
|
|
22
21
|
|
|
23
22
|
```
|
|
24
23
|
src/
|
|
25
|
-
├── index.ts
|
|
26
|
-
├── types.ts
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
24
|
+
├── index.ts # Core type exports
|
|
25
|
+
├── types.ts # AgentuityAuth, AgentuityAuthUser interfaces
|
|
26
|
+
└── clerk/ # (or other provider)
|
|
27
|
+
├── index.ts # Re-exports
|
|
28
|
+
├── client.tsx # React component
|
|
29
|
+
└── server.ts # Hono middleware
|
|
38
30
|
```
|
|
39
31
|
|
|
40
|
-
## Code
|
|
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
|
-
```
|
|
32
|
+
## Code Conventions
|
|
80
33
|
|
|
81
|
-
|
|
34
|
+
- **Naming**: `Agentuity<Provider>` for components, `createMiddleware()` for server
|
|
35
|
+
- **Type safety**: Use generics `AgentuityAuth<TUser, TRaw>`
|
|
36
|
+
- **Tree shaking**: Import paths like `@agentuity/auth/clerk`
|
|
37
|
+
- **Env vars**: Support `AGENTUITY_PUBLIC_<PROVIDER>_*` and standard provider names
|
|
82
38
|
|
|
83
|
-
|
|
39
|
+
## Key Patterns
|
|
84
40
|
|
|
85
41
|
```typescript
|
|
86
|
-
//
|
|
42
|
+
// Hono module augmentation (required per provider)
|
|
87
43
|
declare module 'hono' {
|
|
88
44
|
interface ContextVariableMap {
|
|
89
45
|
auth: AgentuityAuth<User, ClerkJWTPayload>;
|
|
90
46
|
}
|
|
91
47
|
}
|
|
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 { useAuth } 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: workosUseAuth }: AgentuityWorkOSProps) {
|
|
184
|
-
const { getToken, isLoaded } = workosUseAuth();
|
|
185
|
-
const { setAuthHeader, setAuthLoading } = useAuth();
|
|
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 { useAuth } from '@agentuity/react';
|
|
355
|
-
|
|
356
|
-
function ProtectedComponent() {
|
|
357
|
-
const { isAuthenticated, authLoading } = useAuth();
|
|
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
48
|
|
|
450
|
-
|
|
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
|
|
49
|
+
// Error handling - include setup instructions
|
|
497
50
|
if (!secretKey) {
|
|
498
|
-
console.error(
|
|
499
|
-
|
|
500
|
-
);
|
|
501
|
-
throw new Error('Provider secret key is required');
|
|
51
|
+
console.error('[Provider] SECRET_KEY not set. Add to .env');
|
|
52
|
+
throw new Error('Provider secret key required');
|
|
502
53
|
}
|
|
503
54
|
```
|
|
504
55
|
|
|
505
|
-
|
|
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`.
|
|
56
|
+
## Adding New Providers
|
|
586
57
|
|
|
587
|
-
|
|
588
|
-
A: Mock the provider SDK functions. See test files for examples.
|
|
58
|
+
See [docs/adding-providers.md](docs/adding-providers.md) for full implementation guide.
|
|
589
59
|
|
|
590
|
-
##
|
|
60
|
+
## Publishing
|
|
591
61
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
- [Clerk Template](../../templates/clerk/) - Complete working example
|
|
62
|
+
1. Run build, typecheck, test
|
|
63
|
+
2. Publish **after** `@agentuity/react` and `@agentuity/runtime`
|