Skip to content

Commit ba0dc32

Browse files
authored
Feature/keycloak vscode redirects (Azure-Samples#22)
* keycloak and vscode * Add VS Code GitHub Copilot integration docs for Keycloak OAuth - Add "Use Keycloak OAuth MCP server with GitHub Copilot" section to README - Add step-by-step instructions for connecting VS Code to deployed MCP server - Include screenshots for authentication flow (allow access, sign-in, redirect) - Add Spanish translation of the new section to spanish/README.md - Configure VS Code redirect URIs in Keycloak realm for DCR support - Update infra and keycloak configs for VS Code OAuth redirect handling * update with credit * ruff run * ruff run again * attemtp to get audience validation * Refactor Keycloak auth with scope injection and proxying * add openid scope * remove mcp url * ruff * clean up to realm so it has only what we need * addresses feedback from Pamela and Copilot
1 parent 65cf218 commit ba0dc32

File tree

18 files changed

+416
-239
lines changed

18 files changed

+416
-239
lines changed

.vscode/mcp.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"0.0.0.0:5678",
2828
"servers/basic_mcp_stdio.py"
2929
]
30-
},
30+
}
31+
3132
},
3233
"inputs": []
33-
}
34+
}

README.md

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -432,23 +432,46 @@ This project supports deploying with OAuth 2.0 authentication using Keycloak as
432432

433433
Login with `admin` and your configured password.
434434

435-
### Testing with the agent
436435

437-
1. Generate the local environment file (automatically created after `azd up`):
436+
### Use Keycloak OAuth MCP server with GitHub Copilot
438437

439-
```bash
440-
./infra/write_env.sh
441-
```
438+
The Keycloak deployment supports Dynamic Client Registration (DCR), which allows VS Code to automatically register as an OAuth client. VS Code redirect URIs are pre-configured in the Keycloak realm.
439+
440+
To use the deployed MCP server with GitHub Copilot Chat:
441+
442+
1. To avoid conflicts, stop the MCP servers from `mcp.json` and disable the expense MCP servers in GitHub Copilot Chat tools.
443+
2. Select "MCP: Add Server" from the VS Code Command Palette
444+
3. Select "HTTP" as the server type
445+
4. Enter the URL of the MCP server from `azd env get-value MCP_SERVER_URL`
446+
5. You should see a Keycloak authentication screen open in your browser. Select "Allow access":
442447

443-
This creates `.env` with `KEYCLOAK_REALM_URL`, `MCP_SERVER_URL`, and Azure OpenAI settings.
448+
![Keycloak allow access screen](screenshots/kc-allow-1.jpg)
444449

445-
2. Run the agent:
450+
6. Sign in with a Keycloak user (e.g., `testuser` / `testpass` for the pre-configured demo user):
446451

447-
```bash
448-
uv run agents/agentframework_http.py
452+
![Keycloak sign-in screen](screenshots/kc-signin-2.jpg)
453+
454+
7. After authentication, the browser will redirect back to VS Code:
455+
456+
![VS Code redirect after Keycloak sign-in](screenshots/kc-redirect-3.jpg)
457+
458+
8. Enable the MCP server in GitHub Copilot Chat tools:
459+
460+
![Select MCP tools in GitHub Copilot](screenshots/kc-select-tools-4.jpg)
461+
462+
9. Test it with an expense tracking query:
463+
464+
```text
465+
Log expense for 75 dollars of office supplies on my visa last Friday
449466
```
450467

451-
The agent automatically detects `KEYCLOAK_REALM_URL` in the environment and authenticates via DCR + client credentials. On success, it will add an expense and print the result.
468+
![Example GitHub Copilot Chat with Keycloak auth](screenshots/kc-chat-5.jpg)
469+
470+
10. Verify the expense was added by checking the Cosmos DB `user-expenses` container in the Azure Portal or by asking GitHub Copilot Chat:
471+
472+
```text
473+
Show me my expenses from last week
474+
```
452475
453476
### Known limitations (demo trade-offs)
454477

agents/keycloak_auth.py

Lines changed: 0 additions & 120 deletions
This file was deleted.

infra/http-routes.bicep

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,14 @@ resource httpRouteConfig 'Microsoft.App/managedEnvironments/httpRouteConfigs@202
2929
parent: containerEnv
3030
properties: {
3131
rules: [
32-
// Route /auth/* to Keycloak (strip /auth prefix since Keycloak serves at root)
33-
// Using pathSeparatedPrefix ensures /auth doesn't match /authentication
3432
{
3533
description: 'Keycloak Authentication Server'
3634
routes: [
3735
{
3836
match: {
3937
pathSeparatedPrefix: '/auth'
4038
}
41-
action: {
42-
prefixRewrite: '/'
43-
}
39+
action: {}
4440
}
4541
]
4642
targets: [
@@ -49,17 +45,14 @@ resource httpRouteConfig 'Microsoft.App/managedEnvironments/httpRouteConfigs@202
4945
}
5046
]
5147
}
52-
// Route everything else to MCP server (catch-all)
5348
{
5449
description: 'MCP Expenses Server'
5550
routes: [
5651
{
5752
match: {
5853
prefix: '/'
5954
}
60-
action: {
61-
prefixRewrite: '/'
62-
}
55+
action: {}
6356
}
6457
]
6558
targets: [

infra/main.bicep

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -782,8 +782,7 @@ module server 'server.bicep' = {
782782
openTelemetryPlatform: openTelemetryPlatform
783783
exists: serverExists
784784
// Keycloak authentication configuration (only when enabled)
785-
keycloakRealmUrl: useKeycloak ? '${keycloak!.outputs.uri}/realms/${keycloakRealmName}' : ''
786-
keycloakTokenIssuer: useKeycloak ? '${keycloakMcpServerBaseUrl}/realms/${keycloakRealmName}' : ''
785+
keycloakRealmUrl: useKeycloak ? '${keycloak!.outputs.uri}/auth/realms/${keycloakRealmName}' : ''
787786
keycloakMcpServerBaseUrl: useKeycloak ? keycloakMcpServerBaseUrl : ''
788787
keycloakMcpServerAudience: keycloakMcpServerAudience
789788
// Azure/Entra ID OAuth Proxy authentication configuration (only when enabled)
@@ -810,7 +809,7 @@ module agent 'agent.bicep' = {
810809
openAiDeploymentName: openAiDeploymentName
811810
openAiEndpoint: openAi.outputs.endpoint
812811
mcpServerUrl: useKeycloak ? 'https://mcproutes.${containerApps.outputs.defaultDomain}/mcp' : '${server.outputs.uri}/mcp'
813-
keycloakRealmUrl: useKeycloak ? '${keycloak.outputs.uri}/realms/${keycloakRealmName}' : ''
812+
keycloakRealmUrl: useKeycloak ? '${keycloak.outputs.uri}/auth/realms/${keycloakRealmName}' : ''
814813
exists: agentExists
815814
}
816815
}
@@ -946,9 +945,10 @@ output KEYCLOAK_MCP_SERVER_BASE_URL string = useKeycloak ? keycloakMcpServerBase
946945

947946
// Keycloak and MCP Server routing outputs (only populated when mcpAuthProvider is keycloak)
948947
output KEYCLOAK_REALM_URL string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/realms/${keycloakRealmName}' : ''
949-
output KEYCLOAK_ADMIN_CONSOLE string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/admin' : ''
948+
output KEYCLOAK_ADMIN_CONSOLE string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/admin/master/console' : ''
950949
output KEYCLOAK_DIRECT_URL string = keycloak.outputs.uri
951-
output KEYCLOAK_TOKEN_ISSUER string = useKeycloak ? '${keycloakMcpServerBaseUrl}/realms/${keycloakRealmName}' : ''
950+
output KEYCLOAK_TOKEN_ISSUER string = useKeycloak ? '${keycloakMcpServerBaseUrl}/auth/realms/${keycloakRealmName}' : ''
951+
output KEYCLOAK_AGENT_REALM_URL string = useKeycloak ? '${keycloak!.outputs.uri}/auth/realms/${keycloakRealmName}' : ''
952952

953953
// Auth provider for env scripts
954954
output MCP_AUTH_PROVIDER string = mcpAuthProvider

infra/server.bicep

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ param applicationInsightsConnectionString string = ''
2222
])
2323
param openTelemetryPlatform string = 'appinsights'
2424
param keycloakRealmUrl string = ''
25-
param keycloakTokenIssuer string = ''
2625
param keycloakMcpServerAudience string = 'mcp-server'
2726
param keycloakMcpServerBaseUrl string = ''
2827
param entraProxyClientId string = ''
@@ -112,10 +111,6 @@ var keycloakEnv = !empty(keycloakRealmUrl) ? [
112111
name: 'KEYCLOAK_REALM_URL'
113112
value: keycloakRealmUrl
114113
}
115-
{
116-
name: 'KEYCLOAK_TOKEN_ISSUER'
117-
value: !empty(keycloakTokenIssuer) ? keycloakTokenIssuer : keycloakRealmUrl
118-
}
119114
{
120115
name: 'KEYCLOAK_MCP_SERVER_AUDIENCE'
121116
value: keycloakMcpServerAudience

infra/write_env.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ $KEYCLOAK_REALM_URL = Get-AzdValue KEYCLOAK_REALM_URL
4949
if ($KEYCLOAK_REALM_URL -and $KEYCLOAK_REALM_URL -ne "") {
5050
Add-Content -Path $ENV_FILE_PATH -Value "KEYCLOAK_REALM_URL=$KEYCLOAK_REALM_URL"
5151
Write-EnvIfSet KEYCLOAK_TOKEN_ISSUER
52+
Write-EnvIfSet KEYCLOAK_AGENT_REALM_URL
5253
}
5354

5455
# Entra proxy env vars (only if ENTRA_PROXY_AZURE_CLIENT_ID is set)

infra/write_env.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ KEYCLOAK_REALM_URL=$(get_azd_value KEYCLOAK_REALM_URL)
5555
if [ -n "$KEYCLOAK_REALM_URL" ]; then
5656
echo "KEYCLOAK_REALM_URL=${KEYCLOAK_REALM_URL}" >> "$ENV_FILE_PATH"
5757
write_env_if_set KEYCLOAK_TOKEN_ISSUER
58+
write_env_if_set KEYCLOAK_AGENT_REALM_URL
5859
fi
5960

6061
# Entra proxy env vars (only if ENTRA_PROXY_AZURE_CLIENT_ID is set)

keycloak/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
2222
# Start in dev mode with H2 database (still uses pre-built themes)
2323
# --proxy-headers=xforwarded tells Keycloak it's behind a reverse proxy that sets X-Forwarded-* headers
2424
# --hostname-strict=false allows dynamic hostname resolution from proxy headers
25+
# --http-relative-path=/auth sets the base path so Keycloak serves all content under /auth/*
2526
# --import-realm imports the MCP realm on startup
26-
CMD ["start-dev", "--http-port=8080", "--proxy-headers=xforwarded", "--hostname-strict=false", "--import-realm"]
27+
# --import-strategy=overwrite-existing ensures realm.json changes are applied even if the realm exists
28+
CMD ["start-dev", "--http-port=8080", "--proxy-headers=xforwarded", "--hostname-strict=false", "--http-relative-path=/auth", "--import-realm", "--import-strategy=overwrite-existing"]

0 commit comments

Comments
 (0)