From 8182aea29d274b4554f2ecea9829863bd15f79ad Mon Sep 17 00:00:00 2001 From: Emanuel Jesus Leiva Navarro Date: Sat, 6 Sep 2025 18:45:51 -0400 Subject: [PATCH] add: authorization token --- .env.example | 6 +++--- README.md | 13 +++++++++++++ src/config/index.ts | 1 + src/middleware/auth.ts | 29 +++++++++++++++++++++++++++++ src/server.ts | 5 +++-- 5 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 src/middleware/auth.ts diff --git a/.env.example b/.env.example index 90a5158..6441482 100644 --- a/.env.example +++ b/.env.example @@ -14,12 +14,12 @@ MCP_TIMEOUT=180000 LOG_LEVEL=info # CORS Configuration -<<<<<<< HEAD # Specifies the allowed origin for CORS. Use '*' for all origins or a specific URL (e.g., https://example.com). -======= ->>>>>>> origin/main CORS_ALLOW_ORIGIN=* +# Authentication +API_KEY=your-secret-api-key + # Multiplexing SSE Transport Configuration # Set to 'true' to enable multiplexing SSE transport (handles multiple clients with a single transport) # Set to 'false' to use individual SSE transport for each client (legacy behavior) diff --git a/README.md b/README.md index 3b25745..2ea3c31 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,16 @@ A Model Context Protocol (MCP) server that provides GitHub API integration throu - Configurable timeouts, CORS settings, and logging levels - Robust error handling and detailed logging +## Authentication + +This server uses API key authentication to protect its endpoints. All requests to `/mcp` and `/messages` must include an `Authorization` header with a valid bearer token. + +Example: `Authorization: Bearer your-secret-api-key` + +To set up authentication, add the following variable to your `.env` file: + +`API_KEY=your-secret-api-key` + ## Project Structure The project follows a modular, feature-based architecture. All source code is located in the `src` directory. @@ -66,6 +76,9 @@ The project follows a modular, feature-based architecture. All source code is lo # Generate a token at https://github.com/settings/tokens GITHUB_TOKEN=your_github_token_here + # Authentication + API_KEY=your-secret-api-key + # Server Port Configuration MCP_SSE_PORT=3200 diff --git a/src/config/index.ts b/src/config/index.ts index 8a50a35..105eb78 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -23,6 +23,7 @@ export const config = { sseTimeout: process.env.SSE_TIMEOUT ? parseInt(process.env.SSE_TIMEOUT, 10) : 1800000, corsAllowOrigin: process.env.CORS_ALLOW_ORIGIN ?? '', useMultiplexing: process.env.USE_MULTIPLEXING_SSE === 'true', + apiKey: process.env.API_KEY ?? '', // Rate Limiting Configuration rateLimitWindowMs: process.env.RATE_LIMIT_WINDOW_MS ? parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) : 15 * 60 * 1000, // 15 minutes rateLimitMaxRequests: process.env.RATE_LIMIT_MAX_REQUESTS ? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10) : 100, // 100 requests diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts new file mode 100644 index 0000000..66bc1b1 --- /dev/null +++ b/src/middleware/auth.ts @@ -0,0 +1,29 @@ +import { Request, Response, NextFunction } from 'express'; +import { config } from '#config/index'; +import { logger } from '#core/logger'; + +export const authenticate = (req: Request, res: Response, next: NextFunction) => { + const authHeader = req.headers['authorization']; + + if (!authHeader) { + logger.warn('Authentication failed: No Authorization header'); + return res.status(401).json({ error: 'Authorization header required' }); + } + + const tokenParts = authHeader.split(' '); + + if (tokenParts.length !== 2 || tokenParts[0].toLowerCase() !== 'bearer') { + logger.warn('Authentication failed: Invalid token format'); + return res.status(401).json({ error: 'Invalid token format. Expected "Bearer "' }); + } + + const token = tokenParts[1]; + + if (token !== config.apiKey) { + logger.warn('Authentication failed: Invalid API key'); + return res.status(401).json({ error: 'Invalid API key' }); + } + + logger.info('Authentication successful'); + next(); +}; diff --git a/src/server.ts b/src/server.ts index fe7439f..2bf3fb4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -15,6 +15,7 @@ import { config } from '#config/index'; import { logger } from '#core/logger'; import rateLimit from 'express-rate-limit'; import express, { Request, Response, NextFunction } from 'express'; +import { authenticate } from './middleware/auth.js'; const generalLimiter = rateLimit({ windowMs: config.rateLimitWindowMs, @@ -183,7 +184,7 @@ export function createServer(mcpServer: McpServer, port: number): http.Server { }); } - app.all('/mcp', createUserLimiter(), (req: express.Request, res: express.Response, next: express.NextFunction) => { + app.all('/mcp', authenticate, createUserLimiter(), (req: express.Request, res: express.Response, next: express.NextFunction) => { logger.debug(`New Streamable HTTP request: ${req.method} ${req.url}`); if (req.method === 'OPTIONS') { res.status(200).end(); @@ -312,7 +313,7 @@ export function createServer(mcpServer: McpServer, port: number): http.Server { } }); - app.post('/messages', messageLimiter, (req: express.Request, res: express.Response) => { + app.post('/messages', authenticate, messageLimiter, (req: express.Request, res: express.Response) => { try { const sessionIdSchema = z.string().uuid(); const sessionId = sessionIdSchema.parse(req.query.sessionId);