diff --git a/chat-app/backend/requirements.txt b/chat-app/backend/requirements.txt index 551ffafa..8b5a91c2 100644 --- a/chat-app/backend/requirements.txt +++ b/chat-app/backend/requirements.txt @@ -7,7 +7,7 @@ fastapi==0.136.0 uvicorn[standard]==0.44.0 pydantic[email]==2.13.2 pydantic-settings==2.14.2 -PyJWT[crypto]==2.10.1 +PyJWT[crypto]==2.15.0 agent-framework-azure-ai==1.0.0rc6 agent-framework-core==1.0.0rc6 diff --git a/chat-app/frontend/src/components/EnhancedChatPanel.tsx b/chat-app/frontend/src/components/EnhancedChatPanel.tsx index 8fb9869a..48294240 100644 --- a/chat-app/frontend/src/components/EnhancedChatPanel.tsx +++ b/chat-app/frontend/src/components/EnhancedChatPanel.tsx @@ -146,7 +146,11 @@ export const EnhancedChatPanel = ({ const ttsHeaders: Record = { 'Content-Type': 'application/json' }; const bearer = getApiBearerToken(); if (bearer) { - ttsHeaders.Authorization = `Bearer ${bearer}`; + const isSameOriginProxy = + typeof window !== 'undefined' && + new URL(apiBase || '/', window.location.origin).origin === window.location.origin; + const headerName = isSameOriginProxy ? 'X-Backend-Authorization' : 'Authorization'; + ttsHeaders[headerName] = `Bearer ${bearer}`; } const resp = await fetch(`${apiBase}/api/voice/tts`, { method: 'POST', diff --git a/chat-app/frontend/src/lib/api.ts b/chat-app/frontend/src/lib/api.ts index 6c9c9d8a..5c35d2d0 100644 --- a/chat-app/frontend/src/lib/api.ts +++ b/chat-app/frontend/src/lib/api.ts @@ -73,7 +73,12 @@ api.interceptors.request.use((config) => { api.interceptors.request.use( (config) => { if (cachedBearerToken && config.headers) { - config.headers.Authorization = `Bearer ${cachedBearerToken}`; + const base = getApiBaseUrl(); + const isSameOriginProxy = + typeof window !== 'undefined' && + new URL(base || '/', window.location.origin).origin === window.location.origin; + const headerName = isSameOriginProxy ? 'X-Backend-Authorization' : 'Authorization'; + config.headers[headerName] = `Bearer ${cachedBearerToken}`; } return config; diff --git a/chat-app/frontend/startup.sh b/chat-app/frontend/startup.sh index c11546c6..37d565e2 100644 --- a/chat-app/frontend/startup.sh +++ b/chat-app/frontend/startup.sh @@ -41,6 +41,7 @@ location /api/ { set \$backend "${BACKEND_API_URL}"; proxy_pass \$backend; proxy_set_header Host "${BACKEND_HOST}"; + proxy_set_header Authorization \$http_x_backend_authorization; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; @@ -49,6 +50,9 @@ location /api/ { proxy_connect_timeout 60s; proxy_buffering off; + proxy_redirect ~^https?://${BACKEND_HOST}/(.*)\$ /\$1; + + # WebSocket support (needed for /api/voice/ws/... connections) proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; diff --git a/infra/scripts/post-provision/configure_auth.ps1 b/infra/scripts/post-provision/configure_auth.ps1 index d46691b1..79820356 100644 --- a/infra/scripts/post-provision/configure_auth.ps1 +++ b/infra/scripts/post-provision/configure_auth.ps1 @@ -117,7 +117,7 @@ function Set-FrontendAuth { openIdIssuer = "https://login.microsoftonline.com/$TenantId/v2.0" } login = @{ loginParameters = @("scope=openid profile email offline_access api://$ApplicationClientId/user_impersonation") } - validation = @{ allowedAudiences = @($ApplicationClientId) } + validation = @{ allowedAudiences = @($ApplicationClientId, "api://$ApplicationClientId") } } } login = @{ tokenStore = @{ enabled = $true } } diff --git a/infra/scripts/post-provision/configure_auth.sh b/infra/scripts/post-provision/configure_auth.sh index 8061cf54..ed4c1510 100644 --- a/infra/scripts/post-provision/configure_auth.sh +++ b/infra/scripts/post-provision/configure_auth.sh @@ -171,7 +171,7 @@ configure_frontend() { auth_uri="https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/sites/$app_name/config/authsettingsV2?api-version=2022-09-01" body_file="$(mktemp)" trap 'rm -f "$body_file"' RETURN - printf '%s' "{\"properties\":{\"platform\":{\"enabled\":true,\"runtimeVersion\":\"~1\"},\"globalValidation\":{\"requireAuthentication\":false,\"unauthenticatedClientAction\":\"AllowAnonymous\"},\"httpSettings\":{\"requireHttps\":true},\"identityProviders\":{\"azureActiveDirectory\":{\"enabled\":true,\"registration\":{\"clientId\":\"$CLIENT_ID\",\"clientSecretSettingName\":\"$SECRET_SETTING_NAME\",\"openIdIssuer\":\"https://login.microsoftonline.com/$TENANT_ID/v2.0\"},\"login\":{\"loginParameters\":[\"scope=openid profile email offline_access api://$CLIENT_ID/user_impersonation\"]},\"validation\":{\"allowedAudiences\":[\"$CLIENT_ID\"]}}},\"login\":{\"tokenStore\":{\"enabled\":true}}}}" > "$body_file" + printf '%s' "{\"properties\":{\"platform\":{\"enabled\":true,\"runtimeVersion\":\"~1\"},\"globalValidation\":{\"requireAuthentication\":false,\"unauthenticatedClientAction\":\"AllowAnonymous\"},\"httpSettings\":{\"requireHttps\":true},\"identityProviders\":{\"azureActiveDirectory\":{\"enabled\":true,\"registration\":{\"clientId\":\"$CLIENT_ID\",\"clientSecretSettingName\":\"$SECRET_SETTING_NAME\",\"openIdIssuer\":\"https://login.microsoftonline.com/$TENANT_ID/v2.0\"},\"login\":{\"loginParameters\":[\"scope=openid profile email offline_access api://$CLIENT_ID/user_impersonation\"]},\"validation\":{\"allowedAudiences\":[\"$CLIENT_ID\",\"api://$CLIENT_ID\"]}}},\"login\":{\"tokenStore\":{\"enabled\":true}}}}" > "$body_file" az rest --method put --uri "$auth_uri" --body "@$body_file" --output none rm -f "$body_file" trap - RETURN diff --git a/scenario-app/backend/requirements.txt b/scenario-app/backend/requirements.txt index 04e38bd1..99c29fa0 100644 --- a/scenario-app/backend/requirements.txt +++ b/scenario-app/backend/requirements.txt @@ -8,7 +8,7 @@ uvicorn[standard]==0.40.0 pydantic[email]==2.11.10 pydantic-settings==2.14.2 python-multipart==0.0.32 -PyJWT[crypto]==2.10.1 +PyJWT[crypto]==2.15.0 # Azure Services for E-commerce azure-identity==1.25.2 diff --git a/scenario-app/frontend/src/components/EnhancedChatPanel.tsx b/scenario-app/frontend/src/components/EnhancedChatPanel.tsx index 1c0fe84f..a44ec2cc 100644 --- a/scenario-app/frontend/src/components/EnhancedChatPanel.tsx +++ b/scenario-app/frontend/src/components/EnhancedChatPanel.tsx @@ -144,7 +144,10 @@ export const EnhancedChatPanel = ({ const ttsHeaders: Record = { 'Content-Type': 'application/json' }; const bearer = getApiBearerToken(); if (bearer) { - ttsHeaders.Authorization = `Bearer ${bearer}`; + const isSameOriginProxy = + typeof window !== 'undefined' && apiBase === window.location.origin; + const headerName = isSameOriginProxy ? 'X-Backend-Authorization' : 'Authorization'; + ttsHeaders[headerName] = `Bearer ${bearer}`; } const resp = await fetch(`${apiBase}/api/voice/tts`, { method: 'POST', diff --git a/scenario-app/frontend/src/lib/api.ts b/scenario-app/frontend/src/lib/api.ts index 4ebcfe16..2e8536c3 100644 --- a/scenario-app/frontend/src/lib/api.ts +++ b/scenario-app/frontend/src/lib/api.ts @@ -63,7 +63,11 @@ api.interceptors.request.use((config) => { api.interceptors.request.use( (config) => { if (cachedBearerToken && config.headers) { - config.headers.Authorization = `Bearer ${cachedBearerToken}`; + const base = getApiBaseUrl(); + const isSameOriginProxy = + typeof window !== 'undefined' && base === window.location.origin; + const headerName = isSameOriginProxy ? 'X-Backend-Authorization' : 'Authorization'; + config.headers[headerName] = `Bearer ${cachedBearerToken}`; } return config; diff --git a/scenario-app/frontend/startup.sh b/scenario-app/frontend/startup.sh index 8709d167..b248cdf6 100644 --- a/scenario-app/frontend/startup.sh +++ b/scenario-app/frontend/startup.sh @@ -76,10 +76,11 @@ if [ -n "${BACKEND_API_URL}" ]; then cat > /etc/nginx/conf.d/api-proxy.conf << PROXYEOF # Reverse proxy for backend API - WAF private networking deployment location /api/ { - resolver 168.63.129.16 valid=30s; - set \$backend "${BACKEND_API_URL}"; - proxy_pass \$backend; - proxy_set_header Host "${BACKEND_HOST}"; + resolver 168.63.129.16 valid=30s; + set \$backend "${BACKEND_API_URL}"; + proxy_pass \$backend; + proxy_set_header Host "${BACKEND_HOST}"; + proxy_set_header Authorization \$http_x_backend_authorization; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; @@ -88,6 +89,8 @@ location /api/ { proxy_connect_timeout 60s; proxy_buffering off; + proxy_redirect ~^https?://${BACKEND_HOST}/(.*)\$ /\$1; + # WebSocket support (needed for /api/voice/ws/... connections) proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; @@ -104,6 +107,7 @@ location /chat-api/ { rewrite ^/chat-api/(.*)\$ /\$1 break; proxy_pass \$chat_backend; proxy_set_header Host "${CHAT_BACKEND_HOST}"; + proxy_set_header Authorization \$http_x_backend_authorization; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme;