feat: enhanced authentication logging for better debugging (fixes #22, #16)

- Added specific error reasons for auth failures: no_auth_header, invalid_auth_format, invalid_token
- Fixed AUTH_TOKEN_FILE support in Docker production stacks (issue #16)
- Added AUTH_TOKEN_FILE support to single-session HTTP server for consistency
- Enhanced security by removing token lengths from logs
- Added token trimming and empty token validation
- Updated Docker entrypoint to properly support AUTH_TOKEN_FILE
- Bumped version to 2.7.10

This improves debugging for mcp-remote authentication issues and enables
proper Docker secrets usage in production environments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-07-07 23:19:35 +02:00
parent 6f11d339ca
commit 87f0cfc4dc
8 changed files with 169 additions and 29 deletions

View File

@@ -53,14 +53,17 @@ function validateEnvironment() {
// Load auth token from env var or file
authToken = loadAuthToken();
if (!authToken) {
logger.error('No authentication token found');
console.error('ERROR: AUTH_TOKEN is required for HTTP mode');
if (!authToken || authToken.trim() === '') {
logger.error('No authentication token found or token is empty');
console.error('ERROR: AUTH_TOKEN is required for HTTP mode and cannot be empty');
console.error('Set AUTH_TOKEN environment variable or AUTH_TOKEN_FILE pointing to a file containing the token');
console.error('Generate AUTH_TOKEN with: openssl rand -base64 32');
process.exit(1);
}
// Update authToken to trimmed version
authToken = authToken.trim();
if (authToken.length < 32) {
logger.warn('AUTH_TOKEN should be at least 32 characters for security');
console.warn('WARNING: AUTH_TOKEN should be at least 32 characters for security');
@@ -182,16 +185,55 @@ export async function startFixedHTTPServer() {
app.post('/mcp', async (req: express.Request, res: express.Response): Promise<void> => {
const startTime = Date.now();
// Simple auth check
// Enhanced authentication check with specific logging
const authHeader = req.headers.authorization;
const token = authHeader?.startsWith('Bearer ')
? authHeader.slice(7)
: authHeader;
if (token !== authToken) {
logger.warn('Authentication failed', {
// Check if Authorization header is missing
if (!authHeader) {
logger.warn('Authentication failed: Missing Authorization header', {
ip: req.ip,
userAgent: req.get('user-agent')
userAgent: req.get('user-agent'),
reason: 'no_auth_header'
});
res.status(401).json({
jsonrpc: '2.0',
error: {
code: -32001,
message: 'Unauthorized'
},
id: null
});
return;
}
// Check if Authorization header has Bearer prefix
if (!authHeader.startsWith('Bearer ')) {
logger.warn('Authentication failed: Invalid Authorization header format (expected Bearer token)', {
ip: req.ip,
userAgent: req.get('user-agent'),
reason: 'invalid_auth_format',
headerPrefix: authHeader.substring(0, 10) + '...' // Log first 10 chars for debugging
});
res.status(401).json({
jsonrpc: '2.0',
error: {
code: -32001,
message: 'Unauthorized'
},
id: null
});
return;
}
// Extract token and trim whitespace
const token = authHeader.slice(7).trim();
// Check if token matches
if (token !== authToken) {
logger.warn('Authentication failed: Invalid token', {
ip: req.ip,
userAgent: req.get('user-agent'),
reason: 'invalid_token'
});
res.status(401).json({
jsonrpc: '2.0',