@leanmcp/auth 0.1.1 → 0.3.0
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/README.md +606 -18
- package/dist/auth0-54GZT2EI.mjs +102 -0
- package/dist/{chunk-NALGJYQB.mjs → chunk-EVD2TRPR.mjs} +63 -15
- package/dist/clerk-FR7ITM33.mjs +115 -0
- package/dist/{cognito-GBSAAMZI.mjs → cognito-I6V5YNYM.mjs} +1 -1
- package/dist/index.d.mts +55 -6
- package/dist/index.d.ts +55 -6
- package/dist/index.js +289 -15
- package/dist/index.mjs +3 -1
- package/package.json +1 -1
- package/dist/chunk-YC7GFXAO.mjs +0 -193
- package/dist/cognito-VCVS77OX.mjs +0 -145
package/README.md
CHANGED
|
@@ -5,11 +5,13 @@ Authentication module for LeanMCP providing token-based authentication decorator
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **@Authenticated decorator** - Protect MCP tools, prompts, and resources with token authentication
|
|
8
|
-
- **
|
|
8
|
+
- **Automatic authUser injection** - Access decoded user info via global `authUser` variable in protected methods
|
|
9
|
+
- **Multi-provider support** - AWS Cognito, Clerk, Auth0
|
|
9
10
|
- **Method or class-level protection** - Apply to individual methods or entire services
|
|
10
11
|
- **Automatic token validation** - Validates tokens before method execution
|
|
11
12
|
- **Custom error handling** - Detailed error codes for different auth failures
|
|
12
|
-
- **Type-safe** - Full TypeScript support with type inference
|
|
13
|
+
- **Type-safe** - Full TypeScript support with type inference and global type declarations
|
|
14
|
+
- **OAuth & Session modes** - Support for both session-based and OAuth refresh token flows
|
|
13
15
|
|
|
14
16
|
## Installation
|
|
15
17
|
|
|
@@ -24,6 +26,16 @@ For AWS Cognito:
|
|
|
24
26
|
npm install @aws-sdk/client-cognito-identity-provider axios jsonwebtoken jwk-to-pem
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
For Clerk:
|
|
30
|
+
```bash
|
|
31
|
+
npm install axios jsonwebtoken jwk-to-pem
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For Auth0:
|
|
35
|
+
```bash
|
|
36
|
+
npm install axios jsonwebtoken jwk-to-pem
|
|
37
|
+
```
|
|
38
|
+
|
|
27
39
|
## Quick Start
|
|
28
40
|
|
|
29
41
|
### 1. Initialize Auth Provider
|
|
@@ -48,13 +60,20 @@ import { Tool } from "@leanmcp/core";
|
|
|
48
60
|
import { Authenticated } from "@leanmcp/auth";
|
|
49
61
|
|
|
50
62
|
export class SentimentService {
|
|
51
|
-
// This method requires authentication
|
|
63
|
+
// This method requires authentication with automatic user info
|
|
52
64
|
@Tool({ description: 'Analyze sentiment (requires auth)' })
|
|
53
|
-
@Authenticated(authProvider)
|
|
65
|
+
@Authenticated(authProvider) // getUser: true by default
|
|
54
66
|
async analyzeSentiment(input: { text: string }) {
|
|
55
67
|
// Token is automatically validated from _meta.authorization.token
|
|
56
|
-
//
|
|
57
|
-
|
|
68
|
+
// authUser is automatically available with decoded JWT payload
|
|
69
|
+
console.log('User ID:', authUser.sub);
|
|
70
|
+
console.log('Email:', authUser.email);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
sentiment: 'positive',
|
|
74
|
+
score: 0.8,
|
|
75
|
+
analyzedBy: authUser.sub
|
|
76
|
+
};
|
|
58
77
|
}
|
|
59
78
|
|
|
60
79
|
// This method is public
|
|
@@ -75,16 +94,134 @@ import { Authenticated } from "@leanmcp/auth";
|
|
|
75
94
|
export class SecureService {
|
|
76
95
|
@Tool({ description: 'Protected tool 1' })
|
|
77
96
|
async tool1(input: { data: string }) {
|
|
78
|
-
//
|
|
97
|
+
// authUser is automatically available in all methods
|
|
98
|
+
console.log('Authenticated user:', authUser.email);
|
|
99
|
+
return { data: input.data, userId: authUser.sub };
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
@Tool({ description: 'Protected tool 2' })
|
|
82
103
|
async tool2(input: { data: string }) {
|
|
83
|
-
//
|
|
104
|
+
// authUser is available here too
|
|
105
|
+
return { data: input.data, userId: authUser.sub };
|
|
84
106
|
}
|
|
85
107
|
}
|
|
86
108
|
```
|
|
87
109
|
|
|
110
|
+
## authUser Variable
|
|
111
|
+
|
|
112
|
+
### Automatic User Information Injection
|
|
113
|
+
|
|
114
|
+
When you use the `@Authenticated` decorator, a global `authUser` variable is automatically injected into your protected methods containing the decoded JWT token payload.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
@Tool({ description: 'Create post' })
|
|
118
|
+
@Authenticated(authProvider)
|
|
119
|
+
async createPost(input: { title: string, content: string }) {
|
|
120
|
+
// authUser is automatically available - no need to pass it as a parameter
|
|
121
|
+
console.log('User ID:', authUser.sub);
|
|
122
|
+
console.log('Email:', authUser.email);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
id: generateId(),
|
|
126
|
+
title: input.title,
|
|
127
|
+
content: input.content,
|
|
128
|
+
authorId: authUser.sub,
|
|
129
|
+
authorEmail: authUser.email
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Controlling User Data Fetching
|
|
135
|
+
|
|
136
|
+
You can control whether user information is fetched using the `getUser` option:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Fetch user info (default behavior)
|
|
140
|
+
@Authenticated(authProvider, { getUser: true })
|
|
141
|
+
async methodWithUserInfo(input: any) {
|
|
142
|
+
// authUser is available
|
|
143
|
+
console.log(authUser);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Only verify token, don't fetch user info
|
|
147
|
+
@Authenticated(authProvider, { getUser: false })
|
|
148
|
+
async methodWithoutUserInfo(input: any) {
|
|
149
|
+
// authUser is undefined
|
|
150
|
+
// Faster execution, use when you only need token validation
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Provider-Specific User Data
|
|
155
|
+
|
|
156
|
+
The structure of `authUser` depends on your authentication provider:
|
|
157
|
+
|
|
158
|
+
**AWS Cognito:**
|
|
159
|
+
```typescript
|
|
160
|
+
{
|
|
161
|
+
sub: 'user-uuid',
|
|
162
|
+
email: 'user@example.com',
|
|
163
|
+
email_verified: true,
|
|
164
|
+
'cognito:username': 'username',
|
|
165
|
+
'cognito:groups': ['admin', 'users'],
|
|
166
|
+
// ... other Cognito claims
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Clerk:**
|
|
171
|
+
```typescript
|
|
172
|
+
{
|
|
173
|
+
sub: 'user_2abc123xyz',
|
|
174
|
+
userId: 'user_2abc123xyz',
|
|
175
|
+
email: 'user@example.com',
|
|
176
|
+
firstName: 'John',
|
|
177
|
+
lastName: 'Doe',
|
|
178
|
+
imageUrl: 'https://img.clerk.com/...',
|
|
179
|
+
// ... other Clerk claims
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Auth0:**
|
|
184
|
+
```typescript
|
|
185
|
+
{
|
|
186
|
+
sub: 'auth0|507f1f77bcf86cd799439011',
|
|
187
|
+
email: 'user@example.com',
|
|
188
|
+
email_verified: true,
|
|
189
|
+
name: 'John Doe',
|
|
190
|
+
picture: 'https://s.gravatar.com/avatar/...',
|
|
191
|
+
// ... other Auth0 claims
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### TypeScript Support
|
|
196
|
+
|
|
197
|
+
The `authUser` variable is globally declared and available without TypeScript errors:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// No need to import or declare authUser
|
|
201
|
+
@Authenticated(authProvider)
|
|
202
|
+
async myMethod(input: any) {
|
|
203
|
+
// TypeScript knows about authUser
|
|
204
|
+
const userId: string = authUser.sub;
|
|
205
|
+
const email: string = authUser.email;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
For better type safety, you can create a typed interface:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
interface MyAuthUser {
|
|
213
|
+
sub: string;
|
|
214
|
+
email: string;
|
|
215
|
+
name?: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Authenticated(authProvider)
|
|
219
|
+
async myMethod(input: any) {
|
|
220
|
+
const user = authUser as MyAuthUser;
|
|
221
|
+
console.log(user.email); // Fully typed
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
88
225
|
## Usage
|
|
89
226
|
|
|
90
227
|
### Client Side - Calling Protected Methods
|
|
@@ -169,6 +306,17 @@ try {
|
|
|
169
306
|
|
|
170
307
|
## Supported Auth Providers
|
|
171
308
|
|
|
309
|
+
### Provider Comparison
|
|
310
|
+
|
|
311
|
+
| Feature | AWS Cognito | Clerk | Auth0 |
|
|
312
|
+
|---------|-------------|-------|-------|
|
|
313
|
+
| **JWT Verification** | ✅ JWKS | ✅ JWKS | ✅ JWKS |
|
|
314
|
+
| **Refresh Tokens** | ✅ Yes | ✅ Yes (OAuth mode) | ✅ Yes |
|
|
315
|
+
| **Session Mode** | ❌ No | ✅ Yes (default) | ❌ No |
|
|
316
|
+
| **OAuth Mode** | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
317
|
+
| **User Data** | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
318
|
+
| **Setup Complexity** | Low | Low | Low |
|
|
319
|
+
|
|
172
320
|
### AWS Cognito
|
|
173
321
|
|
|
174
322
|
```typescript
|
|
@@ -185,10 +333,123 @@ await authProvider.init();
|
|
|
185
333
|
- Token must be valid and not expired
|
|
186
334
|
- Token must be issued by the configured User Pool
|
|
187
335
|
|
|
336
|
+
**Environment Variables:**
|
|
337
|
+
```bash
|
|
338
|
+
AWS_REGION=us-east-1
|
|
339
|
+
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
|
|
340
|
+
COGNITO_CLIENT_ID=your-client-id
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Clerk
|
|
344
|
+
|
|
345
|
+
Clerk supports both **Session Mode** (default) and **OAuth Mode** (with refresh tokens).
|
|
346
|
+
|
|
347
|
+
#### Session Mode (Default)
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
const authProvider = new AuthProvider('clerk', {
|
|
351
|
+
frontendApi: 'your-frontend-api.clerk.accounts.dev',
|
|
352
|
+
secretKey: 'sk_test_...'
|
|
353
|
+
});
|
|
354
|
+
await authProvider.init();
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Configuration:**
|
|
358
|
+
- `frontendApi` - Your Clerk Frontend API domain
|
|
359
|
+
- `secretKey` - Your Clerk Secret Key
|
|
360
|
+
|
|
361
|
+
**Environment Variables:**
|
|
362
|
+
```bash
|
|
363
|
+
CLERK_FRONTEND_API=your-frontend-api.clerk.accounts.dev
|
|
364
|
+
CLERK_SECRET_KEY=sk_test_...
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### OAuth Mode (Refresh Tokens)
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const authProvider = new AuthProvider('clerk', {
|
|
371
|
+
frontendApi: 'your-frontend-api.clerk.accounts.dev',
|
|
372
|
+
secretKey: 'sk_test_...',
|
|
373
|
+
clientId: 'your-oauth-client-id',
|
|
374
|
+
clientSecret: 'your-oauth-client-secret',
|
|
375
|
+
redirectUri: 'https://yourapp.com/callback'
|
|
376
|
+
});
|
|
377
|
+
await authProvider.init();
|
|
378
|
+
|
|
379
|
+
// Refresh tokens when needed
|
|
380
|
+
const newTokens = await authProvider.refreshToken(refreshToken);
|
|
381
|
+
// Returns: { access_token, id_token, refresh_token }
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**OAuth Configuration:**
|
|
385
|
+
- `clientId` - OAuth Client ID from Clerk
|
|
386
|
+
- `clientSecret` - OAuth Client Secret from Clerk
|
|
387
|
+
- `redirectUri` - OAuth redirect URI
|
|
388
|
+
|
|
389
|
+
**Token Requirements:**
|
|
390
|
+
- JWT token from Clerk (ID token or session token)
|
|
391
|
+
- Token must be valid and not expired
|
|
392
|
+
- Token must be issued by your Clerk instance
|
|
393
|
+
|
|
394
|
+
**User Data:**
|
|
395
|
+
```typescript
|
|
396
|
+
const user = await authProvider.getUser(idToken);
|
|
397
|
+
// Returns: { sub, email, email_verified, first_name, last_name, attributes }
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Auth0
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
const authProvider = new AuthProvider('auth0', {
|
|
404
|
+
domain: 'your-tenant.auth0.com',
|
|
405
|
+
clientId: 'your-client-id',
|
|
406
|
+
clientSecret: 'your-client-secret', // Optional for public clients
|
|
407
|
+
audience: 'https://your-api-identifier',
|
|
408
|
+
scopes: 'openid profile email offline_access' // Optional, defaults shown
|
|
409
|
+
});
|
|
410
|
+
await authProvider.init();
|
|
411
|
+
|
|
412
|
+
// Refresh tokens when needed
|
|
413
|
+
const newTokens = await authProvider.refreshToken(refreshToken);
|
|
414
|
+
// Returns: { access_token, id_token, refresh_token, expires_in }
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Configuration:**
|
|
418
|
+
- `domain` - Your Auth0 tenant domain (e.g., `your-tenant.auth0.com`)
|
|
419
|
+
- `clientId` - Your Auth0 Application Client ID
|
|
420
|
+
- `clientSecret` - Your Auth0 Application Client Secret (optional for public clients)
|
|
421
|
+
- `audience` - Your API identifier (required for API access)
|
|
422
|
+
- `scopes` - OAuth scopes (default: `openid profile email offline_access`)
|
|
423
|
+
|
|
424
|
+
**Environment Variables:**
|
|
425
|
+
```bash
|
|
426
|
+
AUTH0_DOMAIN=your-tenant.auth0.com
|
|
427
|
+
AUTH0_CLIENT_ID=your-client-id
|
|
428
|
+
AUTH0_CLIENT_SECRET=your-client-secret
|
|
429
|
+
AUTH0_AUDIENCE=https://your-api-identifier
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Token Requirements:**
|
|
433
|
+
- JWT token from Auth0 (access token or ID token)
|
|
434
|
+
- Token must be valid and not expired
|
|
435
|
+
- Token must be issued by your Auth0 tenant
|
|
436
|
+
- Token must have the correct audience
|
|
437
|
+
|
|
438
|
+
**User Data:**
|
|
439
|
+
```typescript
|
|
440
|
+
const user = await authProvider.getUser(idToken);
|
|
441
|
+
// Returns: { sub, email, email_verified, name, attributes }
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Error Handling:**
|
|
445
|
+
Auth0 provider includes detailed error messages:
|
|
446
|
+
- `Token has expired` - Token is expired
|
|
447
|
+
- `Invalid token signature` - Token signature verification failed
|
|
448
|
+
- `Malformed token` - Token format is invalid
|
|
449
|
+
- `Invalid token issuer` - Token issuer doesn't match
|
|
450
|
+
|
|
188
451
|
### More Providers Coming Soon
|
|
189
452
|
|
|
190
|
-
- Clerk
|
|
191
|
-
- Auth0
|
|
192
453
|
- Firebase Auth
|
|
193
454
|
- Custom JWT providers
|
|
194
455
|
|
|
@@ -210,13 +471,24 @@ class AuthProvider {
|
|
|
210
471
|
### @Authenticated Decorator
|
|
211
472
|
|
|
212
473
|
```typescript
|
|
213
|
-
function Authenticated(
|
|
474
|
+
function Authenticated(
|
|
475
|
+
authProvider: AuthProvider,
|
|
476
|
+
options?: AuthenticatedOptions
|
|
477
|
+
): ClassDecorator | MethodDecorator;
|
|
478
|
+
|
|
479
|
+
interface AuthenticatedOptions {
|
|
480
|
+
getUser?: boolean; // Default: true
|
|
481
|
+
}
|
|
214
482
|
```
|
|
215
483
|
|
|
216
484
|
Can be applied to:
|
|
217
485
|
- **Classes** - Protects all methods in the class
|
|
218
486
|
- **Methods** - Protects individual methods
|
|
219
487
|
|
|
488
|
+
**Options:**
|
|
489
|
+
- `getUser: true` (default) - Fetches user info and injects `authUser` variable
|
|
490
|
+
- `getUser: false` - Only validates token, skips user info fetch (faster)
|
|
491
|
+
|
|
220
492
|
### AuthenticationError
|
|
221
493
|
|
|
222
494
|
```typescript
|
|
@@ -238,16 +510,32 @@ function getAuthProvider(target: any, propertyKey?: string): AuthProvider | unde
|
|
|
238
510
|
|
|
239
511
|
## Environment Variables
|
|
240
512
|
|
|
241
|
-
|
|
513
|
+
### AWS Cognito
|
|
242
514
|
```bash
|
|
243
515
|
AWS_REGION=us-east-1
|
|
244
516
|
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
|
|
245
517
|
COGNITO_CLIENT_ID=your-client-id
|
|
246
518
|
```
|
|
247
519
|
|
|
248
|
-
|
|
520
|
+
### Clerk (Session Mode)
|
|
521
|
+
```bash
|
|
522
|
+
CLERK_FRONTEND_API=your-frontend-api.clerk.accounts.dev
|
|
523
|
+
CLERK_SECRET_KEY=sk_test_...
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Auth0
|
|
527
|
+
```bash
|
|
528
|
+
AUTH0_DOMAIN=your-tenant.auth0.com
|
|
529
|
+
AUTH0_CLIENT_ID=your-client-id
|
|
530
|
+
AUTH0_CLIENT_SECRET=your-client-secret
|
|
531
|
+
AUTH0_AUDIENCE=https://your-api-identifier
|
|
532
|
+
```
|
|
249
533
|
|
|
250
|
-
|
|
534
|
+
## Complete Examples
|
|
535
|
+
|
|
536
|
+
### AWS Cognito Example
|
|
537
|
+
|
|
538
|
+
See [examples/slack-with-auth](../../examples/slack-with-auth) for a complete working example.
|
|
251
539
|
|
|
252
540
|
```typescript
|
|
253
541
|
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
@@ -264,9 +552,17 @@ await authProvider.init();
|
|
|
264
552
|
// Create service with protected methods
|
|
265
553
|
@Authenticated(authProvider)
|
|
266
554
|
class MyService {
|
|
267
|
-
@Tool()
|
|
555
|
+
@Tool({ description: 'Process protected data' })
|
|
268
556
|
async protectedTool(input: { data: string }) {
|
|
269
|
-
|
|
557
|
+
// authUser is automatically available
|
|
558
|
+
console.log('User:', authUser.sub);
|
|
559
|
+
console.log('Email:', authUser.email);
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
result: "Protected data",
|
|
563
|
+
processedBy: authUser.sub,
|
|
564
|
+
userEmail: authUser.email
|
|
565
|
+
};
|
|
270
566
|
}
|
|
271
567
|
}
|
|
272
568
|
|
|
@@ -280,20 +576,184 @@ const serverFactory = () => {
|
|
|
280
576
|
await createHTTPServer(serverFactory, { port: 3000 });
|
|
281
577
|
```
|
|
282
578
|
|
|
579
|
+
### Clerk Example
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
583
|
+
import { AuthProvider, Authenticated } from "@leanmcp/auth";
|
|
584
|
+
|
|
585
|
+
// Initialize Clerk in Session Mode
|
|
586
|
+
const authProvider = new AuthProvider('clerk', {
|
|
587
|
+
frontendApi: process.env.CLERK_FRONTEND_API,
|
|
588
|
+
secretKey: process.env.CLERK_SECRET_KEY
|
|
589
|
+
});
|
|
590
|
+
await authProvider.init();
|
|
591
|
+
|
|
592
|
+
// Or initialize in OAuth Mode (with refresh tokens)
|
|
593
|
+
const authProviderOAuth = new AuthProvider('clerk', {
|
|
594
|
+
frontendApi: process.env.CLERK_FRONTEND_API,
|
|
595
|
+
secretKey: process.env.CLERK_SECRET_KEY,
|
|
596
|
+
clientId: process.env.CLERK_CLIENT_ID,
|
|
597
|
+
clientSecret: process.env.CLERK_CLIENT_SECRET,
|
|
598
|
+
redirectUri: process.env.CLERK_REDIRECT_URI
|
|
599
|
+
});
|
|
600
|
+
await authProviderOAuth.init();
|
|
601
|
+
|
|
602
|
+
@Authenticated(authProvider)
|
|
603
|
+
class UserService {
|
|
604
|
+
@Tool({ description: 'Get user profile' })
|
|
605
|
+
async getProfile() {
|
|
606
|
+
// authUser is automatically available with Clerk user data
|
|
607
|
+
return {
|
|
608
|
+
userId: authUser.userId || authUser.sub,
|
|
609
|
+
email: authUser.email,
|
|
610
|
+
firstName: authUser.firstName,
|
|
611
|
+
lastName: authUser.lastName,
|
|
612
|
+
imageUrl: authUser.imageUrl
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
@Tool({ description: 'Create user post' })
|
|
617
|
+
async createPost(input: { title: string, content: string }) {
|
|
618
|
+
// Access authUser in any protected method
|
|
619
|
+
return {
|
|
620
|
+
id: generateId(),
|
|
621
|
+
title: input.title,
|
|
622
|
+
content: input.content,
|
|
623
|
+
authorId: authUser.userId,
|
|
624
|
+
authorEmail: authUser.email
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const serverFactory = () => {
|
|
630
|
+
const server = new MCPServer({ name: "clerk-server", version: "1.0.0" });
|
|
631
|
+
server.registerService(new UserService());
|
|
632
|
+
return server.getServer();
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
await createHTTPServer(serverFactory, { port: 3000 });
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Auth0 Example
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
642
|
+
import { AuthProvider, Authenticated } from "@leanmcp/auth";
|
|
643
|
+
|
|
644
|
+
// Initialize Auth0
|
|
645
|
+
const authProvider = new AuthProvider('auth0', {
|
|
646
|
+
domain: process.env.AUTH0_DOMAIN,
|
|
647
|
+
clientId: process.env.AUTH0_CLIENT_ID,
|
|
648
|
+
clientSecret: process.env.AUTH0_CLIENT_SECRET,
|
|
649
|
+
audience: process.env.AUTH0_AUDIENCE,
|
|
650
|
+
scopes: 'openid profile email offline_access'
|
|
651
|
+
});
|
|
652
|
+
await authProvider.init();
|
|
653
|
+
|
|
654
|
+
@Authenticated(authProvider)
|
|
655
|
+
class SecureAPIService {
|
|
656
|
+
@Tool({ description: 'Get sensitive data' })
|
|
657
|
+
async getSensitiveData(input: { dataId: string }) {
|
|
658
|
+
// authUser is automatically available with Auth0 user data
|
|
659
|
+
console.log('User:', authUser.sub);
|
|
660
|
+
console.log('Email:', authUser.email);
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
dataId: input.dataId,
|
|
664
|
+
data: "Sensitive information",
|
|
665
|
+
accessedBy: authUser.sub,
|
|
666
|
+
userName: authUser.name
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
@Tool({ description: 'Update user settings' })
|
|
671
|
+
async updateSettings(input: { settings: Record<string, any> }) {
|
|
672
|
+
// Access authUser in any protected method
|
|
673
|
+
return {
|
|
674
|
+
success: true,
|
|
675
|
+
settings: input.settings,
|
|
676
|
+
userId: authUser.sub,
|
|
677
|
+
updatedBy: authUser.email
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const serverFactory = () => {
|
|
683
|
+
const server = new MCPServer({ name: "auth0-server", version: "1.0.0" });
|
|
684
|
+
server.registerService(new SecureAPIService());
|
|
685
|
+
return server.getServer();
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
await createHTTPServer(serverFactory, { port: 3000 });
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### Multi-Provider Example
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
import { AuthProvider, Authenticated } from "@leanmcp/auth";
|
|
695
|
+
|
|
696
|
+
// Initialize multiple providers
|
|
697
|
+
const clerkAuth = new AuthProvider('clerk', {
|
|
698
|
+
frontendApi: process.env.CLERK_FRONTEND_API,
|
|
699
|
+
secretKey: process.env.CLERK_SECRET_KEY
|
|
700
|
+
});
|
|
701
|
+
await clerkAuth.init();
|
|
702
|
+
|
|
703
|
+
const auth0Auth = new AuthProvider('auth0', {
|
|
704
|
+
domain: process.env.AUTH0_DOMAIN,
|
|
705
|
+
clientId: process.env.AUTH0_CLIENT_ID,
|
|
706
|
+
audience: process.env.AUTH0_AUDIENCE
|
|
707
|
+
});
|
|
708
|
+
await auth0Auth.init();
|
|
709
|
+
|
|
710
|
+
// Use different providers for different services
|
|
711
|
+
@Authenticated(clerkAuth)
|
|
712
|
+
class UserService {
|
|
713
|
+
@Tool({ description: 'Get user data' })
|
|
714
|
+
async getUserData() {
|
|
715
|
+
// authUser contains Clerk user data
|
|
716
|
+
return {
|
|
717
|
+
userId: authUser.userId,
|
|
718
|
+
email: authUser.email,
|
|
719
|
+
firstName: authUser.firstName
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
@Authenticated(auth0Auth)
|
|
725
|
+
class AdminService {
|
|
726
|
+
@Tool({ description: 'Get admin data' })
|
|
727
|
+
async getAdminData() {
|
|
728
|
+
// authUser contains Auth0 user data
|
|
729
|
+
return {
|
|
730
|
+
adminId: authUser.sub,
|
|
731
|
+
email: authUser.email,
|
|
732
|
+
name: authUser.name
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
283
738
|
## How It Works
|
|
284
739
|
|
|
285
740
|
1. **Request arrives** with `_meta.authorization.token`
|
|
286
741
|
2. **Decorator intercepts** the method call before execution
|
|
287
742
|
3. **Token is extracted** from `_meta.authorization.token`
|
|
288
743
|
4. **Token is validated** using the configured auth provider
|
|
289
|
-
5. **
|
|
290
|
-
6. **
|
|
744
|
+
5. **User info is fetched** (if `getUser: true`) and decoded from JWT
|
|
745
|
+
6. **authUser is injected** as a global variable in method scope
|
|
746
|
+
7. **Method executes** with clean business arguments and access to `authUser`
|
|
747
|
+
8. **authUser is cleaned up** after method execution
|
|
748
|
+
9. **Response returns** to client
|
|
291
749
|
|
|
292
750
|
**Key Benefits:**
|
|
293
751
|
- **Clean separation** - Authentication metadata separate from business data
|
|
294
752
|
- **MCP compliant** - Follows standard `_meta` pattern
|
|
295
753
|
- **Type-safe** - Input classes don't need token fields
|
|
754
|
+
- **Automatic user injection** - Access user data via `authUser` without manual extraction
|
|
296
755
|
- **Reusable** - Same input classes work for authenticated and public methods
|
|
756
|
+
- **Secure** - `authUser` is scoped to method execution and cleaned up after
|
|
297
757
|
|
|
298
758
|
## Best Practices
|
|
299
759
|
|
|
@@ -304,6 +764,134 @@ await createHTTPServer(serverFactory, { port: 3000 });
|
|
|
304
764
|
5. **Log authentication failures** - Monitor for suspicious activity
|
|
305
765
|
6. **Use environment variables** - Never hardcode credentials
|
|
306
766
|
7. **Use _meta for auth** - Don't include tokens in business arguments
|
|
767
|
+
8. **Choose the right mode** - Use Session mode for simpler setups, OAuth mode for refresh tokens
|
|
768
|
+
9. **Test token expiration** - Ensure your app handles expired tokens gracefully
|
|
769
|
+
10. **Monitor JWKS cache** - Providers cache JWKS keys for performance
|
|
770
|
+
11. **Use authUser for user context** - Access user data via `authUser` instead of parsing tokens manually
|
|
771
|
+
12. **Type authUser when needed** - Cast `authUser` to a typed interface for better type safety
|
|
772
|
+
13. **Use getUser: false for performance** - Skip user fetch when you only need token validation
|
|
773
|
+
|
|
774
|
+
## Quick Reference
|
|
775
|
+
|
|
776
|
+
### Initialization Patterns
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
// AWS Cognito
|
|
780
|
+
const cognito = new AuthProvider('cognito', {
|
|
781
|
+
region: 'us-east-1',
|
|
782
|
+
userPoolId: 'us-east-1_XXX',
|
|
783
|
+
clientId: 'xxx'
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// Clerk (Session Mode)
|
|
787
|
+
const clerk = new AuthProvider('clerk', {
|
|
788
|
+
frontendApi: 'xxx.clerk.accounts.dev',
|
|
789
|
+
secretKey: 'sk_test_xxx'
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
// Clerk (OAuth Mode)
|
|
793
|
+
const clerkOAuth = new AuthProvider('clerk', {
|
|
794
|
+
frontendApi: 'xxx.clerk.accounts.dev',
|
|
795
|
+
secretKey: 'sk_test_xxx',
|
|
796
|
+
clientId: 'xxx',
|
|
797
|
+
clientSecret: 'xxx',
|
|
798
|
+
redirectUri: 'https://app.com/callback'
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Auth0
|
|
802
|
+
const auth0 = new AuthProvider('auth0', {
|
|
803
|
+
domain: 'tenant.auth0.com',
|
|
804
|
+
clientId: 'xxx',
|
|
805
|
+
clientSecret: 'xxx',
|
|
806
|
+
audience: 'https://api-identifier'
|
|
807
|
+
});
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Common Operations
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
// Initialize
|
|
814
|
+
await authProvider.init();
|
|
815
|
+
|
|
816
|
+
// Verify token
|
|
817
|
+
const isValid = await authProvider.verifyToken(token);
|
|
818
|
+
|
|
819
|
+
// Refresh token (OAuth/Auth0 only)
|
|
820
|
+
const newTokens = await authProvider.refreshToken(refreshToken);
|
|
821
|
+
|
|
822
|
+
// Get user data
|
|
823
|
+
const user = await authProvider.getUser(idToken);
|
|
824
|
+
|
|
825
|
+
// Get provider type
|
|
826
|
+
const type = authProvider.getProviderType(); // 'cognito' | 'clerk' | 'auth0'
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Decorator Usage
|
|
830
|
+
|
|
831
|
+
```typescript
|
|
832
|
+
// Protect single method with authUser (default)
|
|
833
|
+
@Authenticated(authProvider)
|
|
834
|
+
async myMethod(input: { data: string }) {
|
|
835
|
+
console.log(authUser.sub); // User ID available
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Protect method without fetching user (faster)
|
|
839
|
+
@Authenticated(authProvider, { getUser: false })
|
|
840
|
+
async fastMethod(input: { data: string }) {
|
|
841
|
+
// Only token validation, no authUser
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Protect entire class
|
|
845
|
+
@Authenticated(authProvider)
|
|
846
|
+
class MyService {
|
|
847
|
+
@Tool() async method1() {
|
|
848
|
+
// authUser available in all methods
|
|
849
|
+
return { userId: authUser.sub };
|
|
850
|
+
}
|
|
851
|
+
@Tool() async method2() {
|
|
852
|
+
return { email: authUser.email };
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Check if authentication required
|
|
857
|
+
const required = isAuthenticationRequired(target, 'methodName');
|
|
858
|
+
|
|
859
|
+
// Get auth provider for method
|
|
860
|
+
const provider = getAuthProvider(target, 'methodName');
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
### authUser Quick Reference
|
|
864
|
+
|
|
865
|
+
```typescript
|
|
866
|
+
// Access user data in protected methods
|
|
867
|
+
@Authenticated(authProvider)
|
|
868
|
+
async createPost(input: { title: string }) {
|
|
869
|
+
// authUser is automatically available
|
|
870
|
+
const userId = authUser.sub; // User ID (all providers)
|
|
871
|
+
const email = authUser.email; // Email (all providers)
|
|
872
|
+
|
|
873
|
+
// Provider-specific fields
|
|
874
|
+
const clerkId = authUser.userId; // Clerk only
|
|
875
|
+
const firstName = authUser.firstName; // Clerk only
|
|
876
|
+
const cognitoGroups = authUser['cognito:groups']; // Cognito only
|
|
877
|
+
const auth0Name = authUser.name; // Auth0 only
|
|
878
|
+
|
|
879
|
+
return { authorId: userId, authorEmail: email };
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Type authUser for better type safety
|
|
883
|
+
interface MyUser {
|
|
884
|
+
sub: string;
|
|
885
|
+
email: string;
|
|
886
|
+
name?: string;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
@Authenticated(authProvider)
|
|
890
|
+
async typedMethod(input: any) {
|
|
891
|
+
const user = authUser as MyUser;
|
|
892
|
+
console.log(user.email); // Fully typed
|
|
893
|
+
}
|
|
894
|
+
```
|
|
307
895
|
|
|
308
896
|
## License
|
|
309
897
|
|