@agentuity/auth 0.0.101 → 0.0.102

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 CHANGED
@@ -2,593 +2,62 @@
2
2
 
3
3
  ## Package Overview
4
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.
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` (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)
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 dependencies**: Provider SDKs (Clerk, WorkOS, etc.) are optional peers
18
+ - **Peer deps**: Provider SDKs are optional peers
20
19
 
21
20
  ## Structure
22
21
 
23
22
  ```
24
23
  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
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 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
- ```
32
+ ## Code Conventions
80
33
 
81
- ### Hono Module Augmentation
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
- Each provider must augment Hono's types:
39
+ ## Key Patterns
84
40
 
85
41
  ```typescript
86
- // src/clerk/server.ts
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
- ## 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
49
+ // Error handling - include setup instructions
497
50
  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');
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
- ### 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`.
56
+ ## Adding New Providers
586
57
 
587
- **Q: How do I test without real provider accounts?**
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
- ## Related Documentation
60
+ ## Publishing
591
61
 
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
62
+ 1. Run build, typecheck, test
63
+ 2. Publish **after** `@agentuity/react` and `@agentuity/runtime`