Skip to content

Commit 4a081bc

Browse files
committed
Improve e2e tests with OAuth flow validation
Add state parameter round-trip validation to prevent CSRF attacks. Document each OAuth step including PKCE generation and token exchange. Kill existing servers before starting new ones to avoid conflicts.
1 parent 4fe1374 commit 4a081bc

2 files changed

Lines changed: 111 additions & 30 deletions

File tree

scripts/test-integrated-e2e.sh

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ echo "=================================================="
55
echo "End-to-End Test - Integrated Mode"
66
echo "=================================================="
77

8+
# Kill any existing servers
9+
echo "🛑 Cleaning up existing servers..."
10+
pkill -f "node.*dist/src/index" || true
11+
pkill -f "tsx watch.*src/index" || true
12+
sleep 2
13+
814
# Use environment variables if available, otherwise defaults
915
MCP_SERVER="${BASE_URI:-http://localhost:3232}"
1016
USER_ID="e2e-test-$(date +%s)"
@@ -33,34 +39,68 @@ if [ "${AUTH_MODE:-integrated}" != "integrated" ]; then
3339
echo " Or use: ./scripts/test-separate-e2e.sh"
3440
fi
3541

42+
# Start MCP server in integrated mode
43+
echo "🚀 Starting MCP server in integrated mode..."
44+
AUTH_MODE=integrated npm start &
45+
MCP_PID=$!
46+
sleep 5
47+
3648
# Check MCP server
3749
if ! curl -s -f "$MCP_SERVER/" > /dev/null; then
38-
echo "❌ MCP server not running at $MCP_SERVER"
39-
echo " Required setup:"
40-
echo " 1. Start Redis: docker compose up -d"
41-
echo " 2. Start MCP server: npm run dev:integrated"
42-
echo " 3. Or set up environment:"
43-
echo " cp .env.integrated .env && npm run dev"
50+
echo "❌ MCP server failed to start at $MCP_SERVER"
51+
kill $MCP_PID 2>/dev/null || true
4452
exit 1
4553
fi
46-
echo "✅ MCP server is running"
54+
echo "✅ MCP server is running (PID: $MCP_PID)"
55+
56+
# Clean up on exit
57+
trap "kill $MCP_PID 2>/dev/null || true" EXIT
4758

4859
echo "🔐 PHASE 1: OAuth Authentication"
4960
echo "================================"
5061

51-
# OAuth flow (abbreviated for clarity)
62+
# OAuth Step 1: Client Registration
63+
# Register a new OAuth client application with the authorization server
64+
# This would typically be done once during app setup, not for each user
5265
CLIENT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d '{"client_name":"e2e-fixed","redirect_uris":["http://localhost:3000/callback"]}' "$MCP_SERVER/register")
5366
CLIENT_ID=$(echo "$CLIENT_RESPONSE" | jq -r .client_id)
5467
CLIENT_SECRET=$(echo "$CLIENT_RESPONSE" | jq -r .client_secret)
5568

69+
# OAuth Step 2: Generate PKCE (Proof Key for Code Exchange) parameters
70+
# PKCE adds security to the OAuth flow by preventing authorization code interception attacks
5671
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-43)
5772
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr "+/" "-_" | tr -d "=")
5873

59-
AUTH_PAGE=$(curl -s "$MCP_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=e2e-state")
74+
# OAuth Step 3: Authorization Request
75+
# Direct the user to the authorization server's /authorize endpoint
76+
# Include state parameter for CSRF protection
77+
STATE_PARAM="e2e-state-$(date +%s)"
78+
79+
AUTH_PAGE=$(curl -s "$MCP_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=$STATE_PARAM")
80+
# Extract the authorization code from the HTML response (normally would be in redirect URL)
6081
AUTH_CODE=$(echo "$AUTH_PAGE" | grep -o 'state=[^"&]*' | cut -d= -f2)
6182

62-
curl -s "$MCP_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID" > /dev/null
83+
# OAuth Step 4: User Authentication & Authorization
84+
# In a real flow, the user would authenticate with the auth server here
85+
# For testing, we simulate this with the fake upstream auth endpoint
86+
CALLBACK_RESPONSE=$(curl -s -i "$MCP_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID")
87+
88+
# OAuth Step 5: Authorization Code Redirect
89+
# Verify the auth server redirects back to our redirect_uri with the code and state
90+
# The state parameter MUST match what we sent to prevent CSRF attacks
91+
LOCATION_HEADER=$(echo "$CALLBACK_RESPONSE" | grep -i "^location:" | tr -d '\r')
92+
if echo "$LOCATION_HEADER" | grep -q "state=$STATE_PARAM"; then
93+
echo "✅ State parameter verified in callback"
94+
else
95+
echo "❌ State parameter mismatch or missing in callback"
96+
echo " Expected state: $STATE_PARAM"
97+
echo " Location header: $LOCATION_HEADER"
98+
exit 1
99+
fi
63100

101+
# OAuth Step 6: Token Exchange
102+
# Exchange the authorization code for access and refresh tokens
103+
# Include the PKCE code_verifier to prove we initiated the flow
64104
TOKEN_RESPONSE=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&code=$AUTH_CODE&redirect_uri=http://localhost:3000/callback&code_verifier=$CODE_VERIFIER" "$MCP_SERVER/token")
65105

66106
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r .access_token)

scripts/test-separate-e2e.sh

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ echo "This script tests the complete OAuth flow and MCP features"
88
echo "using separate auth server and MCP server."
99
echo ""
1010

11+
# Kill any existing servers
12+
echo "🛑 Cleaning up existing servers..."
13+
pkill -f "node.*dist/src/index" || true
14+
pkill -f "node.*dist/auth-server/index" || true
15+
pkill -f "tsx watch.*src/index" || true
16+
pkill -f "tsx watch.*auth-server/index" || true
17+
sleep 2
18+
1119
# Use environment variables if available, otherwise defaults
1220
AUTH_SERVER="${AUTH_SERVER_URL:-http://localhost:3001}"
1321
MCP_SERVER="${BASE_URI:-http://localhost:3232}"
@@ -38,34 +46,45 @@ if [ "${AUTH_MODE}" = "integrated" ]; then
3846
echo " Or use: ./scripts/test-integrated-e2e-fixed.sh"
3947
fi
4048

49+
# Start auth server
50+
echo "🚀 Starting auth server..."
51+
npm run start:auth-server &
52+
AUTH_PID=$!
53+
sleep 5
54+
4155
# Check auth server
4256
if ! curl -s -f "$AUTH_SERVER/health" > /dev/null; then
43-
echo "❌ Auth server not running at $AUTH_SERVER"
44-
echo " Required setup:"
45-
echo " 1. Start Redis: docker compose up -d"
46-
echo " 2. Start both servers: npm run dev:with-separate-auth"
47-
echo " 3. Or start separately:"
48-
echo " Terminal 1: npm run dev:auth-server"
49-
echo " Terminal 2: AUTH_MODE=separate npm run dev"
50-
echo " 4. Or set up environment:"
51-
echo " cp .env.separate .env && npm run dev:with-separate-auth"
57+
echo "❌ Auth server failed to start at $AUTH_SERVER"
58+
kill $AUTH_PID 2>/dev/null || true
5259
exit 1
5360
fi
54-
echo "✅ Auth server is running"
61+
echo "✅ Auth server is running (PID: $AUTH_PID)"
62+
63+
# Start MCP server in separate mode
64+
echo "🚀 Starting MCP server in separate mode..."
65+
AUTH_MODE=separate npm start &
66+
MCP_PID=$!
67+
sleep 5
5568

5669
# Check MCP server
5770
if ! curl -s -f "$MCP_SERVER/" > /dev/null; then
58-
echo "❌ MCP server not running at $MCP_SERVER"
59-
echo " See auth server setup instructions above"
71+
echo "❌ MCP server failed to start at $MCP_SERVER"
72+
kill $AUTH_PID 2>/dev/null || true
73+
kill $MCP_PID 2>/dev/null || true
6074
exit 1
6175
fi
62-
echo "✅ MCP server is running"
76+
echo "✅ MCP server is running (PID: $MCP_PID)"
77+
78+
# Clean up on exit
79+
trap "kill $AUTH_PID $MCP_PID 2>/dev/null || true" EXIT
6380

6481
echo ""
6582
echo "🔐 PHASE 1: OAuth Authentication (with Auth Server)"
6683
echo "================================================="
6784

68-
# Step 1: Register OAuth client with AUTH SERVER
85+
# OAuth Step 1: Client Registration
86+
# Register a new OAuth client application with the authorization server
87+
# This would typically be done once during app setup, not for each user
6988
echo "📝 Step 1: Register OAuth client with auth server"
7089
CLIENT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" \
7190
-d "{\"client_name\":\"e2e-separate-client\",\"redirect_uris\":[\"http://localhost:3000/callback\"]}" \
@@ -75,19 +94,24 @@ CLIENT_ID=$(echo "$CLIENT_RESPONSE" | jq -r .client_id)
7594
CLIENT_SECRET=$(echo "$CLIENT_RESPONSE" | jq -r .client_secret)
7695
echo " Client ID: $CLIENT_ID"
7796

78-
# Step 2: Generate PKCE
97+
# OAuth Step 2: Generate PKCE (Proof Key for Code Exchange) parameters
98+
# PKCE adds security to the OAuth flow by preventing authorization code interception attacks
7999
echo ""
80100
echo "🔐 Step 2: Generate PKCE challenge"
81101
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-43)
82102
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr "+/" "-_" | tr -d "=")
83103
echo " Code verifier generated"
84104

85-
# Step 3: Get authorization code from AUTH SERVER
105+
# OAuth Step 3: Authorization Request
106+
# Direct the user to the authorization server's /authorize endpoint
107+
# Include state parameter for CSRF protection
86108
echo ""
87109
echo "🎫 Step 3: Get authorization code from auth server"
88-
AUTH_URL="$AUTH_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=separate-test"
110+
STATE_PARAM="separate-test-$(date +%s)"
111+
AUTH_URL="$AUTH_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=$STATE_PARAM"
89112

90113
AUTH_PAGE=$(curl -s "$AUTH_URL")
114+
# Extract the authorization code from the HTML response (normally would be in redirect URL)
91115
AUTH_CODE=$(echo "$AUTH_PAGE" | grep -o 'state=[^"&]*' | cut -d= -f2 | head -1)
92116

93117
if [ -z "$AUTH_CODE" ]; then
@@ -96,14 +120,31 @@ if [ -z "$AUTH_CODE" ]; then
96120
fi
97121
echo " Auth Code: ${AUTH_CODE:0:20}..."
98122

99-
# Step 4: Complete fake upstream auth with AUTH SERVER
123+
# OAuth Step 4: User Authentication & Authorization
124+
# In a real flow, the user would authenticate with the auth server here
125+
# For testing, we simulate this with the fake upstream auth endpoint
100126
echo ""
101127
echo "🔄 Step 4: Complete fake upstream auth with auth server"
102128
CALLBACK_URL="$AUTH_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID"
103-
curl -s -L "$CALLBACK_URL" > /dev/null
129+
CALLBACK_RESPONSE=$(curl -s -i "$CALLBACK_URL")
130+
131+
# OAuth Step 5: Authorization Code Redirect
132+
# Verify the auth server redirects back to our redirect_uri with the code and state
133+
# The state parameter MUST match what we sent to prevent CSRF attacks
134+
LOCATION_HEADER=$(echo "$CALLBACK_RESPONSE" | grep -i "^location:" | tr -d '\r')
135+
if echo "$LOCATION_HEADER" | grep -q "state=$STATE_PARAM"; then
136+
echo " ✅ State parameter verified in callback"
137+
else
138+
echo " ❌ State parameter mismatch or missing in callback"
139+
echo " Expected state: $STATE_PARAM"
140+
echo " Location header: $LOCATION_HEADER"
141+
exit 1
142+
fi
104143
echo " Fake upstream auth completed"
105144

106-
# Step 5: Exchange for tokens with AUTH SERVER
145+
# OAuth Step 6: Token Exchange
146+
# Exchange the authorization code for access and refresh tokens
147+
# Include the PKCE code_verifier to prove we initiated the flow
107148
echo ""
108149
echo "🎟️ Step 5: Exchange code for access token with auth server"
109150
TOKEN_RESPONSE=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" \

0 commit comments

Comments
 (0)