Create beautiful black and white MCP-branded auth pages

**MCP Server Auth Page (src/auth/provider.ts):**
- Black background with white card design
- Official MCP logo and branding
- Clear client information display
- Elegant "Continue to Authentication" flow
- Professional MCP server identification

**Upstream Fake Auth Page (src/handlers/fakeauth.ts):**
- Updated branding to distinguish from MCP server
- Lock icon and "Upstream Authentication" title
- Clear user ID management with localStorage
- Generate/edit user ID functionality for testing
- Multi-user testing instructions

**Technical Updates:**
- Permissive CSP headers for both auth pages
- Logo serving route at /mcp-logo.png
- Responsive design with modern CSS
- Clear visual distinction between MCP server auth vs upstream auth
- Enhanced userId integration in OAuth authorization flow

Both pages now provide professional user experience while maintaining
clear distinction between the MCP server's auth and the fake upstream
provider auth for testing purposes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jerome 2025-07-08 12:33:30 +01:00
commit d8e706ff9d
No known key found for this signature in database

View file

@ -12,18 +12,245 @@ export async function handleFakeAuthorize(req: Request, res: Response) {
// get the redirect_uri and state from the query params
const { redirect_uri, state } = req.query;
// TODO, mint actual codes?
// Set a more permissive CSP for auth pages to allow inline styles and scripts
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
"style-src 'self' 'unsafe-inline'", // Allow inline styles for auth page styling
"script-src 'self' 'unsafe-inline'", // Allow inline scripts for auth page functionality
"object-src 'none'",
"frame-ancestors 'none'",
"form-action 'self'",
"base-uri 'self'"
].join('; '));
res.send(`
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fake Upstream Auth Provider!</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upstream Provider Authentication</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.auth-container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
max-width: 480px;
width: 100%;
text-align: center;
}
.logo {
width: 64px;
height: 64px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 16px;
margin: 0 auto 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
color: white;
}
h1 {
color: #1a202c;
font-size: 28px;
font-weight: 700;
margin-bottom: 8px;
}
.subtitle {
color: #718096;
font-size: 16px;
margin-bottom: 32px;
}
.user-section {
background: #f7fafc;
border-radius: 12px;
padding: 24px;
margin-bottom: 32px;
border: 2px solid #e2e8f0;
}
.user-section h3 {
color: #2d3748;
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
}
.user-id-display {
background: white;
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 12px;
font-family: 'Courier New', monospace;
font-size: 14px;
color: #4a5568;
margin-bottom: 16px;
word-break: break-all;
}
.user-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.btn {
flex: 1;
min-width: 120px;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn-secondary {
background: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background: #cbd5e0;
}
.btn-primary {
background: linear-gradient(135deg, #4299e1, #3182ce);
color: white;
font-size: 16px;
padding: 16px 32px;
margin-top: 16px;
width: 100%;
}
.btn-primary:hover {
background: linear-gradient(135deg, #3182ce, #2c5282);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
}
.help-text {
color: #718096;
font-size: 14px;
margin-top: 24px;
line-height: 1.5;
}
.help-text strong {
color: #4a5568;
}
</style>
</head>
<body>
<h1>Fake Auth</h1>
<p>Fake auth page</p>
<p>Click <a href="${redirect_uri}?state=${state}&code=fakecode">here</a> to authorize</p>
<p>Click <a href="${redirect_uri}?state=${state}&code=fakecode">here</a> to fail authorization</p>
<div class="auth-container">
<div class="logo">🔒</div>
<h1>Upstream Authentication</h1>
<p class="subtitle">Please verify your identity with the upstream provider</p>
<div class="user-section">
<h3>Your User Identity</h3>
<div class="user-id-display" id="userIdDisplay">Loading...</div>
<div class="user-actions">
<button class="btn btn-secondary" onclick="generateNewUserId()">Generate New ID</button>
<button class="btn btn-secondary" onclick="editUserId()">Edit ID</button>
</div>
</div>
<button class="btn btn-primary" onclick="authorize()">
Complete Authentication
</button>
<div class="help-text">
<strong>Testing Multiple Users:</strong> Open this page in different browser windows or incognito tabs to simulate different users. Each will have their own unique User ID and separate MCP sessions.
</div>
</div>
<script>
// Generate UUID v4
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Get or create user ID
function getUserId() {
let userId = localStorage.getItem('mcpUserId');
if (!userId) {
userId = generateUUID();
localStorage.setItem('mcpUserId', userId);
}
return userId;
}
// Update the display
function updateDisplay() {
const userId = getUserId();
document.getElementById('userIdDisplay').textContent = userId;
}
// Generate new user ID
function generateNewUserId() {
const newId = generateUUID();
localStorage.setItem('mcpUserId', newId);
updateDisplay();
}
// Edit user ID
function editUserId() {
const currentId = getUserId();
const newId = prompt('Enter new User ID:', currentId);
if (newId && newId.trim()) {
localStorage.setItem('mcpUserId', newId.trim());
updateDisplay();
}
}
// Authorize with current user ID
function authorize() {
const userId = getUserId();
// Handle relative URLs by making them absolute
const redirectUri = '${redirect_uri}';
const baseUrl = redirectUri.startsWith('http') ? redirectUri : window.location.origin + redirectUri;
const url = new URL(baseUrl);
url.searchParams.set('state', '${state}');
url.searchParams.set('code', 'fakecode');
url.searchParams.set('userId', userId);
window.location.href = url.toString();
}
// Initialize on page load
updateDisplay();
</script>
</body>
</html>
`);
@ -36,6 +263,7 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) {
// The state returned from the upstream auth server is actually the authorization code
state: mcpAuthorizationCode,
code: upstreamAuthorizationCode,
userId, // User ID from the authorization flow
} = req.query;
// This is where you'd exchange the upstreamAuthorizationCode for access/refresh tokens
@ -63,6 +291,7 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) {
mcpTokens,
clientId: pendingAuth.clientId,
issuedAt: Date.now() / 1000,
userId: (userId as string) || 'anonymous-user', // Include user ID from auth flow
}
// Store the upstream authorization data