Skip to main content
Glama
auth-middleware.ts6.28 kB
import { Request, Response, NextFunction } from 'express'; import { AuthProvider } from './auth-provider.js'; import { createContextLogger } from '../../utils/logger.js'; const logger = createContextLogger('AuthMiddleware'); /** * Options for authentication middleware */ export interface AuthMiddlewareOptions { /** Whether to bypass authentication for certain paths */ bypassPaths?: string[]; /** Whether to throw an error on authentication failure */ throwOnFailure?: boolean; /** Whether to enable CORS headers */ enableCors?: boolean; /** Origin to allow for CORS (default: '*') */ corsOrigin?: string; /** HTTP methods to allow for CORS (default: 'GET, POST, OPTIONS') */ corsMethods?: string; /** Headers to allow for CORS (default: 'Origin, X-Requested-With, Content-Type, Accept, Authorization') */ corsHeaders?: string; /** Log authentication requests (default: false in production, true in development) */ logAuth?: boolean; } /** * Extract token from request * * @param req - HTTP request * @returns Authentication token or null */ function extractToken(req: Request): string | null { // Check for authorization header const authHeader = req.headers.authorization; if (authHeader) { // Bearer token if (authHeader.startsWith('Bearer ')) { return authHeader.slice(7); } // Basic auth if (authHeader.startsWith('Basic ')) { const credentials = Buffer.from(authHeader.slice(6), 'base64').toString().split(':'); return credentials.length === 2 ? credentials.join(':') : null; } } // Check for token in query parameters if (req.query.token) { return req.query.token as string; } // Check for token in cookies if (req.cookies && req.cookies.token) { return req.cookies.token; } return null; } /** * Create authentication middleware * * @param authProvider - Authentication provider * @param options - Middleware options * @returns Express middleware function */ export function createAuthMiddleware( authProvider: AuthProvider, options: AuthMiddlewareOptions = {} ): (req: Request, res: Response, next: NextFunction) => Promise<void | Response> { const { bypassPaths = [], throwOnFailure = true, enableCors = true, // Default to enabling CORS for better compatibility corsOrigin = '*', corsMethods = 'GET, POST, OPTIONS', corsHeaders = 'Origin, X-Requested-With, Content-Type, Accept, Authorization', logAuth = process.env.NODE_ENV === 'development' || process.env.DEBUG_AUTH === 'true', } = options; // Essential endpoints are always bypassed const allBypassPaths = [...bypassPaths]; if (!allBypassPaths.includes('/health')) { allBypassPaths.push('/health'); } if (!allBypassPaths.includes('/status')) { allBypassPaths.push('/status'); } if (!allBypassPaths.includes('/auth')) { allBypassPaths.push('/auth'); } // Debug endpoints are only accessible in development mode if ( process.env.NODE_ENV === 'development' && !allBypassPaths.some(path => path.startsWith('/debug/')) ) { allBypassPaths.push('/debug/'); logger.info('Debug endpoints are accessible in development mode'); } return async (req: Request, res: Response, next: NextFunction) => { // Add CORS headers if enabled if (enableCors) { res.setHeader('Access-Control-Allow-Origin', corsOrigin); res.setHeader('Access-Control-Allow-Headers', corsHeaders); res.setHeader('Access-Control-Allow-Methods', corsMethods); // Handle OPTIONS requests for CORS preflight if (req.method === 'OPTIONS') { return res.status(200).end(); } } // Skip authentication for bypassed paths if (allBypassPaths.some(path => req.path === path || req.path.startsWith(path))) { // For health and debug endpoints, set system authentication req.auth = { authenticated: true, userId: 'system', roles: ['system'], }; return next(); } // Extract token from request const token = extractToken(req); if (!token) { if (logAuth) { logger.info(`Auth failed: No token provided for ${req.method} ${req.path}`); } else { logger.debug('No authentication token found'); } if (throwOnFailure) { return res.status(401).json({ error: 'Authentication required', message: 'Please provide a valid token via Bearer auth header or token query parameter', }); } // Set empty auth info and continue req.auth = { authenticated: false }; return next(); } try { // Validate token const authResult = await authProvider.validateToken(token); if (!authResult.success) { if (logAuth) { logger.info(`Auth failed: ${authResult.error} for ${req.method} ${req.path}`); } else { logger.debug(`Authentication failed: ${authResult.error}`); } if (throwOnFailure) { return res.status(401).json({ error: authResult.error || 'Authentication failed' }); } // Set empty auth info and continue req.auth = { authenticated: false }; return next(); } // Set authentication info on request req.auth = { authenticated: true, userId: authResult.userId, roles: authResult.roles || [], }; if (logAuth) { logger.info(`Auth success: User ${authResult.userId} for ${req.method} ${req.path}`); } else { logger.debug(`Authenticated user: ${authResult.userId}`); } next(); } catch (error) { logger.error('Authentication error:', error); if (throwOnFailure) { return res.status(500).json({ error: 'Authentication error' }); } // Set empty auth info and continue req.auth = { authenticated: false }; next(); } }; } // Extend Express Request type to include auth information import 'express'; // Make sure we're using the correct module augmentation pattern declare global { namespace Express { // Extend the Express Request interface interface Request { auth?: { authenticated: boolean; userId?: string; roles?: string[]; }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/metcalfc/atrax'

If you have feedback or need assistance with the MCP directory API, please join our Discord server