|
| 1 | +# Client Architecture |
| 2 | + |
| 3 | +This document describes the three OIDC clients configured in Keycloak for McpServer authentication and their respective OAuth 2.0 flows. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +McpServer uses Keycloak as its identity provider with three distinct client configurations: |
| 8 | + |
| 9 | +1. **mcp-server-api** — Confidential client for JWT validation on the API server |
| 10 | +2. **mcp-director** — Public client for CLI authentication via Device Authorization Flow |
| 11 | +3. **mcp-web** — Confidential client for browser-based authentication via Authorization Code Flow |
| 12 | + |
| 13 | +All three clients are configured to work with the `mcpserver` realm and include the `mcp-server-api` audience in their tokens. |
| 14 | + |
| 15 | +## 1. mcp-server-api (API Client) |
| 16 | + |
| 17 | +### Purpose |
| 18 | +This client validates JWT tokens issued by Keycloak on the McpServer API endpoints. It does not directly authenticate users but provides the API server with the credentials needed to verify tokens. |
| 19 | + |
| 20 | +### Configuration |
| 21 | +- **Client Type**: Confidential (has a client secret) |
| 22 | +- **Client ID**: `mcp-server-api` |
| 23 | +- **Service Accounts**: Enabled (for token introspection if needed) |
| 24 | +- **Standard Flow**: Disabled (does not authenticate users directly) |
| 25 | +- **Direct Access Grants**: Disabled |
| 26 | +- **Audience**: Self-referencing (tokens issued to other clients must include `mcp-server-api` as audience) |
| 27 | + |
| 28 | +### Usage in Code |
| 29 | +The API server validates incoming JWT tokens using the client secret and authority URL configured in `appsettings.yaml`: |
| 30 | + |
| 31 | +```yaml |
| 32 | +Mcp: |
| 33 | + Auth: |
| 34 | + Authority: http://localhost:7080/realms/mcpserver |
| 35 | + Audience: mcp-server-api |
| 36 | + ClientSecret: <retrieved-from-keycloak> |
| 37 | + RequireHttpsMetadata: false |
| 38 | +``` |
| 39 | +
|
| 40 | +**Location**: `lib/McpServer/appsettings.yaml` (or environment variables/user secrets) |
| 41 | + |
| 42 | +**Code Reference**: `lib/McpServer/src/McpServer.Services/Options/OidcAuthOptions.cs` |
| 43 | + |
| 44 | +## 2. mcp-director (Director CLI Client) |
| 45 | + |
| 46 | +### Purpose |
| 47 | +This client authenticates CLI users via the OAuth 2.0 Device Authorization Flow. Users run the Director CLI, receive a user code, navigate to a verification URL in their browser, and authenticate through Keycloak. Once authenticated, the CLI receives an access token. |
| 48 | + |
| 49 | +### Configuration |
| 50 | +- **Client Type**: Public (no client secret required) |
| 51 | +- **Client ID**: `mcp-director` |
| 52 | +- **Device Authorization Grant**: Enabled |
| 53 | +- **Standard Flow**: Disabled |
| 54 | +- **Direct Access Grants**: Disabled |
| 55 | +- **Service Accounts**: Disabled |
| 56 | + |
| 57 | +### Protocol Mappers |
| 58 | +- **mcp-server-api-audience**: Adds `mcp-server-api` to the `aud` claim |
| 59 | +- **realm-roles**: Maps user realm roles into the `realm_roles` claim |
| 60 | + |
| 61 | +### OAuth 2.0 Device Flow |
| 62 | + |
| 63 | +The Device Authorization Flow is a multi-step process designed for CLI tools and devices without a web browser: |
| 64 | + |
| 65 | +1. **Device Authorization Request**: The CLI calls `/auth/device` (proxied to Keycloak) with the client ID and scopes |
| 66 | +2. **User Code Displayed**: Keycloak returns a `user_code`, `verification_uri`, and `device_code` |
| 67 | +3. **User Authentication**: The user navigates to the `verification_uri` in a browser, enters the `user_code`, and authenticates with Keycloak |
| 68 | +4. **Token Polling**: The CLI polls `/auth/token` (proxied to Keycloak) with the `device_code` until the user completes authentication |
| 69 | +5. **Token Issued**: Once authenticated, Keycloak returns an `access_token` and `refresh_token` |
| 70 | + |
| 71 | +The Director CLI caches tokens locally and automatically refreshes them when expired. |
| 72 | + |
| 73 | +### Usage in Code |
| 74 | + |
| 75 | +**CLI Implementation**: `src/McpServer.Director/Auth/OidcAuthService.cs` |
| 76 | + |
| 77 | +The Director CLI discovers OIDC configuration from the MCP server: |
| 78 | + |
| 79 | +```bash |
| 80 | +director auth login |
| 81 | +``` |
| 82 | + |
| 83 | +This: |
| 84 | +1. Calls `GET /auth/config` on the MCP server to retrieve authority and client ID |
| 85 | +2. Initiates the Device Flow by calling `POST /auth/device` |
| 86 | +3. Displays the user code and verification URI |
| 87 | +4. Polls `POST /auth/token` until the user completes authentication |
| 88 | +5. Caches the token locally in `~/.mcp-director/token.json` |
| 89 | + |
| 90 | +**Configuration Discovery**: `lib/McpServer/src/McpServer.Support.Mcp/Controllers/AuthConfigController.cs` |
| 91 | + |
| 92 | +The MCP server exposes `/auth/config` to provide OIDC metadata to CLI clients: |
| 93 | + |
| 94 | +```json |
| 95 | +{ |
| 96 | + "enabled": true, |
| 97 | + "authority": "http://localhost:7080/realms/mcpserver", |
| 98 | + "clientId": "mcp-director", |
| 99 | + "scopes": "openid profile email", |
| 100 | + "deviceAuthorizationEndpoint": "http://localhost:7147/auth/device", |
| 101 | + "tokenEndpoint": "http://localhost:7147/auth/token" |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +The MCP server proxies Device Flow requests to Keycloak so CLI clients only need to know the MCP server URL (e.g., `http://localhost:7147`), not the Keycloak URL. |
| 106 | + |
| 107 | +## 3. mcp-web (Web UI Client) |
| 108 | + |
| 109 | +### Purpose |
| 110 | +This client authenticates browser-based users via the OAuth 2.0 Authorization Code Flow. Users navigate to the McpServer Web UI, click "Login", are redirected to Keycloak for authentication, and redirected back to the Web UI with an authorization code that is exchanged for tokens. |
| 111 | + |
| 112 | +### Configuration |
| 113 | +- **Client Type**: Confidential (has a client secret) |
| 114 | +- **Client ID**: `mcp-web` |
| 115 | +- **Standard Flow**: Enabled (Authorization Code Flow) |
| 116 | +- **Direct Access Grants**: Disabled |
| 117 | +- **Service Accounts**: Disabled |
| 118 | +- **Redirect URIs**: |
| 119 | + - `http://localhost:*` (for local development) |
| 120 | + - `<MCP_SERVER_URL>/*` (for deployed environments) |
| 121 | +- **Web Origins**: |
| 122 | + - `http://localhost:*` |
| 123 | + - `<MCP_SERVER_URL>` |
| 124 | + |
| 125 | +### Protocol Mappers |
| 126 | +- **mcp-server-api-audience**: Adds `mcp-server-api` to the `aud` claim |
| 127 | +- **realm-roles**: Maps user realm roles into the `realm_roles` claim |
| 128 | + |
| 129 | +### OAuth 2.0 Authorization Code Flow |
| 130 | + |
| 131 | +The Authorization Code Flow is the standard OAuth 2.0 flow for browser-based applications: |
| 132 | + |
| 133 | +1. **Login Initiated**: User clicks "Login" in the Web UI |
| 134 | +2. **Redirect to Keycloak**: User is redirected to Keycloak's authorization endpoint |
| 135 | +3. **Authentication**: User enters credentials in Keycloak |
| 136 | +4. **Authorization Code**: Keycloak redirects back to the Web UI with an authorization code |
| 137 | +5. **Token Exchange**: The Web UI backend exchanges the authorization code for an access token and refresh token using the client secret |
| 138 | +6. **Token Storage**: Tokens are stored in an HTTP-only session cookie |
| 139 | + |
| 140 | +### Usage in Code |
| 141 | + |
| 142 | +**Web UI Configuration**: `src/McpServer.Web/appsettings.Development.json` |
| 143 | + |
| 144 | +```json |
| 145 | +{ |
| 146 | + "Authentication": { |
| 147 | + "Schemes": { |
| 148 | + "OpenIdConnect": { |
| 149 | + "Authority": "http://localhost:7080/realms/mcpserver", |
| 150 | + "ClientId": "mcp-web", |
| 151 | + "ClientSecret": "<retrieved-from-keycloak>" |
| 152 | + } |
| 153 | + } |
| 154 | + } |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +**Code Reference**: `src/McpServer.Web/Program.cs` |
| 159 | + |
| 160 | +The Web UI uses ASP.NET Core's OpenID Connect middleware to handle the Authorization Code Flow automatically. The middleware: |
| 161 | +- Redirects unauthenticated requests to Keycloak |
| 162 | +- Handles the callback with the authorization code |
| 163 | +- Exchanges the code for tokens using the client secret |
| 164 | +- Stores tokens in a secure session cookie |
| 165 | +- Automatically refreshes tokens when expired |
| 166 | + |
| 167 | +The Web UI can also discover OIDC configuration from the MCP server by setting `DiscoverAuthorityFromMcpAuthConfig: true` in `appsettings.json`. |
| 168 | + |
| 169 | +## Token Validation |
| 170 | + |
| 171 | +All three clients issue tokens that include: |
| 172 | +- **Audience (`aud`)**: `mcp-server-api` (via the audience mapper) |
| 173 | +- **Realm Roles (`realm_roles`)**: User's assigned roles (`admin`, `agent-manager`, `viewer`) |
| 174 | +- **Subject (`sub`)**: Unique user identifier |
| 175 | +- **Username (`preferred_username`)**: User's username |
| 176 | +- **Email (`email`)**: User's email address |
| 177 | + |
| 178 | +The MCP server validates these tokens using the `mcp-server-api` client credentials and the Keycloak authority URL. |
| 179 | + |
| 180 | +## Security Considerations |
| 181 | + |
| 182 | +### mcp-server-api |
| 183 | +- **Client secret must be kept secure** and never exposed in client applications |
| 184 | +- Configured via environment variables, user secrets, or secure configuration management |
| 185 | +- Only used server-side for token validation |
| 186 | + |
| 187 | +### mcp-director |
| 188 | +- **Public client** (no client secret) |
| 189 | +- Device Flow ensures user authentication happens in a browser, not in the CLI |
| 190 | +- Tokens cached locally in `~/.mcp-director/token.json` with appropriate file permissions |
| 191 | + |
| 192 | +### mcp-web |
| 193 | +- **Client secret must be kept secure** and never exposed to the browser |
| 194 | +- Stored server-side only (in `appsettings.Development.json` or user secrets) |
| 195 | +- Tokens stored in HTTP-only, secure session cookies (not accessible to JavaScript) |
| 196 | + |
| 197 | +## Next Steps |
| 198 | + |
| 199 | +- [Setup Scripts](./SetupScripts.md) — Running the Keycloak setup automation |
| 200 | +- [Helper Scripts](./HelperScripts.md) — Syncing client secrets into application configuration |
0 commit comments