- Created auth-server/index.ts with full OAuth endpoint support - Reuses EverythingAuthProvider from main server - Added custom /oauth/introspect endpoint for token validation - Includes fake upstream auth routes for user authentication - Added TypeScript configuration for auth server - Added comprehensive npm scripts for all modes: - dev:integrated, dev:separate, dev:auth-server - dev:with-separate-auth (runs both servers) - build:auth-server, build:all - Added concurrently dependency for running multiple servers - Added detailed README for auth server - Successfully serves OAuth metadata and health endpoints
146 lines
No EOL
4.8 KiB
TypeScript
146 lines
No EOL
4.8 KiB
TypeScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
|
|
import { EverythingAuthProvider } from '../src/auth/provider.js';
|
|
import { handleFakeAuthorize, handleFakeAuthorizeRedirect } from '../src/handlers/fakeauth.js';
|
|
import { redisClient } from '../src/redis.js';
|
|
import { logger } from '../src/utils/logger.js';
|
|
import { AUTH_SERVER_PORT, AUTH_SERVER_URL } from '../src/config.js';
|
|
|
|
const app = express();
|
|
|
|
console.log('=====================================');
|
|
console.log('MCP Demonstration Authorization Server');
|
|
console.log('=====================================');
|
|
console.log('This standalone server demonstrates OAuth 2.0');
|
|
console.log('authorization separate from the MCP resource server');
|
|
console.log('');
|
|
console.log('This is for demonstration purposes only.');
|
|
console.log('In production, you would use a real OAuth provider');
|
|
console.log('like Auth0, Okta, Google, GitHub, etc.');
|
|
console.log('=====================================');
|
|
|
|
// CORS for Inspector and MCP server
|
|
app.use(cors({
|
|
origin: true,
|
|
credentials: true
|
|
}));
|
|
|
|
app.use(express.json());
|
|
app.use(logger.middleware());
|
|
|
|
// Health check endpoint
|
|
app.get('/health', (req, res) => {
|
|
res.json({
|
|
status: 'healthy',
|
|
mode: 'authorization-server',
|
|
endpoints: {
|
|
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
|
authorize: `${AUTH_SERVER_URL}/oauth/authorize`,
|
|
token: `${AUTH_SERVER_URL}/oauth/token`,
|
|
register: `${AUTH_SERVER_URL}/oauth/register`,
|
|
introspect: `${AUTH_SERVER_URL}/oauth/introspect`
|
|
}
|
|
});
|
|
});
|
|
|
|
// Create auth provider instance for reuse
|
|
const authProvider = new EverythingAuthProvider();
|
|
|
|
// OAuth endpoints via SDK's mcpAuthRouter
|
|
app.use(mcpAuthRouter({
|
|
provider: authProvider,
|
|
issuerUrl: new URL(AUTH_SERVER_URL),
|
|
tokenOptions: {
|
|
rateLimit: { windowMs: 5000, limit: 100 }
|
|
},
|
|
clientRegistrationOptions: {
|
|
rateLimit: { windowMs: 60000, limit: 10 }
|
|
}
|
|
}));
|
|
|
|
// Token introspection endpoint (RFC 7662)
|
|
app.post('/oauth/introspect', express.urlencoded({ extended: false }), async (req, res) => {
|
|
try {
|
|
const { token } = req.body;
|
|
|
|
if (!token) {
|
|
return res.status(400).json({ error: 'invalid_request', error_description: 'Missing token parameter' });
|
|
}
|
|
|
|
// Verify the token using the auth provider
|
|
const authInfo = await authProvider.verifyAccessToken(token);
|
|
|
|
// Return RFC 7662 compliant response
|
|
res.json({
|
|
active: true,
|
|
client_id: authInfo.clientId,
|
|
scope: authInfo.scopes.join(' '),
|
|
exp: authInfo.expiresAt,
|
|
sub: authInfo.extra?.userId || 'unknown',
|
|
userId: authInfo.extra?.userId, // Custom field for our implementation
|
|
username: authInfo.extra?.username,
|
|
iss: AUTH_SERVER_URL,
|
|
aud: authInfo.clientId,
|
|
token_type: 'Bearer'
|
|
});
|
|
|
|
} catch (error) {
|
|
logger.debug('Token introspection failed', { error: (error as Error).message });
|
|
|
|
// Return inactive token response (don't leak error details)
|
|
res.json({
|
|
active: false
|
|
});
|
|
}
|
|
});
|
|
|
|
// Fake upstream auth endpoints (for user authentication simulation)
|
|
app.get('/fakeupstreamauth/authorize', cors(), handleFakeAuthorize);
|
|
app.get('/fakeupstreamauth/callback', cors(), handleFakeAuthorizeRedirect);
|
|
|
|
// Static assets (for auth page styling)
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
app.get('/mcp-logo.png', (req, res) => {
|
|
// Serve from the main server's static directory
|
|
const logoPath = path.join(__dirname, '../src/static/mcp.png');
|
|
res.sendFile(logoPath);
|
|
});
|
|
|
|
// Connect to Redis (shared with MCP server in dev)
|
|
try {
|
|
await redisClient.connect();
|
|
logger.info('Connected to Redis', { url: redisClient.options?.url });
|
|
} catch (error) {
|
|
logger.error('Could not connect to Redis', error as Error);
|
|
process.exit(1);
|
|
}
|
|
|
|
app.listen(AUTH_SERVER_PORT, () => {
|
|
logger.info('Authorization server started', {
|
|
port: AUTH_SERVER_PORT,
|
|
url: AUTH_SERVER_URL,
|
|
endpoints: {
|
|
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
|
authorize: `${AUTH_SERVER_URL}/oauth/authorize`,
|
|
token: `${AUTH_SERVER_URL}/oauth/token`,
|
|
register: `${AUTH_SERVER_URL}/oauth/register`,
|
|
introspect: `${AUTH_SERVER_URL}/oauth/introspect`
|
|
}
|
|
});
|
|
|
|
console.log('');
|
|
console.log('🚀 Auth server ready! Test with:');
|
|
console.log(` curl ${AUTH_SERVER_URL}/health`);
|
|
console.log(` curl ${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`);
|
|
console.log('');
|
|
console.log('💡 To test separate mode:');
|
|
console.log(' 1. Keep this server running');
|
|
console.log(' 2. In another terminal: AUTH_MODE=separate npm run dev');
|
|
console.log(' 3. Connect Inspector to http://localhost:3232');
|
|
}); |