@agentuity/auth 0.0.100 → 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.
@@ -0,0 +1,260 @@
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
+ - **Public keys**: `AGENTUITY_PUBLIC_<PROVIDER>_<KEY>`
212
+ - **Secret keys**: `<PROVIDER>_SECRET_KEY`
213
+ - **Fallback**: Standard provider env var names
214
+
215
+ ## Common Patterns
216
+
217
+ ### Conditional Rendering
218
+
219
+ ```tsx
220
+ function ProtectedComponent() {
221
+ const { isAuthenticated, authLoading } = useAuth();
222
+
223
+ if (authLoading) return <div>Loading...</div>;
224
+ if (!isAuthenticated) return <SignInButton />;
225
+ return <div>Protected content</div>;
226
+ }
227
+ ```
228
+
229
+ ### Optional Auth Routes
230
+
231
+ ```typescript
232
+ router.get('/public-or-personalized', async (c) => {
233
+ const authHeader = c.req.header('Authorization');
234
+ if (authHeader) {
235
+ try {
236
+ const user = await c.var.auth?.requireUser();
237
+ return c.json({ personalized: true, userId: user?.id });
238
+ } catch {
239
+ // Auth failed, treat as public
240
+ }
241
+ }
242
+ return c.json({ personalized: false });
243
+ });
244
+ ```
245
+
246
+ ## Security Rules
247
+
248
+ - **Never log secrets**: `console.log('Auth present:', !!authHeader)` not the actual token
249
+ - **Validate on every request**: Don't cache validation results
250
+ - **Use provider SDKs**: Never manually verify JWTs
251
+
252
+ ## Checklist
253
+
254
+ 1. Research provider's auth flow (JWT, session, OAuth)
255
+ 2. Implement client component (token fetching)
256
+ 3. Implement server middleware (token validation)
257
+ 4. Add Hono type augmentation
258
+ 5. Write tests
259
+ 6. Update package.json exports
260
+ 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.100",
3
+ "version": "0.0.102",
4
4
  "type": "module",
5
5
  "description": "Authentication helpers for popular identity providers",
6
6
  "repository": {
@@ -11,7 +11,10 @@
11
11
  "license": "Apache-2.0",
12
12
  "exports": {
13
13
  ".": "./src/index.ts",
14
- "./clerk": "./src/clerk/index.ts"
14
+ "./clerk": "./src/clerk/index.ts",
15
+ "./auth0": "./src/auth0/index.ts",
16
+ "./auth0/client": "./src/auth0/client.tsx",
17
+ "./auth0/server": "./src/auth0/server.ts"
15
18
  },
16
19
  "scripts": {
17
20
  "build": "tsc --build",
@@ -22,8 +25,11 @@
22
25
  "react": "^18.0.0 || ^19.0.0",
23
26
  "@clerk/clerk-react": "^5.0.0",
24
27
  "@clerk/backend": "^1.0.0",
25
- "@agentuity/react": "0.0.100",
26
- "@agentuity/runtime": "0.0.100",
28
+ "@auth0/auth0-react": "^2.11.0",
29
+ "jwks-rsa": "^3.2.0",
30
+ "jsonwebtoken": "^9.0.3",
31
+ "@agentuity/react": "0.0.102",
32
+ "@agentuity/runtime": "0.0.102",
27
33
  "hono": "^4.0.0"
28
34
  },
29
35
  "peerDependenciesMeta": {
@@ -32,16 +38,29 @@
32
38
  },
33
39
  "@clerk/backend": {
34
40
  "optional": true
41
+ },
42
+ "@auth0/auth0-react": {
43
+ "optional": true
44
+ },
45
+ "jwks-rsa": {
46
+ "optional": true
47
+ },
48
+ "jsonwebtoken": {
49
+ "optional": true
35
50
  }
36
51
  },
37
52
  "devDependencies": {
38
- "@agentuity/react": "0.0.100",
39
- "@agentuity/runtime": "0.0.100",
40
- "@agentuity/test-utils": "0.0.100",
41
- "@clerk/clerk-react": "^5.46.1",
53
+ "@agentuity/react": "0.0.102",
54
+ "@agentuity/runtime": "0.0.102",
55
+ "@agentuity/test-utils": "0.0.102",
56
+ "@auth0/auth0-react": "^2.2.4",
42
57
  "@clerk/backend": "^2.27.1",
58
+ "@clerk/clerk-react": "^5.46.1",
59
+ "@types/jsonwebtoken": "^9.0.10",
43
60
  "@types/react": "^18.3.18",
44
61
  "hono": "^4.6.14",
62
+ "jsonwebtoken": "^9.0.3",
63
+ "jwks-rsa": "^3.2.0",
45
64
  "react": "^18.3.1",
46
65
  "typescript": "^5.8.3"
47
66
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Auth0 client-side authentication provider for React.
3
+ *
4
+ * @module auth0/client
5
+ */
6
+
7
+ import React, { useEffect, useRef } from 'react';
8
+ import type { useAuth0 as Auth0UseAuth } from '@auth0/auth0-react';
9
+ import { useAuth } from '@agentuity/react';
10
+
11
+ type UseAuth0 = typeof Auth0UseAuth;
12
+
13
+ export interface AgentuityAuth0Props {
14
+ /** React children to render */
15
+ children: React.ReactNode;
16
+
17
+ /** Auth0's useAuth0 hook from @auth0/auth0-react */
18
+ useAuth0: UseAuth0;
19
+
20
+ /** Token refresh interval in milliseconds (default: 60000 = 1 minute) */
21
+ refreshInterval?: number;
22
+
23
+ /** Options to pass to getAccessTokenSilently */
24
+ tokenOptions?: Parameters<ReturnType<UseAuth0>['getAccessTokenSilently']>[0];
25
+ }
26
+
27
+ /**
28
+ * Agentuity authentication provider for Auth0.
29
+ *
30
+ * This component integrates Auth0 authentication with Agentuity's context,
31
+ * automatically injecting auth tokens into API calls via useAPI and useWebsocket.
32
+ *
33
+ * Must be a child of both Auth0Provider and AgentuityProvider.
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * import { Auth0Provider, useAuth0 } from '@auth0/auth0-react';
38
+ * import { AgentuityProvider } from '@agentuity/react';
39
+ * import { AgentuityAuth0 } from '@agentuity/auth/auth0';
40
+ *
41
+ * <Auth0Provider domain={domain} clientId={clientId} authorizationParams={{ redirect_uri: window.location.origin }}>
42
+ * <AgentuityProvider>
43
+ * <AgentuityAuth0 useAuth0={useAuth0}>
44
+ * <App />
45
+ * </AgentuityAuth0>
46
+ * </AgentuityProvider>
47
+ * </Auth0Provider>
48
+ * ```
49
+ */
50
+ export function AgentuityAuth0({
51
+ children,
52
+ useAuth0,
53
+ refreshInterval = 60000,
54
+ tokenOptions,
55
+ }: AgentuityAuth0Props) {
56
+ const { getAccessTokenSilently, isLoading, isAuthenticated } = useAuth0();
57
+ const { setAuthHeader, setAuthLoading } = useAuth();
58
+
59
+ // Use ref for tokenOptions to avoid infinite re-renders when parent passes inline object
60
+ const tokenOptionsRef = useRef(tokenOptions);
61
+ tokenOptionsRef.current = tokenOptions;
62
+
63
+ useEffect(() => {
64
+ if (isLoading || !setAuthHeader || !setAuthLoading) {
65
+ if (setAuthLoading) {
66
+ setAuthLoading(true);
67
+ }
68
+ return;
69
+ }
70
+
71
+ // Not authenticated - clear auth header
72
+ if (!isAuthenticated) {
73
+ setAuthHeader(null);
74
+ setAuthLoading(false);
75
+ return;
76
+ }
77
+
78
+ const fetchToken = async () => {
79
+ try {
80
+ setAuthLoading(true);
81
+ const token = await getAccessTokenSilently(tokenOptionsRef.current);
82
+ setAuthHeader(token ? `Bearer ${token}` : null);
83
+ } catch (error) {
84
+ console.error(
85
+ 'Failed to get Auth0 token:',
86
+ error instanceof Error ? error.message : 'Unknown error'
87
+ );
88
+ setAuthHeader(null);
89
+ } finally {
90
+ setAuthLoading(false);
91
+ }
92
+ };
93
+
94
+ fetchToken();
95
+
96
+ // Refresh token periodically
97
+ const interval = setInterval(fetchToken, refreshInterval);
98
+ return () => clearInterval(interval);
99
+ }, [
100
+ getAccessTokenSilently,
101
+ isLoading,
102
+ isAuthenticated,
103
+ setAuthHeader,
104
+ setAuthLoading,
105
+ refreshInterval,
106
+ ]);
107
+
108
+ return <>{children}</>;
109
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Auth0 authentication provider for Agentuity.
3
+ *
4
+ * Provides client-side (React) and server-side (Hono) authentication.
5
+ *
6
+ * @example Client-side
7
+ * ```tsx
8
+ * import { Auth0Provider, useAuth0 } from '@auth0/auth0-react';
9
+ * import { AgentuityProvider } from '@agentuity/react';
10
+ * import { AgentuityAuth0 } from '@agentuity/auth/auth0';
11
+ *
12
+ * <Auth0Provider domain={domain} clientId={clientId} authorizationParams={{ redirect_uri: window.location.origin }}>
13
+ * <AgentuityProvider>
14
+ * <AgentuityAuth0 useAuth0={useAuth0}>
15
+ * <App />
16
+ * </AgentuityAuth0>
17
+ * </AgentuityProvider>
18
+ * </Auth0Provider>
19
+ * ```
20
+ *
21
+ * @example Server-side
22
+ * ```typescript
23
+ * import { createMiddleware } from '@agentuity/auth/auth0/server';
24
+ *
25
+ * router.get('/api/profile', createMiddleware(), async (c) => {
26
+ * const user = await c.var.auth.getUser();
27
+ * return c.json({ email: user.email });
28
+ * });
29
+ * ```
30
+ *
31
+ * @module auth0
32
+ */
33
+
34
+ // Client-side exports (safe for browser)
35
+ export { AgentuityAuth0 } from './client';
36
+ export type { AgentuityAuth0Props } from './client';
37
+
38
+ // Server-side exports are NOT exported from the main index to prevent bundling server deps in frontend
39
+ // Import server code directly from '@agentuity/auth/auth0/server' instead
40
+ // This ensures jsonwebtoken/jwks-rsa are never bundled into browser code