Skip to main content
Glama
intecrel

Industrial MCP Server

by intecrel
oauth-authentication.test.ts8.94 kB
/** * OAuth 2.1 Authentication Tests * Tests for Bearer token authentication with access_token and refresh_token * Covers 5 production Claude.ai custom connector users */ import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import { NextRequest } from 'next/server'; import { authenticateRequest, authenticateOAuth, detectAuthMethod, hasToolPermission, AuthContext } from '@/lib/oauth/auth-middleware'; import { validateAccessToken } from '@/lib/oauth/jwt'; jest.mock('@/lib/oauth/jwt'); jest.mock('@/lib/oauth/config'); jest.mock('@/lib/oauth/scopes', () => ({ isToolAccessible: jest.fn() })); describe('OAuth 2.1 Authentication', () => { describe('detectAuthMethod', () => { it('should detect OAuth Bearer token method', () => { const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'authorization': 'Bearer test-token-123' } }); const method = detectAuthMethod(request); expect(method).toBe('oauth'); }); it('should return none when no auth headers present', () => { const request = new NextRequest('http://localhost:3000/api/mcp'); const method = detectAuthMethod(request); expect(method).toBe('none'); }); it('should detect API key method when x-api-key header present', () => { const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'x-api-key': 'test-key' } }); const method = detectAuthMethod(request); expect(method).toBe('api_key'); }); }); describe('authenticateOAuth', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should successfully authenticate with valid access token', async () => { const mockClaims = { iss: 'https://industrial-mcp.vercel.app', sub: 'claude-connector-user-1', aud: 'https://industrial-mcp.vercel.app', client_id: 'claude-connector-user-1', scope: 'mcp:read mcp:write', token_type: 'access_token' as const, exp: Math.floor(Date.now() / 1000) + 3600, iat: Math.floor(Date.now() / 1000) }; (validateAccessToken as jest.MockedFunction<typeof validateAccessToken>) .mockResolvedValue(mockClaims); const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'authorization': 'Bearer valid-access-token' } }); const authContext = await authenticateOAuth(request); expect(authContext).toEqual({ method: 'oauth', userId: 'claude-connector-user-1', clientId: 'claude-connector-user-1', scopes: ['mcp:read', 'mcp:write'], permissions: ['mcp:read', 'mcp:write'] }); }); it('should reject expired access token', async () => { (validateAccessToken as jest.MockedFunction<typeof validateAccessToken>) .mockRejectedValue(new Error('Token expired')); const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'authorization': 'Bearer expired-token' } }); await expect(authenticateOAuth(request)).rejects.toThrow( 'OAuth authentication failed: Token expired' ); }); it('should reject malformed Bearer token', async () => { (validateAccessToken as jest.MockedFunction<typeof validateAccessToken>) .mockRejectedValue(new Error('Invalid token format')); const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'authorization': 'Bearer invalid-format' } }); await expect(authenticateOAuth(request)).rejects.toThrow( 'OAuth authentication failed: Invalid token format' ); }); it('should handle missing authorization header', async () => { (validateAccessToken as jest.MockedFunction<typeof validateAccessToken>) .mockRejectedValue(new Error('Missing Authorization header')); const request = new NextRequest('http://localhost:3000/api/mcp'); await expect(authenticateOAuth(request)).rejects.toThrow( 'OAuth authentication failed: Missing Authorization header' ); }); it('should parse multiple scopes correctly', async () => { const mockClaims = { iss: 'https://industrial-mcp.vercel.app', sub: 'claude-connector-user-2', aud: 'https://industrial-mcp.vercel.app', client_id: 'claude-connector-user-2', scope: 'mcp:read mcp:write mcp:admin database:query analytics:read', token_type: 'access_token' as const, exp: Math.floor(Date.now() / 1000) + 3600, iat: Math.floor(Date.now() / 1000) }; (validateAccessToken as jest.MockedFunction<typeof validateAccessToken>) .mockResolvedValue(mockClaims); const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'authorization': 'Bearer multi-scope-token' } }); const authContext = await authenticateOAuth(request); expect(authContext.scopes).toEqual([ 'mcp:read', 'mcp:write', 'mcp:admin', 'database:query', 'analytics:read' ]); }); }); describe('authenticateRequest', () => { beforeEach(() => { jest.clearAllMocks(); // Mock OAuth as enabled by default const { isOAuthEnabled } = require('@/lib/oauth/config'); (isOAuthEnabled as jest.MockedFunction<typeof isOAuthEnabled>) .mockReturnValue(true); }); it('should route to OAuth authentication for Bearer token', async () => { const mockClaims = { iss: 'https://industrial-mcp.vercel.app', sub: 'claude-connector-user-3', aud: 'https://industrial-mcp.vercel.app', client_id: 'claude-connector-user-3', scope: 'mcp:read', token_type: 'access_token' as const, exp: Math.floor(Date.now() / 1000) + 3600, iat: Math.floor(Date.now() / 1000) }; (validateAccessToken as jest.MockedFunction<typeof validateAccessToken>) .mockResolvedValue(mockClaims); const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'authorization': 'Bearer test-token' } }); const authContext = await authenticateRequest(request); expect(authContext.method).toBe('oauth'); expect(authContext.userId).toBe('claude-connector-user-3'); }); it('should throw error when no authentication method provided', async () => { const request = new NextRequest('http://localhost:3000/api/mcp'); await expect(authenticateRequest(request)).rejects.toThrow( 'Authentication required' ); }); it('should throw error when OAuth is disabled', async () => { const { isOAuthEnabled } = require('@/lib/oauth/config'); (isOAuthEnabled as jest.MockedFunction<typeof isOAuthEnabled>) .mockReturnValue(false); const request = new NextRequest('http://localhost:3000/api/mcp', { headers: { 'authorization': 'Bearer test-token' } }); await expect(authenticateRequest(request)).rejects.toThrow( 'OAuth authentication is disabled' ); }); }); describe('hasToolPermission', () => { it('should grant access when user has required scope', () => { const authContext: AuthContext = { method: 'oauth', userId: 'claude-connector-user-4', clientId: 'claude-connector-user-4', scopes: ['mcp:read', 'database:query'], permissions: ['mcp:read', 'database:query'] }; // Mock isToolAccessible to check if scope includes tool access const { isToolAccessible } = require('@/lib/oauth/scopes'); (isToolAccessible as jest.MockedFunction<typeof isToolAccessible>) .mockReturnValue(true); const hasAccess = hasToolPermission(authContext, 'query_database'); expect(hasAccess).toBe(true); }); it('should deny access when user lacks required scope', () => { const authContext: AuthContext = { method: 'oauth', userId: 'claude-connector-user-5', clientId: 'claude-connector-user-5', scopes: ['mcp:read'], permissions: ['mcp:read'] }; const { isToolAccessible } = require('@/lib/oauth/scopes'); (isToolAccessible as jest.MockedFunction<typeof isToolAccessible>) .mockReturnValue(false); const hasAccess = hasToolPermission(authContext, 'analyze_data'); expect(hasAccess).toBe(false); }); it('should grant full access for API key authentication', () => { const authContext: AuthContext = { method: 'api_key', userId: 'api-key-user', permissions: ['*'] }; const hasAccess = hasToolPermission(authContext, 'any_tool'); expect(hasAccess).toBe(true); }); }); });

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/intecrel/industrial-mcp'

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