@leanmcp/auth 0.3.2 → 0.4.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 CHANGED
@@ -1,17 +1,42 @@
1
- # @leanmcp/auth
2
-
3
- Authentication module for LeanMCP providing token-based authentication decorators and multi-provider support.
1
+ <p align="center">
2
+ <img
3
+ src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.svg"
4
+ alt="LeanMCP Logo"
5
+ width="400"
6
+ />
7
+ </p>
8
+
9
+ <p align="center">
10
+ <strong>@leanmcp/auth</strong><br/>
11
+ Token-based authentication decorators and multi-provider support for MCP tools.
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@leanmcp/auth">
16
+ <img src="https://img.shields.io/npm/v/@leanmcp/auth" alt="npm version" />
17
+ </a>
18
+ <a href="https://www.npmjs.com/package/@leanmcp/auth">
19
+ <img src="https://img.shields.io/npm/dm/@leanmcp/auth" alt="npm downloads" />
20
+ </a>
21
+ <a href="https://docs.leanmcp.com/sdk/auth">
22
+ <img src="https://img.shields.io/badge/Docs-leanmcp-0A66C2?" />
23
+ </a>
24
+ <a href="https://discord.com/invite/DsRcA3GwPy">
25
+ <img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
26
+ </a>
27
+ <a href="https://x.com/LeanMcp">
28
+ <img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
29
+ </a>
30
+ </p>
4
31
 
5
32
  ## Features
6
33
 
7
- - **@Authenticated decorator** - Protect MCP tools, prompts, and resources with token authentication
8
- - **Automatic authUser injection** - Access decoded user info via global `authUser` variable in protected methods
9
- - **Multi-provider support** - AWS Cognito, Clerk, Auth0
10
- - **Method or class-level protection** - Apply to individual methods or entire services
11
- - **Automatic token validation** - Validates tokens before method execution
12
- - **Custom error handling** - Detailed error codes for different auth failures
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
34
+ - **@Authenticated Decorator** Protect tools, prompts, and resources with a simple decorator
35
+ - **Multi-Provider Support** AWS Cognito, Clerk, Auth0, and LeanMCP providers
36
+ - **Automatic authUser** Decoded user info injected as global `authUser` variable
37
+ - **Concurrency Safe** Uses AsyncLocalStorage for request-isolated context
38
+ - **Method or Class-Level** Apply to individual methods or entire services
39
+ - **OAuth & Session Modes** Support for both session-based and OAuth refresh token flows
15
40
 
16
41
  ## Installation
17
42
 
@@ -21,17 +46,17 @@ npm install @leanmcp/auth @leanmcp/core
21
46
 
22
47
  ### Provider Dependencies
23
48
 
24
- For AWS Cognito:
49
+ **AWS Cognito:**
25
50
  ```bash
26
51
  npm install @aws-sdk/client-cognito-identity-provider axios jsonwebtoken jwk-to-pem
27
52
  ```
28
53
 
29
- For Clerk:
54
+ **Clerk:**
30
55
  ```bash
31
56
  npm install axios jsonwebtoken jwk-to-pem
32
57
  ```
33
58
 
34
- For Auth0:
59
+ **Auth0:**
35
60
  ```bash
36
61
  npm install axios jsonwebtoken jwk-to-pem
37
62
  ```
@@ -43,7 +68,6 @@ npm install axios jsonwebtoken jwk-to-pem
43
68
  ```typescript
44
69
  import { AuthProvider } from "@leanmcp/auth";
45
70
 
46
- // Initialize with AWS Cognito
47
71
  const authProvider = new AuthProvider('cognito', {
48
72
  region: 'us-east-1',
49
73
  userPoolId: 'us-east-1_XXXXXXXXX',
@@ -53,22 +77,20 @@ const authProvider = new AuthProvider('cognito', {
53
77
  await authProvider.init();
54
78
  ```
55
79
 
56
- ### 2. Protect Individual Methods
80
+ ### 2. Protect Methods with @Authenticated
57
81
 
58
82
  ```typescript
59
83
  import { Tool } from "@leanmcp/core";
60
84
  import { Authenticated } from "@leanmcp/auth";
61
85
 
62
86
  export class SentimentService {
63
- // This method requires authentication with automatic user info
64
87
  @Tool({ description: 'Analyze sentiment (requires auth)' })
65
- @Authenticated(authProvider) // getUser: true by default
88
+ @Authenticated(authProvider)
66
89
  async analyzeSentiment(input: { text: string }) {
67
- // Token is automatically validated from _meta.authorization.token
68
- // authUser is automatically available with decoded JWT payload
90
+ // authUser is automatically available with user info
69
91
  console.log('User ID:', authUser.sub);
70
92
  console.log('Email:', authUser.email);
71
-
93
+
72
94
  return {
73
95
  sentiment: 'positive',
74
96
  score: 0.8,
@@ -76,8 +98,8 @@ export class SentimentService {
76
98
  };
77
99
  }
78
100
 
79
- // This method is public
80
- @Tool({ description: 'Get sentiment categories (public)' })
101
+ // Public method - no authentication
102
+ @Tool({ description: 'Get categories (public)' })
81
103
  async getCategories() {
82
104
  return { categories: ['positive', 'negative', 'neutral'] };
83
105
  }
@@ -87,40 +109,27 @@ export class SentimentService {
87
109
  ### 3. Protect Entire Service
88
110
 
89
111
  ```typescript
90
- import { Authenticated } from "@leanmcp/auth";
91
-
92
112
  // All methods in this class require authentication
93
113
  @Authenticated(authProvider)
94
114
  export class SecureService {
95
- @Tool({ description: 'Protected tool 1' })
96
- async tool1(input: { data: string }) {
97
- // authUser is automatically available in all methods
98
- console.log('Authenticated user:', authUser.email);
99
- return { data: input.data, userId: authUser.sub };
100
- }
101
-
102
- @Tool({ description: 'Protected tool 2' })
103
- async tool2(input: { data: string }) {
104
- // authUser is available here too
115
+ @Tool({ description: 'Protected tool' })
116
+ async protectedTool(input: { data: string }) {
117
+ // authUser is available in all methods
105
118
  return { data: input.data, userId: authUser.sub };
106
119
  }
107
120
  }
108
121
  ```
109
122
 
110
- ## authUser Variable
123
+ ---
111
124
 
112
- ### Automatic User Information Injection
125
+ ## The authUser Variable
113
126
 
114
- When you use the `@Authenticated` decorator, a global `authUser` variable is automatically injected into your protected methods containing the decoded JWT token payload.
127
+ When using `@Authenticated`, a global `authUser` variable is automatically injected containing the decoded JWT payload:
115
128
 
116
129
  ```typescript
117
130
  @Tool({ description: 'Create post' })
118
131
  @Authenticated(authProvider)
119
132
  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
133
  return {
125
134
  id: generateId(),
126
135
  title: input.title,
@@ -131,30 +140,8 @@ async createPost(input: { title: string, content: string }) {
131
140
  }
132
141
  ```
133
142
 
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
143
  ### Provider-Specific User Data
155
144
 
156
- The structure of `authUser` depends on your authentication provider:
157
-
158
145
  **AWS Cognito:**
159
146
  ```typescript
160
147
  {
@@ -162,8 +149,7 @@ The structure of `authUser` depends on your authentication provider:
162
149
  email: 'user@example.com',
163
150
  email_verified: true,
164
151
  'cognito:username': 'username',
165
- 'cognito:groups': ['admin', 'users'],
166
- // ... other Cognito claims
152
+ 'cognito:groups': ['admin', 'users']
167
153
  }
168
154
  ```
169
155
 
@@ -175,8 +161,7 @@ The structure of `authUser` depends on your authentication provider:
175
161
  email: 'user@example.com',
176
162
  firstName: 'John',
177
163
  lastName: 'Doe',
178
- imageUrl: 'https://img.clerk.com/...',
179
- // ... other Clerk claims
164
+ imageUrl: 'https://img.clerk.com/...'
180
165
  }
181
166
  ```
182
167
 
@@ -187,135 +172,29 @@ The structure of `authUser` depends on your authentication provider:
187
172
  email: 'user@example.com',
188
173
  email_verified: true,
189
174
  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
175
+ picture: 'https://s.gravatar.com/avatar/...'
222
176
  }
223
177
  ```
224
178
 
225
- ## Usage
226
-
227
- ### Client Side - Calling Protected Methods
228
-
229
- Authentication tokens are passed via the `_meta` field following MCP protocol standards:
179
+ ### Controlling User Fetch
230
180
 
231
181
  ```typescript
232
- // With token (succeeds)
233
- await mcpClient.callTool({
234
- name: "analyzeSentiment",
235
- arguments: {
236
- text: "Hello world"
237
- },
238
- _meta: {
239
- authorization: {
240
- type: "bearer",
241
- token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
242
- }
243
- }
244
- });
245
-
246
- // Without token (fails with MISSING_TOKEN error)
247
- await mcpClient.callTool({
248
- name: "analyzeSentiment",
249
- arguments: {
250
- text: "Hello world"
251
- }
252
- });
253
- ```
254
-
255
- ### Raw MCP Request Format
256
-
257
- ```json
258
- {
259
- "method": "tools/call",
260
- "params": {
261
- "name": "analyzeSentiment",
262
- "arguments": {
263
- "text": "Hello world"
264
- },
265
- "_meta": {
266
- "authorization": {
267
- "type": "bearer",
268
- "token": "your-jwt-token"
269
- }
270
- }
271
- }
182
+ // Fetch user info (default)
183
+ @Authenticated(authProvider, { getUser: true })
184
+ async withUserInfo(input: any) {
185
+ console.log(authUser); // User data available
272
186
  }
273
- ```
274
187
 
275
- ### Error Handling
276
-
277
- ```typescript
278
- import { AuthenticationError } from "@leanmcp/auth";
279
-
280
- try {
281
- await service.protectedMethod({ text: "test" });
282
- } catch (error) {
283
- if (error instanceof AuthenticationError) {
284
- switch (error.code) {
285
- case 'MISSING_TOKEN':
286
- console.log('No token provided in request');
287
- break;
288
- case 'INVALID_TOKEN':
289
- console.log('Token is invalid or expired');
290
- break;
291
- case 'VERIFICATION_FAILED':
292
- console.log('Token verification failed:', error.message);
293
- break;
294
- }
295
- }
188
+ // Only verify token, skip user fetch (faster)
189
+ @Authenticated(authProvider, { getUser: false })
190
+ async tokenOnlyValidation(input: any) {
191
+ // authUser is undefined
296
192
  }
297
193
  ```
298
194
 
299
- ## Error Codes
300
-
301
- | Code | When | Message |
302
- |------|------|---------|
303
- | `MISSING_TOKEN` | No token in `_meta` | "Authentication required. Please provide a valid token in _meta.authorization.token" |
304
- | `INVALID_TOKEN` | Token invalid/expired | "Invalid or expired token. Please authenticate again." |
305
- | `VERIFICATION_FAILED` | Verification error | "Token verification failed: [details]" |
306
-
307
- ## Supported Auth Providers
308
-
309
- ### Provider Comparison
195
+ ---
310
196
 
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 |
197
+ ## Supported Providers
319
198
 
320
199
  ### AWS Cognito
321
200
 
@@ -328,11 +207,6 @@ const authProvider = new AuthProvider('cognito', {
328
207
  await authProvider.init();
329
208
  ```
330
209
 
331
- **Token Requirements:**
332
- - JWT token from AWS Cognito User Pool
333
- - Token must be valid and not expired
334
- - Token must be issued by the configured User Pool
335
-
336
210
  **Environment Variables:**
337
211
  ```bash
338
212
  AWS_REGION=us-east-1
@@ -342,31 +216,14 @@ COGNITO_CLIENT_ID=your-client-id
342
216
 
343
217
  ### Clerk
344
218
 
345
- Clerk supports both **Session Mode** (default) and **OAuth Mode** (with refresh tokens).
346
-
347
- #### Session Mode (Default)
348
-
349
219
  ```typescript
220
+ // Session Mode (default)
350
221
  const authProvider = new AuthProvider('clerk', {
351
222
  frontendApi: 'your-frontend-api.clerk.accounts.dev',
352
223
  secretKey: 'sk_test_...'
353
224
  });
354
- await authProvider.init();
355
- ```
356
-
357
- **Configuration:**
358
- - `frontendApi` - Your Clerk Frontend API domain
359
- - `secretKey` - Your Clerk Secret Key
360
225
 
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
226
+ // OAuth Mode (with refresh tokens)
370
227
  const authProvider = new AuthProvider('clerk', {
371
228
  frontendApi: 'your-frontend-api.clerk.accounts.dev',
372
229
  secretKey: 'sk_test_...',
@@ -374,27 +231,8 @@ const authProvider = new AuthProvider('clerk', {
374
231
  clientSecret: 'your-oauth-client-secret',
375
232
  redirectUri: 'https://yourapp.com/callback'
376
233
  });
377
- await authProvider.init();
378
234
 
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 }
235
+ await authProvider.init();
398
236
  ```
399
237
 
400
238
  ### Auth0
@@ -403,55 +241,69 @@ const user = await authProvider.getUser(idToken);
403
241
  const authProvider = new AuthProvider('auth0', {
404
242
  domain: 'your-tenant.auth0.com',
405
243
  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
244
+ clientSecret: 'your-client-secret',
245
+ audience: 'https://your-api-identifier'
409
246
  });
410
247
  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
248
  ```
416
249
 
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`)
250
+ ### LeanMCP
423
251
 
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
252
+ For LeanMCP platform deployments with user secrets support:
253
+
254
+ ```typescript
255
+ const authProvider = new AuthProvider('leanmcp', {
256
+ apiKey: 'your-leanmcp-api-key'
257
+ });
258
+ await authProvider.init();
430
259
  ```
431
260
 
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
261
+ ---
262
+
263
+ ## Client Usage
264
+
265
+ Authentication tokens are passed via the `_meta` field following MCP protocol standards:
437
266
 
438
- **User Data:**
439
267
  ```typescript
440
- const user = await authProvider.getUser(idToken);
441
- // Returns: { sub, email, email_verified, name, attributes }
268
+ await mcpClient.callTool({
269
+ name: "analyzeSentiment",
270
+ arguments: { text: "Hello world" },
271
+ _meta: {
272
+ authorization: {
273
+ type: "bearer",
274
+ token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
275
+ }
276
+ }
277
+ });
442
278
  ```
443
279
 
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
280
+ ---
281
+
282
+ ## Error Handling
283
+
284
+ ```typescript
285
+ import { AuthenticationError } from "@leanmcp/auth";
450
286
 
451
- ### More Providers Coming Soon
287
+ try {
288
+ await service.protectedMethod({ text: "test" });
289
+ } catch (error) {
290
+ if (error instanceof AuthenticationError) {
291
+ switch (error.code) {
292
+ case 'MISSING_TOKEN':
293
+ console.log('No token provided');
294
+ break;
295
+ case 'INVALID_TOKEN':
296
+ console.log('Token is invalid or expired');
297
+ break;
298
+ case 'VERIFICATION_FAILED':
299
+ console.log('Verification failed:', error.message);
300
+ break;
301
+ }
302
+ }
303
+ }
304
+ ```
452
305
 
453
- - Firebase Auth
454
- - Custom JWT providers
306
+ ---
455
307
 
456
308
  ## API Reference
457
309
 
@@ -462,7 +314,7 @@ class AuthProvider {
462
314
  constructor(provider: string, config: any);
463
315
  async init(config?: any): Promise<void>;
464
316
  async verifyToken(token: string): Promise<boolean>;
465
- async refreshToken(refreshToken: string, username?: string): Promise<any>;
317
+ async refreshToken(refreshToken: string): Promise<any>;
466
318
  async getUser(token: string): Promise<any>;
467
319
  getProviderType(): string;
468
320
  }
@@ -477,18 +329,11 @@ function Authenticated(
477
329
  ): ClassDecorator | MethodDecorator;
478
330
 
479
331
  interface AuthenticatedOptions {
480
- getUser?: boolean; // Default: true
332
+ getUser?: boolean; // Default: true
333
+ projectId?: string; // For LeanMCP user secrets
481
334
  }
482
335
  ```
483
336
 
484
- Can be applied to:
485
- - **Classes** - Protects all methods in the class
486
- - **Methods** - Protects individual methods
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
-
492
337
  ### AuthenticationError
493
338
 
494
339
  ```typescript
@@ -501,409 +346,51 @@ class AuthenticationError extends Error {
501
346
  ### Helper Functions
502
347
 
503
348
  ```typescript
504
- // Check if method/class requires authentication
505
- function isAuthenticationRequired(target: any, propertyKey?: string): boolean;
349
+ // Check if authentication is required
350
+ function isAuthenticationRequired(target: any): boolean;
506
351
 
507
352
  // Get auth provider for method/class
508
- function getAuthProvider(target: any, propertyKey?: string): AuthProvider | undefined;
509
- ```
510
-
511
- ## Environment Variables
512
-
513
- ### AWS Cognito
514
- ```bash
515
- AWS_REGION=us-east-1
516
- COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
517
- COGNITO_CLIENT_ID=your-client-id
518
- ```
519
-
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
- ```
533
-
534
- ## Complete Examples
535
-
536
- ### AWS Cognito Example
537
-
538
- See [examples/slack-with-auth](../../examples/slack-with-auth) for a complete working example.
539
-
540
- ```typescript
541
- import { createHTTPServer, MCPServer } from "@leanmcp/core";
542
- import { AuthProvider, Authenticated } from "@leanmcp/auth";
543
-
544
- // Initialize auth
545
- const authProvider = new AuthProvider('cognito', {
546
- region: process.env.AWS_REGION,
547
- userPoolId: process.env.COGNITO_USER_POOL_ID,
548
- clientId: process.env.COGNITO_CLIENT_ID
549
- });
550
- await authProvider.init();
551
-
552
- // Create service with protected methods
553
- @Authenticated(authProvider)
554
- class MyService {
555
- @Tool({ description: 'Process protected data' })
556
- async protectedTool(input: { data: string }) {
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
- };
566
- }
567
- }
568
-
569
- // Start server
570
- const serverFactory = () => {
571
- const server = new MCPServer({ name: "auth-server", version: "1.0.0" });
572
- server.registerService(new MyService());
573
- return server.getServer();
574
- };
353
+ function getAuthProvider(target: any): AuthProviderBase | undefined;
575
354
 
576
- await createHTTPServer(serverFactory, { port: 3000 });
355
+ // Get current authenticated user
356
+ function getAuthUser(): any;
577
357
  ```
578
358
 
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
-
738
- ## How It Works
739
-
740
- 1. **Request arrives** with `_meta.authorization.token`
741
- 2. **Decorator intercepts** the method call before execution
742
- 3. **Token is extracted** from `_meta.authorization.token`
743
- 4. **Token is validated** using the configured auth provider
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
749
-
750
- **Key Benefits:**
751
- - **Clean separation** - Authentication metadata separate from business data
752
- - **MCP compliant** - Follows standard `_meta` pattern
753
- - **Type-safe** - Input classes don't need token fields
754
- - **Automatic user injection** - Access user data via `authUser` without manual extraction
755
- - **Reusable** - Same input classes work for authenticated and public methods
756
- - **Secure** - `authUser` is scoped to method execution and cleaned up after
359
+ ---
757
360
 
758
361
  ## Best Practices
759
362
 
760
- 1. **Always use HTTPS in production** - Tokens should never be sent over HTTP
761
- 2. **Store tokens securely** - Use secure storage mechanisms (keychain, encrypted storage)
762
- 3. **Implement token refresh** - Use refresh tokens to get new access tokens
763
- 4. **Add rate limiting** - Protect against brute force attacks
764
- 5. **Log authentication failures** - Monitor for suspicious activity
765
- 6. **Use environment variables** - Never hardcode credentials
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
- });
363
+ ### Security
364
+ - Always use HTTPS in production
365
+ - Store tokens securely (keychain, encrypted storage)
366
+ - Implement token refresh before expiration
367
+ - Add rate limiting to protect against brute force
791
368
 
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
- });
369
+ ### Configuration
370
+ - Use environment variables for credentials
371
+ - Never hardcode secrets in code
372
+ - Use `_meta` for auth, not business arguments
800
373
 
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();
374
+ ### Performance
375
+ - Use `getUser: false` when you only need token validation
376
+ - JWKS keys are cached automatically for performance
815
377
 
816
- // Verify token
817
- const isValid = await authProvider.verifyToken(token);
378
+ ---
818
379
 
819
- // Refresh token (OAuth/Auth0 only)
820
- const newTokens = await authProvider.refreshToken(refreshToken);
380
+ ## Documentation
821
381
 
822
- // Get user data
823
- const user = await authProvider.getUser(idToken);
382
+ - [Full Documentation](https://docs.leanmcp.com/sdk/auth)
824
383
 
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
384
+ ## Related Packages
864
385
 
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
- }
386
+ - [@leanmcp/core](https://www.npmjs.com/package/@leanmcp/core) — Core decorators and server functionality
387
+ - [@leanmcp/env-injection](https://www.npmjs.com/package/@leanmcp/env-injection) Environment variable injection for user secrets
881
388
 
882
- // Type authUser for better type safety
883
- interface MyUser {
884
- sub: string;
885
- email: string;
886
- name?: string;
887
- }
389
+ ## Links
888
390
 
889
- @Authenticated(authProvider)
890
- async typedMethod(input: any) {
891
- const user = authUser as MyUser;
892
- console.log(user.email); // Fully typed
893
- }
894
- ```
391
+ - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
392
+ - [NPM Package](https://www.npmjs.com/package/@leanmcp/auth)
895
393
 
896
394
  ## License
897
395
 
898
396
  MIT
899
-
900
- ## Related Packages
901
-
902
- - [@leanmcp/core](../core) - Core MCP server functionality
903
- - [@leanmcp/cli](../cli) - CLI tool for creating new projects
904
- - [@leanmcp/utils](../utils) - Utility functions
905
-
906
- ## Links
907
-
908
- - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
909
- - [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)