@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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Adding New Auth Providers
|
|
2
|
+
|
|
3
|
+
Guide for implementing new authentication providers in `@agentuity/auth`.
|
|
4
|
+
|
|
5
|
+
## Provider Structure
|
|
6
|
+
|
|
7
|
+
Each provider follows a consistent structure:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/<provider>/
|
|
11
|
+
├── index.ts # Re-exports client and server
|
|
12
|
+
├── client.tsx # React component for client-side auth
|
|
13
|
+
└── server.ts # Hono middleware for server-side validation
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Step 1: Create Provider Directory
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
mkdir -p src/workos
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Step 2: Implement Client Component
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// src/workos/client.tsx
|
|
26
|
+
import React, { useEffect } from 'react';
|
|
27
|
+
import { useAuth } from '@agentuity/react';
|
|
28
|
+
import type { useAuth as WorkOSUseAuth } from '@workos/react';
|
|
29
|
+
|
|
30
|
+
export interface AgentuityWorkOSProps {
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
useAuth: typeof WorkOSUseAuth;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AgentuityWorkOS({ children, useAuth: workosUseAuth }: AgentuityWorkOSProps) {
|
|
36
|
+
const { getToken, isLoaded } = workosUseAuth();
|
|
37
|
+
const { setAuthHeader, setAuthLoading } = useAuth();
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!isLoaded || !setAuthHeader || !setAuthLoading) {
|
|
41
|
+
setAuthLoading?.(true);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fetchToken = async () => {
|
|
46
|
+
try {
|
|
47
|
+
setAuthLoading(true);
|
|
48
|
+
const token = await getToken();
|
|
49
|
+
setAuthHeader(token ? `Bearer ${token}` : null);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to get WorkOS token:', error);
|
|
52
|
+
setAuthHeader(null);
|
|
53
|
+
} finally {
|
|
54
|
+
setAuthLoading(false);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
fetchToken();
|
|
59
|
+
}, [getToken, isLoaded, setAuthHeader, setAuthLoading]);
|
|
60
|
+
|
|
61
|
+
return <>{children}</>;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Step 3: Implement Server Middleware
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// src/workos/server.ts
|
|
69
|
+
import type { MiddlewareHandler } from 'hono';
|
|
70
|
+
import { WorkOS } from '@workos-inc/node';
|
|
71
|
+
import type { AgentuityAuth, AgentuityAuthUser } from '../types';
|
|
72
|
+
|
|
73
|
+
export interface WorkOSMiddlewareOptions {
|
|
74
|
+
apiKey?: string;
|
|
75
|
+
clientId?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createMiddleware(options: WorkOSMiddlewareOptions = {}): MiddlewareHandler {
|
|
79
|
+
const apiKey = options.apiKey || process.env.WORKOS_API_KEY;
|
|
80
|
+
const clientId = options.clientId || process.env.WORKOS_CLIENT_ID;
|
|
81
|
+
|
|
82
|
+
if (!apiKey) {
|
|
83
|
+
console.error('[WorkOS Auth] WORKOS_API_KEY is not set');
|
|
84
|
+
throw new Error('WorkOS API key is required');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const workos = new WorkOS(apiKey);
|
|
88
|
+
|
|
89
|
+
return async (c, next) => {
|
|
90
|
+
const authHeader = c.req.header('Authorization');
|
|
91
|
+
|
|
92
|
+
if (!authHeader) {
|
|
93
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const token = authHeader.replace(/^Bearer\s+/i, '');
|
|
98
|
+
const { user } = await workos.userManagement.authenticateWithSessionCookie(token);
|
|
99
|
+
|
|
100
|
+
const auth: AgentuityAuth<typeof user, unknown> = {
|
|
101
|
+
async requireUser() {
|
|
102
|
+
return {
|
|
103
|
+
id: user.id,
|
|
104
|
+
email: user.email,
|
|
105
|
+
name: `${user.firstName} ${user.lastName}`.trim(),
|
|
106
|
+
raw: user,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
async getToken() {
|
|
110
|
+
return token;
|
|
111
|
+
},
|
|
112
|
+
raw: {},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
c.set('auth', auth);
|
|
116
|
+
await next();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('WorkOS auth error:', error);
|
|
119
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
declare module 'hono' {
|
|
125
|
+
interface ContextVariableMap {
|
|
126
|
+
auth: AgentuityAuth<any, unknown>;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Step 4: Create Index File
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// src/workos/index.ts
|
|
135
|
+
export { AgentuityWorkOS } from './client';
|
|
136
|
+
export type { AgentuityWorkOSProps } from './client';
|
|
137
|
+
export { createMiddleware } from './server';
|
|
138
|
+
export type { WorkOSMiddlewareOptions } from './server';
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Step 5: Update Package Exports
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
// package.json
|
|
145
|
+
{
|
|
146
|
+
"exports": {
|
|
147
|
+
".": "./src/index.ts",
|
|
148
|
+
"./clerk": "./src/clerk/index.ts",
|
|
149
|
+
"./workos": "./src/workos/index.ts"
|
|
150
|
+
},
|
|
151
|
+
"peerDependencies": {
|
|
152
|
+
"@workos-inc/node": "^7.0.0"
|
|
153
|
+
},
|
|
154
|
+
"peerDependenciesMeta": {
|
|
155
|
+
"@workos-inc/node": { "optional": true }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Step 6: Write Tests
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// test/workos-server.test.ts
|
|
164
|
+
import { describe, test, expect } from 'bun:test';
|
|
165
|
+
import { createMiddleware } from '../src/workos/server';
|
|
166
|
+
|
|
167
|
+
describe('WorkOS server middleware', () => {
|
|
168
|
+
test('throws error when WORKOS_API_KEY is missing', () => {
|
|
169
|
+
delete process.env.WORKOS_API_KEY;
|
|
170
|
+
expect(() => createMiddleware()).toThrow('WorkOS API key is required');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Type Safety Requirements
|
|
176
|
+
|
|
177
|
+
### Generic Types
|
|
178
|
+
|
|
179
|
+
Providers must use generic types for full type safety:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
export interface AgentuityAuthUser<T = unknown> {
|
|
183
|
+
id: string;
|
|
184
|
+
name?: string;
|
|
185
|
+
email?: string;
|
|
186
|
+
raw: T; // Provider-specific user object
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface AgentuityAuth<TUser = unknown, TRaw = unknown> {
|
|
190
|
+
requireUser(): Promise<AgentuityAuthUser<TUser>>;
|
|
191
|
+
getToken(): Promise<string | null>;
|
|
192
|
+
raw: TRaw; // Provider-specific auth object
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Hono Module Augmentation
|
|
197
|
+
|
|
198
|
+
Each provider must augment Hono's types:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
declare module 'hono' {
|
|
202
|
+
interface ContextVariableMap {
|
|
203
|
+
auth: AgentuityAuth<User, ClerkJWTPayload>;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Environment Variables
|
|
209
|
+
|
|
210
|
+
Support these patterns:
|
|
211
|
+
|
|
212
|
+
- **Public keys**: `AGENTUITY_PUBLIC_<PROVIDER>_<KEY>`
|
|
213
|
+
- **Secret keys**: `<PROVIDER>_SECRET_KEY`
|
|
214
|
+
- **Fallback**: Standard provider env var names
|
|
215
|
+
|
|
216
|
+
## Common Patterns
|
|
217
|
+
|
|
218
|
+
### Conditional Rendering
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
function ProtectedComponent() {
|
|
222
|
+
const { isAuthenticated, authLoading } = useAuth();
|
|
223
|
+
|
|
224
|
+
if (authLoading) return <div>Loading...</div>;
|
|
225
|
+
if (!isAuthenticated) return <SignInButton />;
|
|
226
|
+
return <div>Protected content</div>;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Optional Auth Routes
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
router.get('/public-or-personalized', async (c) => {
|
|
234
|
+
const authHeader = c.req.header('Authorization');
|
|
235
|
+
if (authHeader) {
|
|
236
|
+
try {
|
|
237
|
+
const user = await c.var.auth?.requireUser();
|
|
238
|
+
return c.json({ personalized: true, userId: user?.id });
|
|
239
|
+
} catch {
|
|
240
|
+
// Auth failed, treat as public
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return c.json({ personalized: false });
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Security Rules
|
|
248
|
+
|
|
249
|
+
- **Never log secrets**: `console.log('Auth present:', !!authHeader)` not the actual token
|
|
250
|
+
- **Validate on every request**: Don't cache validation results
|
|
251
|
+
- **Use provider SDKs**: Never manually verify JWTs
|
|
252
|
+
|
|
253
|
+
## Checklist
|
|
254
|
+
|
|
255
|
+
1. Research provider's auth flow (JWT, session, OAuth)
|
|
256
|
+
2. Implement client component (token fetching)
|
|
257
|
+
3. Implement server middleware (token validation)
|
|
258
|
+
4. Add Hono type augmentation
|
|
259
|
+
5. Write tests
|
|
260
|
+
6. Update package.json exports
|
|
261
|
+
7. Create template if commonly used
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentuity/auth",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.103",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Authentication helpers for popular identity providers",
|
|
6
6
|
"repository": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"@auth0/auth0-react": "^2.11.0",
|
|
29
29
|
"jwks-rsa": "^3.2.0",
|
|
30
30
|
"jsonwebtoken": "^9.0.3",
|
|
31
|
-
"@agentuity/react": "0.0.
|
|
32
|
-
"@agentuity/runtime": "0.0.
|
|
31
|
+
"@agentuity/react": "0.0.103",
|
|
32
|
+
"@agentuity/runtime": "0.0.103",
|
|
33
33
|
"hono": "^4.0.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@agentuity/react": "0.0.
|
|
54
|
-
"@agentuity/runtime": "0.0.
|
|
55
|
-
"@agentuity/test-utils": "0.0.
|
|
53
|
+
"@agentuity/react": "0.0.103",
|
|
54
|
+
"@agentuity/runtime": "0.0.103",
|
|
55
|
+
"@agentuity/test-utils": "0.0.103",
|
|
56
56
|
"@auth0/auth0-react": "^2.2.4",
|
|
57
57
|
"@clerk/backend": "^2.27.1",
|
|
58
58
|
"@clerk/clerk-react": "^5.46.1",
|