Skip to main content

@leanmcp/auth

Authentication module for LeanMCP providing token-based authentication decorators and multi-provider support for protecting MCP tools, prompts, and resources.

Features

@Authenticated Decorator

Protect tools, prompts, and resources with a simple decorator

Multi-Provider Support

AWS Cognito, Clerk, Auth0, and LeanMCP providers

Automatic authUser

Decoded user info injected as global authUser variable

Concurrency Safe

Uses AsyncLocalStorage for request-isolated context

Installation

npm install @leanmcp/auth @leanmcp/core

Provider Dependencies

npm install @aws-sdk/client-cognito-identity-provider axios jsonwebtoken jwk-to-pem

Quick Start

1. Initialize Auth Provider

import { AuthProvider } from "@leanmcp/auth";

const authProvider = new AuthProvider('cognito', {
  region: 'us-east-1',
  userPoolId: 'us-east-1_XXXXXXXXX',
  clientId: 'your-client-id'
});

await authProvider.init();

2. Protect Methods with @Authenticated

import { Tool } from "@leanmcp/core";
import { Authenticated } from "@leanmcp/auth";

export class SentimentService {
  @Tool({ description: 'Analyze sentiment (requires auth)' })
  @Authenticated(authProvider)
  async analyzeSentiment(input: { text: string }) {
    // authUser is automatically available with user info
    console.log('User ID:', authUser.sub);
    console.log('Email:', authUser.email);

    return { 
      sentiment: 'positive', 
      score: 0.8,
      analyzedBy: authUser.sub
    };
  }

  // Public method - no authentication
  @Tool({ description: 'Get categories (public)' })
  async getCategories() {
    return { categories: ['positive', 'negative', 'neutral'] };
  }
}

3. Protect Entire Service

// All methods in this class require authentication
@Authenticated(authProvider)
export class SecureService {
  @Tool({ description: 'Protected tool' })
  async protectedTool(input: { data: string }) {
    // authUser is available in all methods
    return { data: input.data, userId: authUser.sub };
  }
}

The authUser Variable

When using @Authenticated, a global authUser variable is automatically injected containing the decoded JWT payload:
@Tool({ description: 'Create post' })
@Authenticated(authProvider)
async createPost(input: { title: string, content: string }) {
  // authUser is automatically available
  return {
    id: generateId(),
    title: input.title,
    content: input.content,
    authorId: authUser.sub,
    authorEmail: authUser.email
  };
}

Provider-Specific User Data

{
  sub: 'user-uuid',
  email: '[email protected]',
  email_verified: true,
  'cognito:username': 'username',
  'cognito:groups': ['admin', 'users']
}

Controlling User Fetch

// Fetch user info (default)
@Authenticated(authProvider, { getUser: true })
async withUserInfo(input: any) {
  console.log(authUser); // User data available
}

// Only verify token, skip user fetch (faster)
@Authenticated(authProvider, { getUser: false })
async tokenOnlyValidation(input: any) {
  // authUser is undefined
}

Supported Providers

AWS Cognito

const authProvider = new AuthProvider('cognito', {
  region: 'us-east-1',
  userPoolId: 'us-east-1_XXXXXXXXX',
  clientId: 'your-client-id'
});
await authProvider.init();
Environment Variables:
AWS_REGION=us-east-1
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
COGNITO_CLIENT_ID=your-client-id

Clerk

// Session Mode (default)
const authProvider = new AuthProvider('clerk', {
  frontendApi: 'your-frontend-api.clerk.accounts.dev',
  secretKey: 'sk_test_...'
});

// OAuth Mode (with refresh tokens)
const authProvider = new AuthProvider('clerk', {
  frontendApi: 'your-frontend-api.clerk.accounts.dev',
  secretKey: 'sk_test_...',
  clientId: 'your-oauth-client-id',
  clientSecret: 'your-oauth-client-secret',
  redirectUri: 'https://yourapp.com/callback'
});

await authProvider.init();

Auth0

const authProvider = new AuthProvider('auth0', {
  domain: 'your-tenant.auth0.com',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  audience: 'https://your-api-identifier'
});
await authProvider.init();

LeanMCP

For LeanMCP platform deployments with user secrets support:
const authProvider = new AuthProvider('leanmcp', {
  apiKey: 'your-leanmcp-api-key'
});
await authProvider.init();

Client Usage

Authentication tokens are passed via the _meta field following MCP protocol standards:
await mcpClient.callTool({
  name: "analyzeSentiment",
  arguments: { text: "Hello world" },
  _meta: {
    authorization: {
      type: "bearer",
      token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  }
});

Error Handling

import { AuthenticationError } from "@leanmcp/auth";

try {
  await service.protectedMethod({ text: "test" });
} catch (error) {
  if (error instanceof AuthenticationError) {
    switch (error.code) {
      case 'MISSING_TOKEN':
        console.log('No token provided');
        break;
      case 'INVALID_TOKEN':
        console.log('Token is invalid or expired');
        break;
      case 'VERIFICATION_FAILED':
        console.log('Verification failed:', error.message);
        break;
    }
  }
}

API Reference

AuthProvider

class AuthProvider {
  constructor(provider: string, config: any);
  async init(config?: any): Promise<void>;
  async verifyToken(token: string): Promise<boolean>;
  async refreshToken(refreshToken: string): Promise<any>;
  async getUser(token: string): Promise<any>;
  getProviderType(): string;
}

@Authenticated Decorator

function Authenticated(
  authProvider: AuthProvider, 
  options?: AuthenticatedOptions
): ClassDecorator | MethodDecorator;

interface AuthenticatedOptions {
  getUser?: boolean;   // Default: true
  projectId?: string;  // For LeanMCP user secrets
}

AuthenticationError

class AuthenticationError extends Error {
  code: 'MISSING_TOKEN' | 'INVALID_TOKEN' | 'VERIFICATION_FAILED';
  constructor(message: string, code: string);
}

Helper Functions

// Check if authentication is required
function isAuthenticationRequired(target: any): boolean;

// Get auth provider for method/class
function getAuthProvider(target: any): AuthProviderBase | undefined;

// Get current authenticated user
function getAuthUser(): any;

Best Practices

  • Always use HTTPS in production
  • Store tokens securely (keychain, encrypted storage)
  • Implement token refresh before expiration
  • Add rate limiting to protect against brute force
  • Use environment variables for credentials
  • Never hardcode secrets in code
  • Use _meta for auth, not business arguments
  • Use getUser: false when you only need token validation
  • JWKS keys are cached automatically for performance

OAuth 2.1 Support

Beyond server-side token verification, @leanmcp/auth provides complete OAuth 2.1 infrastructure:

Submodule Imports

// Server-side token verification (this page)
import { AuthProvider, Authenticated } from '@leanmcp/auth';

// OAuth client for browser-based flows
import { OAuthClient } from '@leanmcp/auth/client';

// Token storage backends
import { MemoryStorage, FileStorage, KeychainStorage } from '@leanmcp/auth/storage';

// OAuth proxy for external providers
import { OAuthProxy, googleProvider, githubProvider } from '@leanmcp/auth/proxy';

// OAuth authorization server
import { OAuthAuthorizationServer } from '@leanmcp/auth/server';