Back to TutorialsSecurity & Best PracticesAuthentication & Authorization in MCP
Security & Best PracticesAdvanced

Authentication & Authorization in MCP

Implement secure authentication and authorization for MCP servers

30 min read
MCPgee Team
Security & Best Practices

Prerequisites

  • Completed 'MCP Security Fundamentals' tutorial
  • Understanding of authentication concepts (JWT, OAuth)
  • Experience with MCP server development
  • Knowledge of cryptography basics

Authentication & Authorization in MCP

Introduction

Authentication and authorization are critical components of secure MCP server implementations. While MCP itself doesn't mandate specific auth mechanisms, implementing robust authentication ensures that only authorized clients can access your MCP servers and that operations are properly scoped to user permissions.

This comprehensive guide covers various authentication strategies, from simple API keys to advanced OAuth2 implementations, along with best practices for authorization in MCP contexts.

Authentication Strategies Overview

Choosing the Right Strategy

Different authentication methods suit different use cases:

MethodUse CaseSecurity LevelComplexity
API KeysInternal tools, simple integrationsMediumLow
JWT TokensStateless auth, microservicesHighMedium
OAuth2Third-party integrations, enterpriseVery HighHigh
mTLSZero-trust environmentsVery HighHigh
Session-basedTraditional web appsMediumLow

MCP Transport Considerations

typescript
// Different auth for different transports
interface AuthStrategy {
  validateStdio?: (env: ProcessEnv) => Promise<boolean>;
  validateHTTP?: (headers: Headers) => Promise<boolean>;
  validateWebSocket?: (request: Request) => Promise<boolean>;
}

class TransportAwareAuth implements AuthStrategy { async validateStdio(env: ProcessEnv): Promise<boolean> { // For stdio: Use environment variables const token = env.MCP_AUTH_TOKEN; return this.validateToken(token); } async validateHTTP(headers: Headers): Promise<boolean> { // For HTTP: Use Authorization header const authHeader = headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return false; } return this.validateToken(authHeader.slice(7)); } async validateWebSocket(request: Request): Promise<boolean> { // For WebSocket: Use query parameters or headers const url = new URL(request.url); const token = url.searchParams.get('token'); return this.validateToken(token); } private async validateToken(token: string | null): Promise<boolean> { if (!token) return false; // Token validation logic return true; } }

API Key Authentication

Basic Implementation

typescript
import crypto from 'crypto';
import { createHash } from 'crypto';

class APIKeyManager { private keys: Map<string, APIKey> = new Map(); interface APIKey { id: string; hashedKey: string; name: string; permissions: string[]; createdAt: Date; lastUsed: Date; expiresAt?: Date; metadata: Record<string, any>; } generateAPIKey(): { key: string; keyData: APIKey } { // Generate secure random key const key = crypto.randomBytes(32).toString('base64url'); const keyId = crypto.randomBytes(8).toString('hex'); // Hash the key for storage const hashedKey = createHash('sha256') .update(key) .digest('hex'); const keyData: APIKey = { id: keyId, hashedKey, name: 'Generated API Key', permissions: ['read'], createdAt: new Date(), lastUsed: new Date(), metadata: {} }; this.keys.set(keyId, keyData); // Return the key only once - it can't be recovered return { key: mcp_${keyId}_${key}, // Prefixed format keyData }; } async validateAPIKey(apiKey: string): Promise<APIKey | null> { // Parse the key const match = apiKey.match(/^mcp_([a-f0-9]{16})_(.+)$/); if (!match) return null; const [, keyId, key] = match; const keyData = this.keys.get(keyId); if (!keyData) return null; // Check expiration if (keyData.expiresAt && keyData.expiresAt < new Date()) { return null; } // Verify the key const hashedKey = createHash('sha256') .update(key) .digest('hex'); if (hashedKey !== keyData.hashedKey) { return null; } // Update last used keyData.lastUsed = new Date(); return keyData; } revokeAPIKey(keyId: string): boolean { return this.keys.delete(keyId); } }

Advanced API Key Features

typescript
class AdvancedAPIKeyManager extends APIKeyManager {
  private rateLimits: Map<string, RateLimit> = new Map();
  private usageStats: Map<string, UsageStats> = new Map();
  
  interface RateLimit {
    requests: number;
    window: number;  // in seconds
  }
  
  interface UsageStats {
    totalRequests: number;
    lastRequests: Array<{ timestamp: Date; endpoint: string }>;
  }
  
  async validateWithRateLimit(
    apiKey: string,
    endpoint: string
  ): Promise<{ valid: boolean; remaining?: number }> {
    const keyData = await this.validateAPIKey(apiKey);
    if (!keyData) {
      return { valid: false };
    }
    
    // Check rate limit
    const limit = this.rateLimits.get(keyData.id) || {
      requests: 1000,
      window: 3600  // 1 hour
    };
    
    const stats = this.usageStats.get(keyData.id) || {
      totalRequests: 0,
      lastRequests: []
    };
    
    // Clean old requests
    const cutoff = new Date(Date.now() - limit.window * 1000);
    stats.lastRequests = stats.lastRequests.filter(
      req => req.timestamp > cutoff
    );
    
    // Check limit
    if (stats.lastRequests.length >= limit.requests) {
      return { valid: false, remaining: 0 };
    }
    
    // Record request
    stats.lastRequests.push({ timestamp: new Date(), endpoint });
    stats.totalRequests++;
    this.usageStats.set(keyData.id, stats);
    
    return {
      valid: true,
      remaining: limit.requests - stats.lastRequests.length
    };
  }
  
  // Scope-based permissions
  hasPermission(keyData: APIKey, scope: string): boolean {
    // Check exact match
    if (keyData.permissions.includes(scope)) {
      return true;
    }
    
    // Check wildcard permissions
    for (const perm of keyData.permissions) {
      if (perm.endsWith('*')) {
        const prefix = perm.slice(0, -1);
        if (scope.startsWith(prefix)) {
          return true;
        }
      }
    }
    
    return false;
  }
}

JWT Token Authentication

JWT Implementation

typescript
import jwt from 'jsonwebtoken';
import { JWK, JWS } from 'node-jose';

interface TokenPayload { sub: string; // Subject (user ID) iss: string; // Issuer aud: string[]; // Audience exp: number; // Expiration iat: number; // Issued at nbf?: number; // Not before jti?: string; // JWT ID scope?: string[]; // Permissions roles?: string[]; // User roles }

class JWTAuthenticator { private publicKey: string; private privateKey: string; private issuer: string; private audience: string[]; private tokenExpiry: number = 3600; // 1 hour constructor(config: JWTConfig) { this.publicKey = config.publicKey; this.privateKey = config.privateKey; this.issuer = config.issuer; this.audience = config.audience; } async generateToken( userId: string, scope: string[] = [], roles: string[] = [] ): Promise<string> { const now = Math.floor(Date.now() / 1000); const payload: TokenPayload = { sub: userId, iss: this.issuer, aud: this.audience, exp: now + this.tokenExpiry, iat: now, nbf: now, jti: crypto.randomUUID(), scope, roles }; return jwt.sign(payload, this.privateKey, { algorithm: 'RS256', keyid: await this.getKeyId() }); } async validateToken(token: string): Promise<TokenPayload | null> { try { const decoded = jwt.verify(token, this.publicKey, { algorithms: ['RS256'], issuer: this.issuer, audience: this.audience, clockTolerance: 10 // 10 seconds }) as TokenPayload; // Additional validation if (!decoded.sub || !decoded.scope) { return null; } // Check if token is blacklisted if (await this.isTokenBlacklisted(decoded.jti)) { return null; } return decoded; } catch (error) { console.error('Token validation failed:', error); return null; } } async refreshToken(token: string): Promise<string | null> { const payload = await this.validateToken(token); if (!payload) return null; // Check if token is eligible for refresh const now = Math.floor(Date.now() / 1000); const timeUntilExpiry = payload.exp - now; // Only refresh if less than 5 minutes remaining if (timeUntilExpiry > 300) { return token; // Return existing token } // Generate new token with same claims return this.generateToken( payload.sub, payload.scope, payload.roles ); } private async getKeyId(): Promise<string> { // Generate key ID from public key const keyStore = JWK.createKeyStore(); const key = await keyStore.add(this.publicKey, 'pem'); return key.kid; } private tokenBlacklist = new Set<string>(); async blacklistToken(jti: string): Promise<void> { this.tokenBlacklist.add(jti); // In production, store in Redis with TTL } private async isTokenBlacklisted(jti?: string): Promise<boolean> { if (!jti) return false; return this.tokenBlacklist.has(jti); } }

JWT with MCP Integration

typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';

class JWTProtectedMCPServer { private server: Server; private auth: JWTAuthenticator; constructor() { this.auth = new JWTAuthenticator({ publicKey: process.env.JWT_PUBLIC_KEY!, privateKey: process.env.JWT_PRIVATE_KEY!, issuer: 'mcp-server', audience: ['mcp-client'] }); this.setupServer(); } private setupServer() { // Middleware to validate JWT on every request this.server.use(async (request, next) => { // Extract token from request const token = this.extractToken(request); if (!token) { throw new Error('Authentication required'); } const payload = await this.auth.validateToken(token); if (!payload) { throw new Error('Invalid or expired token'); } // Attach user context to request request.context = { userId: payload.sub, scope: payload.scope || [], roles: payload.roles || [] }; return next(); }); // Scope-based authorization for tools this.server.setRequestHandler('tools/call', async (request) => { const { name: toolName } = request.params; const requiredScope = tools:${toolName}; if (!request.context.scope.includes(requiredScope) && !request.context.scope.includes('tools:*')) { throw new Error(Insufficient scope: ${requiredScope} required); } // Execute tool... }); } private extractToken(request: any): string | null { // Try Authorization header first if (request.headers?.authorization) { const parts = request.headers.authorization.split(' '); if (parts.length === 2 && parts[0] === 'Bearer') { return parts[1]; } } // Try query parameter if (request.query?.token) { return request.query.token; } // Try cookie if (request.cookies?.mcp_token) { return request.cookies.mcp_token; } return null; } }

OAuth2 Integration

OAuth2 Server Implementation

typescript
import { OAuth2Server } from 'oauth2-server';

class MCPOAuth2Provider { private oauth: OAuth2Server; private clients: Map<string, OAuthClient> = new Map(); private tokens: Map<string, Token> = new Map(); interface OAuthClient { id: string; secret: string; redirectUris: string[]; grants: string[]; scopes: string[]; } interface Token { accessToken: string; refreshToken?: string; client: string; user: string; scope: string[]; expiresAt: Date; } constructor() { this.oauth = new OAuth2Server({ model: this, grants: ['authorization_code', 'refresh_token', 'client_credentials'], accessTokenLifetime: 3600, refreshTokenLifetime: 86400 }); } // OAuth2 Model Implementation async getClient(clientId: string, clientSecret: string): Promise<OAuthClient | null> { const client = this.clients.get(clientId); if (!client || client.secret !== clientSecret) { return null; } return client; } async saveToken(token: any, client: any, user: any): Promise<Token> { const tokenData: Token = { accessToken: token.accessToken, refreshToken: token.refreshToken, client: client.id, user: user.id, scope: token.scope || [], expiresAt: token.accessTokenExpiresAt }; this.tokens.set(token.accessToken, tokenData); if (token.refreshToken) { this.tokens.set(token.refreshToken, tokenData); } return tokenData; } async getAccessToken(accessToken: string): Promise<Token | null> { const token = this.tokens.get(accessToken); if (!token || token.expiresAt < new Date()) { return null; } return token; } // Authorization flow async authorize(request: Request, response: Response): Promise<void> { const authRequest = new OAuth2Server.Request(request); const authResponse = new OAuth2Server.Response(response); try { const code = await this.oauth.authorize(authRequest, authResponse, { authenticateHandler: { handle: async (req) => { // Verify user authentication const userId = req.headers['x-user-id']; if (!userId) throw new Error('User not authenticated'); return { id: userId }; } } }); // Redirect to callback with code const redirectUri = ${request.query.redirect_uri}?code=${code.authorizationCode}&state=${request.query.state}; response.redirect(redirectUri); } catch (error) { response.status(error.code || 500).json({ error: error.name, error_description: error.message }); } } // Token exchange async token(request: Request, response: Response): Promise<void> { const tokenRequest = new OAuth2Server.Request(request); const tokenResponse = new OAuth2Server.Response(response); try { const token = await this.oauth.token(tokenRequest, tokenResponse); response.json({ access_token: token.accessToken, token_type: 'Bearer', expires_in: token.accessTokenLifetime, refresh_token: token.refreshToken, scope: token.scope.join(' ') }); } catch (error) { response.status(error.code || 500).json({ error: error.name, error_description: error.message }); } } }

OAuth2 Client for MCP

typescript
class OAuth2MCPClient {
  private clientId: string;
  private clientSecret: string;
  private authorizationUrl: string;
  private tokenUrl: string;
  private redirectUri: string;
  private scope: string[];
  
  constructor(config: OAuth2Config) {
    this.clientId = config.clientId;
    this.clientSecret = config.clientSecret;
    this.authorizationUrl = config.authorizationUrl;
    this.tokenUrl = config.tokenUrl;
    this.redirectUri = config.redirectUri;
    this.scope = config.scope;
  }
  
  // Generate authorization URL
  getAuthorizationUrl(state: string): string {
    const params = new URLSearchParams({
      response_type: 'code',
      client_id: this.clientId,
      redirect_uri: this.redirectUri,
      scope: this.scope.join(' '),
      state
    });
    
    return ${this.authorizationUrl}?${params};
  }
  
  // Exchange code for token
  async exchangeCodeForToken(code: string): Promise<TokenResponse> {
    const response = await fetch(this.tokenUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': Basic ${Buffer.from(
          ${this.clientId}:${this.clientSecret}
        ).toString('base64')}
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code,
        redirect_uri: this.redirectUri
      })
    });
    
    if (!response.ok) {
      throw new Error('Token exchange failed');
    }
    
    return response.json();
  }
  
  // Refresh token
  async refreshToken(refreshToken: string): Promise<TokenResponse> {
    const response = await fetch(this.tokenUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': Basic ${Buffer.from(
          ${this.clientId}:${this.clientSecret}
        ).toString('base64')}
      },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: refreshToken
      })
    });
    
    if (!response.ok) {
      throw new Error('Token refresh failed');
    }
    
    return response.json();
  }
  
  // Create authenticated MCP transport
  createAuthenticatedTransport(accessToken: string): AuthenticatedTransport {
    return new AuthenticatedTransport({
      baseTransport: new HttpClientTransport(),
      authHeader: Bearer ${accessToken},
      onAuthError: async () => {
        // Handle token refresh
        if (this.refreshToken) {
          const newToken = await this.refreshToken(this.refreshToken);
          this.accessToken = newToken.access_token;
          return Bearer ${newToken.access_token};
        }
        throw new Error('Authentication failed');
      }
    });
  }
}

Mutual TLS (mTLS) Authentication

mTLS Server Setup

typescript
import tls from 'tls';
import { readFileSync } from 'fs';

class MTLSMCPServer { private server: tls.Server; private allowedCertificates: Map<string, ClientCert> = new Map(); interface ClientCert { fingerprint: string; subject: string; issuer: string; permissions: string[]; validFrom: Date; validTo: Date; } constructor() { const options: tls.TlsOptions = { key: readFileSync('server-key.pem'), cert: readFileSync('server-cert.pem'), ca: readFileSync('ca-cert.pem'), // Require client certificates requestCert: true, rejectUnauthorized: true, // Verify client certificate verifyCallback: (socket, cert) => { return this.verifyCertificate(cert); } }; this.server = tls.createServer(options, (socket) => { this.handleConnection(socket); }); } private verifyCertificate(cert: tls.PeerCertificate): boolean { // Generate fingerprint const fingerprint = cert.fingerprint.replace(/:/g, '').toLowerCase(); // Check if certificate is allowed const allowed = this.allowedCertificates.get(fingerprint); if (!allowed) { console.error('Unknown certificate:', fingerprint); return false; } // Check validity period const now = new Date(); if (now < allowed.validFrom || now > allowed.validTo) { console.error('Certificate expired:', fingerprint); return false; } // Additional checks if (cert.subject.CN !== allowed.subject) { console.error('Subject mismatch:', cert.subject.CN); return false; } return true; } private handleConnection(socket: tls.TLSSocket) { const cert = socket.getPeerCertificate(); const fingerprint = cert.fingerprint.replace(/:/g, '').toLowerCase(); const clientInfo = this.allowedCertificates.get(fingerprint)!; // Create MCP transport with client context const transport = new TLSTransport(socket, { clientId: fingerprint, permissions: clientInfo.permissions }); // Initialize MCP server with auth context const mcpServer = new Server({ name: 'mtls-protected-server', version: '1.0.0' }); // All requests are pre-authenticated via mTLS mcpServer.setRequestHandler('tools/call', async (request) => { // Check permissions from certificate const tool = request.params.name; if (!clientInfo.permissions.includes(tools:${tool})) { throw new Error('Permission denied'); } // Execute tool... }); mcpServer.connect(transport); } // Certificate management async issueCertificate( clientId: string, permissions: string[] ): Promise<{ cert: string; key: string }> { // In production, use a proper CA // This is a simplified example const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); const cert = { subject: /CN=${clientId}/O=MCP Client, issuer: '/CN=MCP CA/O=MCP Server', validFrom: new Date(), validTo: new Date(Date.now() + 365 24 60 60 1000), extensions: { permissions: permissions.join(',') } }; // Generate and sign certificate // (Implementation depends on crypto library) return { cert: 'generated-cert-pem', key: 'generated-key-pem' }; } }

Session-Based Authentication

Secure Session Management

typescript
import { SessionData } from 'express-session';
import Redis from 'ioredis';

class SecureSessionManager { private redis: Redis; private sessionTTL: number = 3600; // 1 hour interface MCPSession extends SessionData { id: string; userId: string; roles: string[]; permissions: string[]; createdAt: Date; lastActivity: Date; ipAddress: string; userAgent: string; metadata: Record<string, any>; } constructor(redisUrl: string) { this.redis = new Redis(redisUrl); } async createSession(userId: string, context: any): Promise<string> { const sessionId = crypto.randomBytes(32).toString('hex'); const session: MCPSession = { id: sessionId, userId, roles: context.roles || [], permissions: context.permissions || [], createdAt: new Date(), lastActivity: new Date(), ipAddress: context.ipAddress, userAgent: context.userAgent, metadata: {} }; // Store in Redis with TTL await this.redis.setex( session:${sessionId}, this.sessionTTL, JSON.stringify(session) ); // Store user's active sessions await this.redis.sadd(user:${userId}:sessions, sessionId); return sessionId; } async getSession(sessionId: string): Promise<MCPSession | null> { const data = await this.redis.get(session:${sessionId}); if (!data) return null; const session = JSON.parse(data) as MCPSession; // Check if session is still valid const inactivityTimeout = 30 60 1000; // 30 minutes const lastActivity = new Date(session.lastActivity); if (Date.now() - lastActivity.getTime() > inactivityTimeout) { await this.revokeSession(sessionId); return null; } // Update last activity session.lastActivity = new Date(); await this.redis.setex( session:${sessionId}, this.sessionTTL, JSON.stringify(session) ); return session; } async revokeSession(sessionId: string): Promise<void> { const session = await this.getSession(sessionId); if (!session) return; // Remove from Redis await this.redis.del(session:${sessionId}); // Remove from user's sessions await this.redis.srem(user:${session.userId}:sessions, sessionId); } async revokeAllUserSessions(userId: string): Promise<void> { const sessions = await this.redis.smembers(user:${userId}:sessions); for (const sessionId of sessions) { await this.revokeSession(sessionId); } } // Session-based MCP middleware createSessionMiddleware() { return async (request: any, next: Function) => { // Extract session ID from cookie or header const sessionId = request.cookies?.mcp_session || request.headers['x-session-id']; if (!sessionId) { throw new Error('No session provided'); } const session = await this.getSession(sessionId); if (!session) { throw new Error('Invalid or expired session'); } // Verify IP address hasn't changed (optional) if (session.ipAddress !== request.ip) { console.warn('IP address mismatch for session:', sessionId); // Optionally revoke session } // Attach session to request request.session = session; return next(); }; } }

Multi-Factor Authentication (MFA)

TOTP Implementation

typescript
import speakeasy from 'speakeasy';
import QRCode from 'qrcode';

class MFAManager { private userSecrets: Map<string, MFASecret> = new Map(); interface MFASecret { secret: string; backupCodes: string[]; createdAt: Date; lastUsed?: Date; } async enableMFA(userId: string): Promise<{ secret: string; qrCode: string; backupCodes: string[]; }> { // Generate secret const secret = speakeasy.generateSecret({ name: MCP Server (${userId}), issuer: 'MCP Auth', length: 32 }); // Generate backup codes const backupCodes = Array.from({ length: 8 }, () => crypto.randomBytes(4).toString('hex') ); // Store secret this.userSecrets.set(userId, { secret: secret.base32, backupCodes, createdAt: new Date() }); // Generate QR code const qrCode = await QRCode.toDataURL(secret.otpauth_url!); return { secret: secret.base32, qrCode, backupCodes }; } async verifyMFA( userId: string, token: string ): Promise<boolean> { const mfaSecret = this.userSecrets.get(userId); if (!mfaSecret) return false; // Try TOTP verification const verified = speakeasy.totp.verify({ secret: mfaSecret.secret, encoding: 'base32', token, window: 2 // Allow 2 time steps tolerance }); if (verified) { mfaSecret.lastUsed = new Date(); return true; } // Try backup codes const codeIndex = mfaSecret.backupCodes.indexOf(token); if (codeIndex !== -1) { // Remove used backup code mfaSecret.backupCodes.splice(codeIndex, 1); mfaSecret.lastUsed = new Date(); return true; } return false; } }

// MFA-protected authentication flow class MFAAuthFlow { private mfaManager: MFAManager; private pendingAuths: Map<string, PendingAuth> = new Map(); interface PendingAuth { userId: string; timestamp: Date; attempts: number; } async authenticate( username: string, password: string ): Promise<{ requiresMFA: boolean; authToken?: string }> { // Verify username/password const user = await this.verifyCredentials(username, password); if (!user) { throw new Error('Invalid credentials'); } // Check if MFA is enabled if (!user.mfaEnabled) { // Generate auth token directly const token = await this.generateAuthToken(user.id); return { requiresMFA: false, authToken: token }; } // Create pending auth const pendingId = crypto.randomUUID(); this.pendingAuths.set(pendingId, { userId: user.id, timestamp: new Date(), attempts: 0 }); // Clean up after 5 minutes setTimeout(() => { this.pendingAuths.delete(pendingId); }, 5 60 1000); return { requiresMFA: true, pendingAuthId: pendingId }; } async completeMFA( pendingAuthId: string, mfaToken: string ): Promise<string> { const pending = this.pendingAuths.get(pendingAuthId); if (!pending) { throw new Error('Invalid or expired auth session'); } // Check attempts pending.attempts++; if (pending.attempts > 3) { this.pendingAuths.delete(pendingAuthId); throw new Error('Too many failed attempts'); } // Verify MFA const verified = await this.mfaManager.verifyMFA( pending.userId, mfaToken ); if (!verified) { throw new Error('Invalid MFA token'); } // Clean up and generate token this.pendingAuths.delete(pendingAuthId); return this.generateAuthToken(pending.userId); } }

Authorization Patterns

Fine-Grained Permissions

typescript
class PermissionManager {
  private permissions: Map<string, Permission> = new Map();
  
  interface Permission {
    id: string;
    resource: string;
    action: string;
    conditions?: PermissionCondition[];
  }
  
  interface PermissionCondition {
    type: 'ownership' | 'time' | 'location' | 'attribute';
    operator: 'eq' | 'ne' | 'gt' | 'lt' | 'in' | 'between';
    value: any;
  }
  
  // Define permissions
  definePermission(permission: Permission): void {
    const key = ${permission.resource}:${permission.action};
    this.permissions.set(key, permission);
  }
  
  // Check permission with context
  async checkPermission(
    user: User,
    resource: string,
    action: string,
    context: any
  ): Promise<boolean> {
    // Check direct permissions
    const directPerm = ${resource}:${action};
    if (user.permissions.includes(directPerm)) {
      return this.evaluateConditions(directPerm, context);
    }
    
    // Check wildcard permissions
    if (user.permissions.includes(${resource}:*)) {
      return true;
    }
    
    if (user.permissions.includes(':')) {
      return true;
    }
    
    // Check role-based permissions
    for (const role of user.roles) {
      const rolePerm = await this.getRolePermissions(role);
      if (rolePerm.includes(directPerm)) {
        return this.evaluateConditions(directPerm, context);
      }
    }
    
    return false;
  }
  
  private evaluateConditions(
    permissionKey: string,
    context: any
  ): boolean {
    const permission = this.permissions.get(permissionKey);
    if (!permission?.conditions) return true;
    
    for (const condition of permission.conditions) {
      if (!this.evaluateCondition(condition, context)) {
        return false;
      }
    }
    
    return true;
  }
  
  private evaluateCondition(
    condition: PermissionCondition,
    context: any
  ): boolean {
    switch (condition.type) {
      case 'ownership':
        return context.ownerId === context.userId;
        
      case 'time':
        const now = new Date();
        const value = new Date(condition.value);
        switch (condition.operator) {
          case 'gt': return now > value;
          case 'lt': return now < value;
          case 'between': 
            return now >= value[0] && now <= value[1];
        }
        break;
        
      case 'location':
        // Implement location-based checks
        break;
        
      case 'attribute':
        const attrValue = context[condition.field];
        switch (condition.operator) {
          case 'eq': return attrValue === condition.value;
          case 'ne': return attrValue !== condition.value;
          case 'in': return condition.value.includes(attrValue);
        }
        break;
    }
    
    return false;
  }
}

Dynamic Authorization Policies

typescript
class PolicyEngine {
  private policies: Policy[] = [];
  
  interface Policy {
    id: string;
    priority: number;
    effect: 'allow' | 'deny';
    subjects: PolicySubject[];
    resources: PolicyResource[];
    actions: string[];
    conditions?: PolicyCondition[];
  }
  
  interface PolicySubject {
    type: 'user' | 'role' | 'group';
    value: string | string[];
  }
  
  interface PolicyResource {
    type: string;
    pattern: string;  // Supports wildcards
  }
  
  interface PolicyCondition {
    key: string;
    operator: string;
    value: any;
  }
  
  evaluateAccess(
    subject: any,
    resource: string,
    action: string,
    environment: any
  ): 'allow' | 'deny' | 'notapplicable' {
    // Sort policies by priority
    const sortedPolicies = [...this.policies].sort(
      (a, b) => b.priority - a.priority
    );
    
    for (const policy of sortedPolicies) {
      if (this.policyApplies(policy, subject, resource, action, environment)) {
        return policy.effect;
      }
    }
    
    return 'notapplicable';
  }
  
  private policyApplies(
    policy: Policy,
    subject: any,
    resource: string,
    action: string,
    environment: any
  ): boolean {
    // Check subjects
    if (!this.subjectMatches(policy.subjects, subject)) {
      return false;
    }
    
    // Check resources
    if (!this.resourceMatches(policy.resources, resource)) {
      return false;
    }
    
    // Check actions
    if (!policy.actions.includes(action) && 
        !policy.actions.includes('*')) {
      return false;
    }
    
    // Check conditions
    if (policy.conditions) {
      const context = { subject, resource, action, environment };
      if (!this.conditionsMatch(policy.conditions, context)) {
        return false;
      }
    }
    
    return true;
  }
  
  private subjectMatches(
    policySubjects: PolicySubject[],
    subject: any
  ): boolean {
    for (const ps of policySubjects) {
      switch (ps.type) {
        case 'user':
          if (Array.isArray(ps.value)) {
            if (ps.value.includes(subject.id)) return true;
          } else {
            if (ps.value === subject.id) return true;
          }
          break;
          
        case 'role':
          if (Array.isArray(ps.value)) {
            if (ps.value.some(r => subject.roles?.includes(r))) return true;
          } else {
            if (subject.roles?.includes(ps.value)) return true;
          }
          break;
          
        case 'group':
          if (Array.isArray(ps.value)) {
            if (ps.value.some(g => subject.groups?.includes(g))) return true;
          } else {
            if (subject.groups?.includes(ps.value)) return true;
          }
          break;
      }
    }
    
    return false;
  }
  
  private resourceMatches(
    policyResources: PolicyResource[],
    resource: string
  ): boolean {
    for (const pr of policyResources) {
      const pattern = pr.pattern.replace(/\/g, '.');
      const regex = new RegExp(^${pattern}$);
      if (regex.test(resource)) {
        return true;
      }
    }
    
    return false;
  }
}

Security Best Practices

Token Storage

typescript
class SecureTokenStorage {
  // Never store tokens in:
  // - localStorage (XSS vulnerable)
  // - sessionStorage (XSS vulnerable)
  // - Cookies without proper flags
  
  // Secure cookie storage
  setSecureToken(res: Response, token: string): void {
    res.cookie('mcp_token', token, {
      httpOnly: true,      // Prevent XSS access
      secure: true,        // HTTPS only
      sameSite: 'strict',  // CSRF protection
      maxAge: 3600000,     // 1 hour
      path: '/',
      domain: '.example.com'
    });
  }
  
  // For SPAs: Use memory + refresh token
  class TokenManager {
    private accessToken: string | null = null;
    private refreshToken: string | null = null;
    
    setTokens(access: string, refresh: string): void {
      this.accessToken = access;
      // Store refresh token in httpOnly cookie
      document.cookie = mcp_refresh=${refresh};  +
        'HttpOnly; Secure; SameSite=Strict; Path=/api/refresh';
    }
    
    getAccessToken(): string | null {
      return this.accessToken;
    }
    
    async refreshAccessToken(): Promise<string> {
      const response = await fetch('/api/refresh', {
        method: 'POST',
        credentials: 'include'  // Send cookies
      });
      
      const { access_token } = await response.json();
      this.accessToken = access_token;
      return access_token;
    }
  }
}

Secure Communication

typescript
// Always use HTTPS in production
if (process.env.NODE_ENV === 'production') {
  app.use((req, res, next) => {
    if (req.header('x-forwarded-proto') !== 'https') {
      res.redirect(https://${req.header('host')}${req.url});
    } else {
      next();
    }
  });
}

// Add security headers app.use((req, res, next) => { res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Content-Security-Policy', "default-src 'self'"); next(); });

Conclusion

Implementing robust authentication and authorization in MCP servers requires careful consideration of security requirements, user experience, and system architecture. Key takeaways:

  1. Choose the right auth method for your use case
  2. Layer security with defense in depth
  3. Validate everything from the client
  4. Audit all access for security monitoring
  5. Keep tokens secure with proper storage
  6. Update regularly to patch vulnerabilities

Remember that security is an ongoing process. Regular security audits, penetration testing, and staying updated with the latest security practices are essential for maintaining secure MCP deployments.

Code Examples

Complete JWT-Based MCP Servertypescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import jwt from 'jsonwebtoken';
import { z } from 'zod';

// Configuration
const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_ISSUER = 'mcp-server';
const JWT_AUDIENCE = 'mcp-client';

// Token payload schema
const TokenPayloadSchema = z.object({
  sub: z.string(),
  roles: z.array(z.string()),
  permissions: z.array(z.string()),
  exp: z.number(),
  iat: z.number()
});

class AuthenticatedMCPServer {
  private server: Server;
  
  constructor() {
    this.server = new Server(
      {
        name: 'authenticated-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          resources: {},
          tools: {},
        },
      }
    );
    
    this.setupAuthentication();
    this.setupHandlers();
  }
  
  private setupAuthentication() {
    // Add authentication to all requests
    this.server.use(async (request, next) => {
      try {
        // Extract token from environment (for stdio transport)
        const token = process.env.MCP_AUTH_TOKEN;
        
        if (!token) {
          throw new Error('Authentication required');
        }
        
        // Verify JWT
        const payload = jwt.verify(token, JWT_SECRET, {
          issuer: JWT_ISSUER,
          audience: JWT_AUDIENCE
        });
        
        // Validate payload structure
        const validatedPayload = TokenPayloadSchema.parse(payload);
        
        // Attach to request context
        request.context = {
          userId: validatedPayload.sub,
          roles: validatedPayload.roles,
          permissions: validatedPayload.permissions
        };
        
        return next();
      } catch (error) {
        console.error('Authentication failed:', error);
        throw new Error('Invalid authentication token');
      }
    });
  }
  
  private setupHandlers() {
    // List tools with permission filtering
    this.server.setRequestHandler('tools/list', async (request) => {
      const allTools = [
        {
          name: 'read_file',
          description: 'Read file contents',
          requiredPermission: 'files:read'
        },
        {
          name: 'write_file',
          description: 'Write file contents',
          requiredPermission: 'files:write'
        },
        {
          name: 'execute_query',
          description: 'Execute database query',
          requiredPermission: 'database:query'
        }
      ];
      
      // Filter based on user permissions
      const userPermissions = request.context.permissions;
      const availableTools = allTools.filter(tool => 
        userPermissions.includes(tool.requiredPermission) ||
        userPermissions.includes('*')
      );
      
      return {
        tools: availableTools.map(({ requiredPermission, ...tool }) => ({
          ...tool,
          inputSchema: {
            type: 'object',
            properties: {},
            required: []
          }
        }))
      };
    });
    
    // Execute tools with authorization
    this.server.setRequestHandler('tools/call', async (request) => {
      const { name: toolName, arguments: args } = request.params;
      
      // Map tools to required permissions
      const toolPermissions: Record<string, string> = {
        'read_file': 'files:read',
        'write_file': 'files:write',
        'execute_query': 'database:query'
      };
      
      const requiredPermission = toolPermissions[toolName];
      if (!requiredPermission) {
        throw new Error('Unknown tool');
      }
      
      // Check permission
      const userPermissions = request.context.permissions;
      if (!userPermissions.includes(requiredPermission) &&
          !userPermissions.includes('*')) {
        throw new Error(`Permission denied: ${requiredPermission} required`);
      }
      
      // Execute tool based on permissions
      switch (toolName) {
        case 'read_file':
          return this.readFile(args, request.context);
        case 'write_file':
          return this.writeFile(args, request.context);
        case 'execute_query':
          return this.executeQuery(args, request.context);
        default:
          throw new Error('Tool not implemented');
      }
    });
  }
  
  private async readFile(args: any, context: any) {
    // Implementation with context-aware access
    return {
      content: [{
        type: 'text',
        text: `File read by user ${context.userId}`
      }]
    };
  }
  
  private async writeFile(args: any, context: any) {
    // Check additional role-based restrictions
    if (!context.roles.includes('editor') && 
        !context.roles.includes('admin')) {
      throw new Error('Editor role required for write operations');
    }
    
    return {
      content: [{
        type: 'text',
        text: 'File written successfully'
      }]
    };
  }
  
  private async executeQuery(args: any, context: any) {
    // Implement with query restrictions based on roles
    return {
      content: [{
        type: 'text',
        text: 'Query executed'
      }]
    };
  }
  
  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Authenticated MCP Server running');
  }
}

// JWT token generator utility
class TokenGenerator {
  static generateToken(
    userId: string,
    roles: string[],
    permissions: string[]
  ): string {
    const payload = {
      sub: userId,
      roles,
      permissions,
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + 3600 // 1 hour
    };
    
    return jwt.sign(payload, JWT_SECRET, {
      issuer: JWT_ISSUER,
      audience: JWT_AUDIENCE
    });
  }
}

// Start server
const server = new AuthenticatedMCPServer();
server.start().catch(console.error);

// Example: Generate a token for testing
if (process.argv.includes('--generate-token')) {
  const token = TokenGenerator.generateToken(
    'user-123',
    ['editor'],
    ['files:read', 'files:write', 'database:query']
  );
  console.log('Generated token:', token);
  console.log('\nUse with: MCP_AUTH_TOKEN=' + token);
}
OAuth2 MCP Integrationtypescript
import express from 'express';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { HttpServerTransport } from '@modelcontextprotocol/sdk/server/http.js';

// OAuth2 configuration
const OAUTH_CONFIG = {
  authorizationURL: 'https://auth.example.com/oauth/authorize',
  tokenURL: 'https://auth.example.com/oauth/token',
  clientId: process.env.OAUTH_CLIENT_ID!,
  clientSecret: process.env.OAUTH_CLIENT_SECRET!,
  redirectUri: 'http://localhost:3000/callback',
  scope: ['mcp:read', 'mcp:write']
};

class OAuth2MCPServer {
  private app: express.Application;
  private mcpServer: Server;
  private sessions: Map<string, Session> = new Map();
  
  interface Session {
    accessToken: string;
    refreshToken: string;
    expiresAt: Date;
    userId: string;
    scope: string[];
  }
  
  constructor() {
    this.app = express();
    this.setupOAuth2Routes();
    this.setupMCPServer();
  }
  
  private setupOAuth2Routes() {
    // Initiate OAuth2 flow
    this.app.get('/auth', (req, res) => {
      const state = crypto.randomBytes(16).toString('hex');
      req.session.oauthState = state;
      
      const authUrl = new URL(OAUTH_CONFIG.authorizationURL);
      authUrl.searchParams.append('response_type', 'code');
      authUrl.searchParams.append('client_id', OAUTH_CONFIG.clientId);
      authUrl.searchParams.append('redirect_uri', OAUTH_CONFIG.redirectUri);
      authUrl.searchParams.append('scope', OAUTH_CONFIG.scope.join(' '));
      authUrl.searchParams.append('state', state);
      
      res.redirect(authUrl.toString());
    });
    
    // OAuth2 callback
    this.app.get('/callback', async (req, res) => {
      const { code, state } = req.query;
      
      // Verify state
      if (state !== req.session.oauthState) {
        return res.status(400).send('Invalid state');
      }
      
      try {
        // Exchange code for token
        const tokenResponse = await fetch(OAUTH_CONFIG.tokenURL, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': `Basic ${Buffer.from(
              `${OAUTH_CONFIG.clientId}:${OAUTH_CONFIG.clientSecret}`
            ).toString('base64')}`
          },
          body: new URLSearchParams({
            grant_type: 'authorization_code',
            code: code as string,
            redirect_uri: OAUTH_CONFIG.redirectUri
          })
        });
        
        const tokens = await tokenResponse.json();
        
        // Decode token to get user info
        const payload = jwt.decode(tokens.access_token) as any;
        
        // Create session
        const sessionId = crypto.randomUUID();
        this.sessions.set(sessionId, {
          accessToken: tokens.access_token,
          refreshToken: tokens.refresh_token,
          expiresAt: new Date(Date.now() + tokens.expires_in * 1000),
          userId: payload.sub,
          scope: tokens.scope.split(' ')
        });
        
        // Set session cookie
        res.cookie('mcp_session', sessionId, {
          httpOnly: true,
          secure: true,
          sameSite: 'strict'
        });
        
        res.redirect('/mcp');
        
      } catch (error) {
        console.error('OAuth2 error:', error);
        res.status(500).send('Authentication failed');
      }
    });
    
    // MCP endpoint with OAuth2 protection
    this.app.use('/mcp', this.requireAuth.bind(this));
  }
  
  private async requireAuth(
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) {
    const sessionId = req.cookies.mcp_session;
    if (!sessionId) {
      return res.redirect('/auth');
    }
    
    const session = this.sessions.get(sessionId);
    if (!session) {
      return res.redirect('/auth');
    }
    
    // Check token expiration
    if (session.expiresAt < new Date()) {
      // Try to refresh
      try {
        const newSession = await this.refreshToken(session);
        this.sessions.set(sessionId, newSession);
        req.session = newSession;
      } catch {
        return res.redirect('/auth');
      }
    } else {
      req.session = session;
    }
    
    next();
  }
  
  private async refreshToken(session: Session): Promise<Session> {
    const response = await fetch(OAUTH_CONFIG.tokenURL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': `Basic ${Buffer.from(
          `${OAUTH_CONFIG.clientId}:${OAUTH_CONFIG.clientSecret}`
        ).toString('base64')}`
      },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: session.refreshToken
      })
    });
    
    const tokens = await response.json();
    
    return {
      ...session,
      accessToken: tokens.access_token,
      expiresAt: new Date(Date.now() + tokens.expires_in * 1000)
    };
  }
  
  private setupMCPServer() {
    this.mcpServer = new Server(
      {
        name: 'oauth2-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          resources: {},
          tools: {},
        },
      }
    );
    
    // Add OAuth2 context to requests
    this.mcpServer.use(async (request, next) => {
      // Get session from HTTP transport
      const session = request.transport.session;
      if (!session) {
        throw new Error('No OAuth2 session');
      }
      
      request.context = {
        userId: session.userId,
        scope: session.scope,
        accessToken: session.accessToken
      };
      
      return next();
    });
    
    // Setup handlers with scope checking
    this.mcpServer.setRequestHandler('tools/call', async (request) => {
      const { name: toolName } = request.params;
      const requiredScope = `mcp:${toolName}`;
      
      if (!request.context.scope.includes(requiredScope) &&
          !request.context.scope.includes('mcp:*')) {
        throw new Error(`OAuth2 scope required: ${requiredScope}`);
      }
      
      // Execute tool
      return this.executeTool(toolName, request.params.arguments);
    });
    
    // Mount MCP on Express
    this.app.use('/mcp', (req, res) => {
      const transport = new HttpServerTransport(req, res);
      transport.session = req.session; // Pass session to transport
      this.mcpServer.connect(transport);
    });
  }
  
  private async executeTool(name: string, args: any) {
    // Tool implementation
    return {
      content: [{
        type: 'text',
        text: `Executed ${name} with OAuth2 authentication`
      }]
    };
  }
  
  start(port: number = 3000) {
    this.app.listen(port, () => {
      console.log(`OAuth2 MCP Server running on port ${port}`);
      console.log(`Visit http://localhost:${port}/auth to authenticate`);
    });
  }
}

// Start server
const server = new OAuth2MCPServer();
server.start();
API Key Authentication with Rate Limitingpython
import hashlib
import secrets
import time
from datetime import datetime, timedelta
from typing import Dict, Optional, List
from dataclasses import dataclass, field
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server

@dataclass
class APIKey:
    key_id: str
    hashed_key: str
    name: str
    created_at: datetime
    last_used: Optional[datetime] = None
    expires_at: Optional[datetime] = None
    permissions: List[str] = field(default_factory=list)
    rate_limit: int = 100  # requests per hour
    
@dataclass
class RateLimitEntry:
    requests: List[datetime]
    
    def clean_old_requests(self, window_seconds: int = 3600):
        cutoff = datetime.now() - timedelta(seconds=window_seconds)
        self.requests = [req for req in self.requests if req > cutoff]
    
    def add_request(self) -> bool:
        self.requests.append(datetime.now())
        return True

class APIKeyAuthenticator:
    def __init__(self):
        self.keys: Dict[str, APIKey] = {}
        self.rate_limits: Dict[str, RateLimitEntry] = {}
        
    def generate_api_key(self, name: str, permissions: List[str]) -> tuple[str, str]:
        """Generate a new API key and return (key, key_id)"""
        # Generate secure random key
        raw_key = secrets.token_urlsafe(32)
        key_id = secrets.token_hex(8)
        
        # Hash the key for storage
        hashed_key = hashlib.sha256(raw_key.encode()).hexdigest()
        
        # Create key object
        api_key = APIKey(
            key_id=key_id,
            hashed_key=hashed_key,
            name=name,
            created_at=datetime.now(),
            permissions=permissions
        )
        
        self.keys[key_id] = api_key
        
        # Return the actual key (only shown once)
        full_key = f"mcp_{key_id}_{raw_key}"
        return full_key, key_id
    
    def validate_api_key(self, api_key: str) -> Optional[APIKey]:
        """Validate an API key and return the key data if valid"""
        # Parse the key format
        if not api_key.startswith("mcp_"):
            return None
            
        parts = api_key.split("_", 2)
        if len(parts) != 3:
            return None
            
        _, key_id, raw_key = parts
        
        # Look up the key
        key_data = self.keys.get(key_id)
        if not key_data:
            return None
            
        # Check expiration
        if key_data.expires_at and datetime.now() > key_data.expires_at:
            return None
            
        # Verify the key
        hashed = hashlib.sha256(raw_key.encode()).hexdigest()
        if hashed != key_data.hashed_key:
            return None
            
        # Update last used
        key_data.last_used = datetime.now()
        
        return key_data
    
    def check_rate_limit(self, key_id: str, limit: int) -> tuple[bool, int]:
        """Check if request is within rate limit. Returns (allowed, remaining)"""
        if key_id not in self.rate_limits:
            self.rate_limits[key_id] = RateLimitEntry(requests=[])
            
        rate_limit = self.rate_limits[key_id]
        rate_limit.clean_old_requests()
        
        current_count = len(rate_limit.requests)
        if current_count >= limit:
            return False, 0
            
        rate_limit.add_request()
        return True, limit - current_count - 1
    
    def has_permission(self, key_data: APIKey, permission: str) -> bool:
        """Check if API key has a specific permission"""
        # Check exact match
        if permission in key_data.permissions:
            return True
            
        # Check wildcard permissions
        for perm in key_data.permissions:
            if perm.endswith('*'):
                prefix = perm[:-1]
                if permission.startswith(prefix):
                    return True
                    
        return False

class AuthenticatedMCPServer:
    def __init__(self):
        self.server = Server("authenticated-mcp-server")
        self.auth = APIKeyAuthenticator()
        self._setup_auth_middleware()
        self._setup_handlers()
        
    def _setup_auth_middleware(self):
        """Add authentication to all requests"""
        @self.server.request_middleware
        async def authenticate(request, next_handler):
            # Extract API key from environment
            api_key = os.environ.get('MCP_API_KEY')
            
            if not api_key:
                raise ValueError("API key required")
                
            # Validate key
            key_data = self.auth.validate_api_key(api_key)
            if not key_data:
                raise ValueError("Invalid API key")
                
            # Check rate limit
            allowed, remaining = self.auth.check_rate_limit(
                key_data.key_id, 
                key_data.rate_limit
            )
            
            if not allowed:
                raise ValueError("Rate limit exceeded")
                
            # Attach auth context to request
            request.auth_context = {
                'key_id': key_data.key_id,
                'permissions': key_data.permissions,
                'rate_limit_remaining': remaining
            }
            
            # Continue to handler
            return await next_handler(request)
    
    def _setup_handlers(self):
        @self.server.list_tools()
        async def handle_list_tools(request) -> List[dict]:
            # Define all available tools
            all_tools = [
                {
                    'name': 'read_data',
                    'description': 'Read data from storage',
                    'required_permission': 'data:read',
                    'inputSchema': {
                        'type': 'object',
                        'properties': {
                            'key': {'type': 'string'}
                        },
                        'required': ['key']
                    }
                },
                {
                    'name': 'write_data',
                    'description': 'Write data to storage',
                    'required_permission': 'data:write',
                    'inputSchema': {
                        'type': 'object',
                        'properties': {
                            'key': {'type': 'string'},
                            'value': {'type': 'string'}
                        },
                        'required': ['key', 'value']
                    }
                },
                {
                    'name': 'admin_action',
                    'description': 'Perform admin action',
                    'required_permission': 'admin:*',
                    'inputSchema': {
                        'type': 'object',
                        'properties': {
                            'action': {'type': 'string'}
                        },
                        'required': ['action']
                    }
                }
            ]
            
            # Filter tools based on permissions
            user_permissions = request.auth_context['permissions']
            available_tools = []
            
            for tool in all_tools:
                required_perm = tool.pop('required_permission')
                
                # Check if user has permission
                has_perm = any(
                    self._permission_matches(perm, required_perm)
                    for perm in user_permissions
                )
                
                if has_perm:
                    available_tools.append(tool)
                    
            return available_tools
        
        @self.server.call_tool()
        async def handle_call_tool(name: str, arguments: dict, request) -> List[dict]:
            # Map tools to required permissions
            tool_permissions = {
                'read_data': 'data:read',
                'write_data': 'data:write',
                'admin_action': 'admin:*'
            }
            
            required_permission = tool_permissions.get(name)
            if not required_permission:
                raise ValueError(f"Unknown tool: {name}")
                
            # Check permission
            key_data = self.auth.keys[request.auth_context['key_id']]
            if not self.auth.has_permission(key_data, required_permission):
                raise ValueError(f"Permission denied: {required_permission} required")
                
            # Execute tool
            if name == 'read_data':
                return [{
                    'type': 'text',
                    'text': f"Read data for key: {arguments['key']}"
                }]
            elif name == 'write_data':
                return [{
                    'type': 'text',
                    'text': f"Wrote data for key: {arguments['key']}"
                }]
            elif name == 'admin_action':
                return [{
                    'type': 'text',
                    'text': f"Admin action performed: {arguments['action']}"
                }]
                
    def _permission_matches(self, user_perm: str, required_perm: str) -> bool:
        """Check if user permission matches required permission"""
        if user_perm == required_perm:
            return True
            
        if user_perm.endswith('*'):
            prefix = user_perm[:-1]
            return required_perm.startswith(prefix)
            
        return False
    
    async def generate_test_key(self):
        """Generate a test API key"""
        key, key_id = self.auth.generate_api_key(
            name="Test Key",
            permissions=['data:read', 'data:write']
        )
        print(f"Generated API Key: {key}")
        print(f"Key ID: {key_id}")
        print(f"\nSet environment variable:")
        print(f"export MCP_API_KEY='{key}'")
        
    async def run(self):
        """Run the MCP server"""
        async with stdio_server() as (read_stream, write_stream):
            await self.server.run(
                read_stream,
                write_stream,
                InitializationOptions(
                    server_name="authenticated-mcp-server",
                    server_version="1.0.0",
                    capabilities=self.server.get_capabilities()
                )
            )

if __name__ == "__main__":
    import sys
    import os
    
    server = AuthenticatedMCPServer()
    
    if "--generate-key" in sys.argv:
        asyncio.run(server.generate_test_key())
    else:
        asyncio.run(server.run())
mTLS Certificate Authenticationtypescript
import tls from 'tls';
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';

interface ClientCertificate {
  fingerprint: string;
  subject: {
    CN: string;  // Common Name
    O?: string;  // Organization
    OU?: string; // Organizational Unit
  };
  issuer: {
    CN: string;
    O?: string;
  };
  valid_from: Date;
  valid_to: Date;
  serialNumber: string;
  permissions: string[];
}

class CertificateManager {
  private trustedCerts: Map<string, ClientCertificate> = new Map();
  private revokedCerts: Set<string> = new Set();
  
  constructor(private caPath: string) {
    this.loadTrustedCertificates();
  }
  
  private loadTrustedCertificates() {
    // Load trusted client certificates from a directory
    const certsDir = path.join(this.caPath, 'clients');
    
    if (fs.existsSync(certsDir)) {
      const files = fs.readdirSync(certsDir);
      
      for (const file of files) {
        if (file.endsWith('.json')) {
          const certInfo = JSON.parse(
            fs.readFileSync(path.join(certsDir, file), 'utf8')
          );
          
          this.trustedCerts.set(certInfo.fingerprint, certInfo);
        }
      }
    }
  }
  
  validateCertificate(cert: tls.PeerCertificate): ClientCertificate | null {
    // Generate fingerprint
    const fingerprint = cert.fingerprint.replace(/:/g, '').toLowerCase();
    
    // Check if revoked
    if (this.revokedCerts.has(fingerprint)) {
      console.error(`Certificate revoked: ${fingerprint}`);
      return null;
    }
    
    // Check if trusted
    const trustedCert = this.trustedCerts.get(fingerprint);
    if (!trustedCert) {
      console.error(`Unknown certificate: ${fingerprint}`);
      return null;
    }
    
    // Validate certificate details
    const now = new Date();
    const validFrom = new Date(cert.valid_from);
    const validTo = new Date(cert.valid_to);
    
    if (now < validFrom || now > validTo) {
      console.error(`Certificate expired or not yet valid: ${fingerprint}`);
      return null;
    }
    
    // Verify subject matches
    if (cert.subject.CN !== trustedCert.subject.CN) {
      console.error(`Subject mismatch: ${cert.subject.CN}`);
      return null;
    }
    
    return trustedCert;
  }
  
  revokeCertificate(fingerprint: string) {
    this.revokedCerts.add(fingerprint);
    // In production, persist to CRL (Certificate Revocation List)
  }
}

class MTLSMCPServer {
  private tlsServer: tls.Server;
  private certManager: CertificateManager;
  private activeSessions: Map<string, tls.TLSSocket> = new Map();
  
  constructor(private config: {
    port: number;
    serverKey: string;
    serverCert: string;
    caCert: string;
    caPath: string;
  }) {
    this.certManager = new CertificateManager(config.caPath);
    this.createTLSServer();
  }
  
  private createTLSServer() {
    const tlsOptions: tls.TlsOptions = {
      key: fs.readFileSync(this.config.serverKey),
      cert: fs.readFileSync(this.config.serverCert),
      ca: fs.readFileSync(this.config.caCert),
      
      // Require and verify client certificates
      requestCert: true,
      rejectUnauthorized: true,
      
      // TLS configuration
      minVersion: 'TLSv1.2',
      ciphers: [
        'ECDHE-RSA-AES128-GCM-SHA256',
        'ECDHE-RSA-AES256-GCM-SHA384',
        'ECDHE-RSA-AES256-SHA384'
      ].join(':'),
      
      // Honor cipher order
      honorCipherOrder: true,
      
      // Session resumption
      sessionTimeout: 300  // 5 minutes
    };
    
    this.tlsServer = tls.createServer(tlsOptions);
    
    // Handle new connections
    this.tlsServer.on('secureConnection', (socket) => {
      this.handleSecureConnection(socket);
    });
    
    // Handle TLS errors
    this.tlsServer.on('tlsClientError', (err, socket) => {
      console.error('TLS client error:', err);
      if (socket.writable) {
        socket.end();
      }
    });
  }
  
  private handleSecureConnection(socket: tls.TLSSocket) {
    // Get and validate client certificate
    const peerCert = socket.getPeerCertificate();
    
    if (!peerCert || Object.keys(peerCert).length === 0) {
      console.error('No client certificate provided');
      socket.end();
      return;
    }
    
    const clientCert = this.certManager.validateCertificate(peerCert);
    if (!clientCert) {
      console.error('Invalid client certificate');
      socket.end();
      return;
    }
    
    console.log(`Client connected: ${clientCert.subject.CN}`);
    
    // Store session
    const sessionId = crypto.randomUUID();
    this.activeSessions.set(sessionId, socket);
    
    // Create MCP server for this connection
    const mcpServer = new Server(
      {
        name: 'mtls-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          resources: {},
          tools: {},
        },
      }
    );
    
    // Add certificate context to all requests
    mcpServer.use(async (request, next) => {
      request.context = {
        clientCert,
        sessionId,
        tlsVersion: socket.getProtocol(),
        cipher: socket.getCipher()
      };
      return next();
    });
    
    // Setup handlers with certificate-based authorization
    mcpServer.setRequestHandler('tools/list', async (request) => {
      const permissions = request.context.clientCert.permissions;
      
      // Return tools based on certificate permissions
      const allTools = [
        { name: 'secure_read', permission: 'read' },
        { name: 'secure_write', permission: 'write' },
        { name: 'secure_admin', permission: 'admin' }
      ];
      
      const availableTools = allTools
        .filter(tool => permissions.includes(tool.permission))
        .map(({ permission, ...tool }) => ({
          ...tool,
          inputSchema: { type: 'object', properties: {} }
        }));
      
      return { tools: availableTools };
    });
    
    mcpServer.setRequestHandler('tools/call', async (request) => {
      const { name } = request.params;
      const cert = request.context.clientCert;
      
      // Log all tool calls for audit
      console.log(`Tool call: ${name} by ${cert.subject.CN}`);
      
      // Check permissions
      const toolPermissions: Record<string, string> = {
        'secure_read': 'read',
        'secure_write': 'write',
        'secure_admin': 'admin'
      };
      
      const required = toolPermissions[name];
      if (!required || !cert.permissions.includes(required)) {
        throw new Error('Permission denied');
      }
      
      return {
        content: [{
          type: 'text',
          text: `Executed ${name} with mTLS authentication`
        }]
      };
    });
    
    // Create transport over TLS socket
    const transport = new TLSTransport(socket);
    mcpServer.connect(transport);
    
    // Cleanup on disconnect
    socket.on('close', () => {
      console.log(`Client disconnected: ${clientCert.subject.CN}`);
      this.activeSessions.delete(sessionId);
    });
  }
  
  start() {
    this.tlsServer.listen(this.config.port, () => {
      console.log(`mTLS MCP Server listening on port ${this.config.port}`);
      console.log('Requiring client certificates for authentication');
    });
  }
  
  // Certificate generation utility
  static async generateClientCertificate(
    clientName: string,
    permissions: string[],
    caKeyPath: string,
    caCertPath: string,
    outputDir: string
  ) {
    const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
      modulusLength: 2048,
    });
    
    // Generate certificate signing request (CSR)
    // In production, use a proper PKI library like node-forge
    
    const certInfo = {
      subject: {
        CN: clientName,
        O: 'MCP Client',
        OU: permissions.join(',')
      },
      permissions,
      valid_from: new Date(),
      valid_to: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
      serialNumber: crypto.randomBytes(8).toString('hex')
    };
    
    // Save private key
    fs.writeFileSync(
      path.join(outputDir, `${clientName}-key.pem`),
      privateKey.export({ type: 'pkcs1', format: 'pem' })
    );
    
    // In production: Generate and sign certificate with CA
    // Save certificate info for server validation
    fs.writeFileSync(
      path.join(outputDir, `${clientName}.json`),
      JSON.stringify(certInfo, null, 2)
    );
    
    console.log(`Generated certificate for ${clientName}`);
    console.log(`Permissions: ${permissions.join(', ')}`);
  }
}

// Custom TLS transport for MCP
class TLSTransport {
  constructor(private socket: tls.TLSSocket) {}
  
  async read(): Promise<string> {
    return new Promise((resolve, reject) => {
      this.socket.once('data', (data) => {
        resolve(data.toString());
      });
      this.socket.once('error', reject);
    });
  }
  
  async write(data: string): Promise<void> {
    return new Promise((resolve, reject) => {
      this.socket.write(data, (err) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }
  
  close(): void {
    this.socket.end();
  }
}

// Start server
const server = new MTLSMCPServer({
  port: 8443,
  serverKey: './certs/server-key.pem',
  serverCert: './certs/server-cert.pem',
  caCert: './certs/ca-cert.pem',
  caPath: './certs'
});

server.start();

// Generate client certificate if requested
if (process.argv.includes('--generate-client')) {
  const clientName = process.argv[process.argv.indexOf('--generate-client') + 1];
  const permissions = ['read', 'write'];
  
  MTLSMCPServer.generateClientCertificate(
    clientName,
    permissions,
    './certs/ca-key.pem',
    './certs/ca-cert.pem',
    './certs/clients'
  );
}
Session-Based Authentication with Redistypescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { HttpServerTransport } from '@modelcontextprotocol/sdk/server/http.js';
import express from 'express';
import session from 'express-session';
import RedisStore from 'connect-redis';
import Redis from 'ioredis';
import bcrypt from 'bcrypt';
import crypto from 'crypto';

// Session configuration
interface UserSession {
  userId: string;
  username: string;
  roles: string[];
  permissions: string[];
  loginTime: Date;
  lastActivity: Date;
  ipAddress: string;
  userAgent: string;
}

class SessionAuthMCPServer {
  private app: express.Application;
  private redis: Redis;
  private mcpServer: Server;
  private sessionStore: RedisStore;
  
  constructor() {
    this.redis = new Redis({
      host: process.env.REDIS_HOST || 'localhost',
      port: parseInt(process.env.REDIS_PORT || '6379'),
      password: process.env.REDIS_PASSWORD,
      db: 0
    });
    
    this.setupExpress();
    this.setupMCPServer();
  }
  
  private setupExpress() {
    this.app = express();
    this.app.use(express.json());
    
    // Session configuration
    this.sessionStore = new RedisStore({
      client: this.redis,
      prefix: 'mcp:sess:',
      ttl: 3600  // 1 hour
    });
    
    this.app.use(session({
      store: this.sessionStore,
      secret: process.env.SESSION_SECRET || crypto.randomBytes(32).toString('hex'),
      resave: false,
      saveUninitialized: false,
      rolling: true,  // Reset expiry on activity
      cookie: {
        secure: process.env.NODE_ENV === 'production',
        httpOnly: true,
        maxAge: 3600000,  // 1 hour
        sameSite: 'strict'
      },
      name: 'mcp.sid'
    }));
    
    // Authentication routes
    this.app.post('/auth/login', this.handleLogin.bind(this));
    this.app.post('/auth/logout', this.handleLogout.bind(this));
    this.app.get('/auth/session', this.handleGetSession.bind(this));
    this.app.post('/auth/refresh', this.handleRefreshSession.bind(this));
    
    // MCP endpoint (protected)
    this.app.use('/mcp', this.requireAuth.bind(this));
  }
  
  private async handleLogin(req: express.Request, res: express.Response) {
    const { username, password, mfaToken } = req.body;
    
    try {
      // Validate credentials
      const user = await this.validateCredentials(username, password);
      if (!user) {
        return res.status(401).json({ error: 'Invalid credentials' });
      }
      
      // Check MFA if enabled
      if (user.mfaEnabled && !await this.validateMFA(user.id, mfaToken)) {
        return res.status(401).json({ error: 'Invalid MFA token' });
      }
      
      // Create session
      const userSession: UserSession = {
        userId: user.id,
        username: user.username,
        roles: user.roles,
        permissions: user.permissions,
        loginTime: new Date(),
        lastActivity: new Date(),
        ipAddress: req.ip,
        userAgent: req.get('user-agent') || ''
      };
      
      req.session.user = userSession;
      
      // Track active sessions
      await this.redis.sadd(`user:${user.id}:sessions`, req.sessionID);
      
      // Set session metadata
      await this.redis.hset(
        `session:${req.sessionID}:meta`,
        {
          userId: user.id,
          loginTime: userSession.loginTime.toISOString(),
          ipAddress: req.ip,
          userAgent: userSession.userAgent
        }
      );
      
      res.json({
        success: true,
        user: {
          id: user.id,
          username: user.username,
          roles: user.roles
        },
        sessionId: req.sessionID
      });
      
    } catch (error) {
      console.error('Login error:', error);
      res.status(500).json({ error: 'Login failed' });
    }
  }
  
  private async handleLogout(req: express.Request, res: express.Response) {
    if (req.session.user) {
      const userId = req.session.user.userId;
      
      // Remove from active sessions
      await this.redis.srem(`user:${userId}:sessions`, req.sessionID);
      
      // Clean up session metadata
      await this.redis.del(`session:${req.sessionID}:meta`);
      
      // Destroy session
      req.session.destroy((err) => {
        if (err) {
          console.error('Session destruction error:', err);
        }
      });
    }
    
    res.json({ success: true });
  }
  
  private async handleGetSession(req: express.Request, res: express.Response) {
    if (!req.session.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    // Get session info
    const sessionInfo = {
      user: {
        id: req.session.user.userId,
        username: req.session.user.username,
        roles: req.session.user.roles
      },
      loginTime: req.session.user.loginTime,
      lastActivity: req.session.user.lastActivity,
      expiresIn: req.session.cookie.maxAge
    };
    
    res.json(sessionInfo);
  }
  
  private async handleRefreshSession(req: express.Request, res: express.Response) {
    if (!req.session.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    // Update last activity
    req.session.user.lastActivity = new Date();
    
    // Extend session
    req.session.touch();
    
    res.json({ 
      success: true,
      expiresIn: req.session.cookie.maxAge
    });
  }
  
  private async requireAuth(
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) {
    if (!req.session.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    // Check session validity
    const sessionMeta = await this.redis.hgetall(
      `session:${req.sessionID}:meta`
    );
    
    if (!sessionMeta || sessionMeta.userId !== req.session.user.userId) {
      return res.status(401).json({ error: 'Invalid session' });
    }
    
    // Check for IP address change (optional security measure)
    if (sessionMeta.ipAddress !== req.ip) {
      console.warn(`IP address changed for session ${req.sessionID}`);
      // Optionally invalidate session on IP change
    }
    
    // Update last activity
    req.session.user.lastActivity = new Date();
    
    next();
  }
  
  private setupMCPServer() {
    this.mcpServer = new Server(
      {
        name: 'session-auth-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          resources: {},
          tools: {},
        },
      }
    );
    
    // Add session context to MCP requests
    this.mcpServer.use(async (request, next) => {
      // Get session from transport
      const session = request.transport.session;
      if (!session?.user) {
        throw new Error('No authenticated session');
      }
      
      request.context = {
        user: session.user,
        sessionId: request.transport.sessionId
      };
      
      return next();
    });
    
    // Permission-based tool access
    this.mcpServer.setRequestHandler('tools/list', async (request) => {
      const userPermissions = request.context.user.permissions;
      
      const allTools = [
        { name: 'user_profile', permission: 'profile:read' },
        { name: 'update_profile', permission: 'profile:write' },
        { name: 'admin_users', permission: 'admin:users' }
      ];
      
      const availableTools = allTools
        .filter(tool => 
          userPermissions.includes(tool.permission) ||
          userPermissions.includes('*')
        )
        .map(({ permission, ...tool }) => ({
          ...tool,
          inputSchema: { type: 'object', properties: {} }
        }));
      
      return { tools: availableTools };
    });
    
    // Mount MCP on Express
    this.app.use('/mcp', async (req, res) => {
      const transport = new HttpServerTransport(req, res);
      
      // Pass session to transport
      transport.session = req.session;
      transport.sessionId = req.sessionID;
      
      await this.mcpServer.connect(transport);
    });
  }
  
  // User management functions
  private users = new Map<string, any>();
  
  private async validateCredentials(username: string, password: string) {
    // In production, fetch from database
    const user = this.users.get(username);
    if (!user) return null;
    
    const valid = await bcrypt.compare(password, user.passwordHash);
    if (!valid) return null;
    
    return user;
  }
  
  private async validateMFA(userId: string, token: string): Promise<boolean> {
    // Implement TOTP validation
    return true;
  }
  
  async createUser(username: string, password: string, roles: string[] = []) {
    const passwordHash = await bcrypt.hash(password, 10);
    
    const user = {
      id: crypto.randomUUID(),
      username,
      passwordHash,
      roles,
      permissions: this.getRolePermissions(roles),
      mfaEnabled: false,
      createdAt: new Date()
    };
    
    this.users.set(username, user);
    return user;
  }
  
  private getRolePermissions(roles: string[]): string[] {
    const rolePermissions: Record<string, string[]> = {
      'user': ['profile:read', 'profile:write'],
      'admin': ['*']
    };
    
    const permissions = new Set<string>();
    
    for (const role of roles) {
      const perms = rolePermissions[role] || [];
      perms.forEach(p => permissions.add(p));
    }
    
    return Array.from(permissions);
  }
  
  // Session management utilities
  async getActiveSessions(userId: string) {
    const sessionIds = await this.redis.smembers(`user:${userId}:sessions`);
    const sessions = [];
    
    for (const sessionId of sessionIds) {
      const meta = await this.redis.hgetall(`session:${sessionId}:meta`);
      if (meta) {
        sessions.push({
          sessionId,
          ...meta
        });
      }
    }
    
    return sessions;
  }
  
  async invalidateSession(sessionId: string) {
    // Get session data
    const session = await this.sessionStore.get(sessionId);
    if (session?.user) {
      await this.redis.srem(`user:${session.user.userId}:sessions`, sessionId);
    }
    
    // Destroy session
    await this.sessionStore.destroy(sessionId);
    await this.redis.del(`session:${sessionId}:meta`);
  }
  
  async invalidateAllUserSessions(userId: string) {
    const sessionIds = await this.redis.smembers(`user:${userId}:sessions`);
    
    for (const sessionId of sessionIds) {
      await this.invalidateSession(sessionId);
    }
    
    await this.redis.del(`user:${userId}:sessions`);
  }
  
  start(port: number = 3000) {
    this.app.listen(port, () => {
      console.log(`Session-based MCP Server running on port ${port}`);
      console.log(`Login at http://localhost:${port}/auth/login`);
    });
  }
}

// Start server
const server = new SessionAuthMCPServer();

// Create test users
server.createUser('alice', 'password123', ['user']);
server.createUser('admin', 'admin123', ['admin']);

server.start();

Key Takeaways

    Next Steps