Understand authorization in MCP gateway
Control which authenticated users can access specific Model Context Protocol (MCP) server tools and prompts by setting up authorization in the MCP gateway. The MCP gateway supports authorization approaches including Gateway API policy extensions and other policy engines.
- Authorization evaluation process
-
The following steps represent what happens during an authorization evaluation:
-
Authentication: A user authenticates and receives a JSON Web Token (JWT) with permissions.
-
Tool request: The client makes an MCP tool call, such as
tools/call. -
Request identity check: An
AuthPolicyobject verifies the JWT and extracts authorization claims. -
Authorization check: A Common Expression Language (CEL) expression evaluates the requested tool against the user permissions extracted from the JWT.
-
Access decision: The final step is to
allowordenybased on the authorization check result.
-
|
Important
|
Ensure that there is not a mismatch between your Envoy proxy layer and upstream application layers in performing URL normalization. Envoy serves raw request paths to the However, your application receives the requests. That upstream service can strip redundant slashes or decode URL-encoded values. This means it bypasses your configuration, serving the protected route. Make sure that you configure your upstream service so that you do not unintentionally bypass your own security. For more information, see "Customize your system on path normalization" in the Istio documentation. |
- Workflow for setting up authorization evaluation
-
To create an authorization evaluation, you must complete the following steps:
-
Set up authentication
-
Define permissions
-
Specify access-control roles
-
- Red Hat build of Keycloak workflow example
-
Take the following steps if using Red Hat build of Keycloak:
-
Define your permissions and user claims. Add groups or attributes to the default JWT.
-
Define role-based access by using Red Hat build of Keycloak client roles and group bindings for permission decisions.
-
Give authorization to certain users to control access to individual MCP tools and prompts.
-
Configure your identity provider to include Access Control List (ACL) claims in the tokens it issues.
-
Define complex authorization logic using CEL expressions that evaluate the claims and decide whether to let them through.
-
Configure MCP gateway authorization with an AuthPolicy
The following example demonstrates using a Connectivity Link AuthPolicy custom resource (CR) with Common Expression Language (CEL) to implement role-based access control. You can use the following general pattern for applying the authorization specific to your use case.
|
Note
|
The MCP gateway supports Istio or Gateway API compatible authorization and authenticatio mechanisms. Use the best solution for your OpenShift Container Platform clusters. |
-
You installed MCP gateway.
-
You installed Connectivity Link.
-
You configured a
Gatewayobject with anmcplistener and anmcpslistener. Themcpslistener is required for internaltools/callrouting and authorization. -
You completed authentication procedures, including creating an
AuthPolicyCR on themcplistener. -
You configured your identity provider to include
groupandroleclaims in JSON Web Tokens (JWT). -
The identity provider client IDs match the namespaced
MCPServerRegistrationCR name in the format<namespace>/<mcpserverregistration_name>.
-
Ensure that your identity provider includes the required
groupandroleclaims in the issued JWTs. In the following example, Red Hat build of Keycloak is used:Example issued OAuth token claims:{ "resource_access": { "mcp-ns/arithmetic-mcp-server": { "roles": ["add", "sum", "multiply", "divide", "prompt:math_tutor"] }, "mcp-ns/geometry-mcp-server": { "roles": ["area", "distance", "volume", "prompt:calculate_area"] } } }-
The
"mcp-ns/arithmetic-mcp-server"specification must match the namespaced name of theMCPServerRegistrationCR in the format{namespace}/{name}. For example, if yourMCPServerRegistrationCR is namedarithmetic-mcp-serverand is applied in themcp-nsnamespace, the Red Hat build of Keycloak client ID must bemcp-ns/arithmetic-mcp-server. -
The
"roles": ["add", "sum", "multiply", "divide", "prompt:<value>"]parameter and values specify the roles representing the allowed tools and prompts.
-
-
Configure authorization by creating an
AuthPolicyCR that enforces access control on themcpslistener, as shown in the following example:ImportantThe authorization
AuthPolicyCR must target themcpslistener, not themcplistener. Themcplistener only handles public traffic and has an authentication-onlyAuthPolicyCR.Example tool and prompt access controlAuthPolicyCRapiVersion: kuadrant.io/v1 kind: AuthPolicy metadata: name: <mcp_tool_auth_policy> namespace: <gateway_system> spec: targetRef: group: gateway.networking.k8s.io kind: Gateway name: <mcp_gateway> sectionName: <mcps> rules: authentication: 'sso-server': jwt: issuerUrl: https://<keycloak.example.com>/realms/mcp authorization: 'tool-access-check': when: - predicate: "request.headers.exists(h, h == 'x-mcp-toolname')" patternMatching: patterns: - predicate: | ('tool:' + request.headers['x-mcp-toolname']) in (has(auth.identity.resource_access) && auth.identity.resource_access.exists(p, p == request.headers['x-mcp-servername']) ? auth.identity.resource_access[request.headers['x-mcp-servername']].roles : []) 'prompt-access-check': when: - predicate: "request.headers.exists(h, h == 'x-mcp-promptname')" patternMatching: patterns: - predicate: | ('prompt:' + request.headers['x-mcp-promptname']) in (has(auth.identity.resource_access) && auth.identity.resource_access.exists(p, p == request.headers['x-mcp-servername']) ? auth.identity.resource_access[request.headers['x-mcp-servername']].roles : []) response: unauthenticated: headers: 'WWW-Authenticate': value: Bearer resource_metadata=http://<mcp.example.com:8001>/.well-known/oauth-protected-resource body: value: | { "error": "Unauthorized", "message": "MCP Access denied: Authentication required." } unauthorized: body: value: | { "error": "Forbidden", "message": "MCP Access denied: Insufficient permissions." }-
Replace
metadata.name:with the name of theAuthPolicy. -
Replace
metadata.namespace:with the namespace where theAuthPolicyCR is applied. -
Replace
spec.targetRef.name:with the name of theGatewayCR. -
The
spec.targetRef.sectionName:value must bemcps, which is the internal listener fortool/callauthorization. This listener must exist on yourGatewayobject. -
Authentication: Validates the JWT token using the configured issuer URL. Replace
<keycloak.example.com>with your identity provider hostname. -
Authorization Logic: CEL expressions check if the user role allows access to the requested tool or prompt. The appropriate check is triggered based on the presence of the
x-mcp-toolnameorx-mcp-promptnameheader. -
CEL Breakdown:
-
request.headers['x-mcp-toolname']: The name of the requested Model Context Protocol (MCP) tool. -
request.headers['x-mcp-servername']: The namespaced name of the MCP server matching theMCPServerRegistrationCR. -
request.headers['x-mcp-promptname']: The name of the requested MCP prompt. -
auth.identity.resource_access: The JWT claim containing all roles representing each allowed capability, tool or prompt, that the user can access, grouped by MCP server.
-
-
Response handling: Custom
401and403responses for unauthenticated and unauthorized access attempts.ImportantEnsure that there is not a mismatch between your Envoy proxy layer and upstream application layers in performing URL normalization.
Envoy serves raw request paths to the
wasm-shimcomponent unlessnormalize_pathandmerge_slashesare explicitly enabled in the Envoy HTTP Connection Manager (HCM) settings. This means that you can treat/adminand//adminas distinct paths in your CEL predicates, and so on.However, your application receives the requests. That upstream service can strip redundant slashes or decode URL-encoded values. This means it bypasses your configuration, serving the protected route.
Make sure that you configure your upstream service so that you do not unintentionally bypass your own security. For more information, see "Customize your system on path normalization" in the Istio documentation.
-
-
Apply the
AuthPolicyCR by running the following command:$ oc apply -f <mcp_tool_auth_policy.yaml>Replace
<mcp_tool_auth_policy.yaml>with the name of theAuthPolicyYAML filename. -
Optional. Configure authorization by creating an
AuthPolicyCR using an Open Policy Agent (OPA) rule, as shown in the following example:ExampleAuthPolicyCR with an OPA ruleapiVersion: kuadrant.io/v1 kind: AuthPolicy metadata: name: <mcp_tool_auth_policy> namespace: <gateway_system> spec: targetRef: group: gateway.networking.k8s.io kind: Gateway name: <mcp_gateway> sectionName: <mcps> rules: authentication: 'sso-server': jwt: issuerUrl: https://<keycloak.example.com>/realms/mcp authorization: 'authorized-capabilities': opa: rego: | allow = true capabilities = { "tools": { server: tools | server := object.keys(input.auth.identity.resource_access)[] tools := [substring(r, count("tool:"), -1) | r := input.auth.identity.resource_access[server].roles[] startswith(r, "tool:") ] }, "prompts": { server: prompts | server := object.keys(input.auth.identity.resource_access)[] prompts := [substring(r, count("prompt:"), -1) | r := input.auth.identity.resource_access[server].roles[] startswith(r, "prompt:") ] } } allValues: true #... -
Apply the OPA
AuthPolicyCR by running the following command:$ oc apply -f <mcp_tool_auth_policy.yaml>Replace
<mcp_tool_auth_policy.yaml>with the name of theAuthPolicyYAML filename.
-
Monitor authorization decisions by checking the
AuthPolicyCRstatuswith the following command:$ oc get authpolicy <mcp_tool_auth_policy> -n <gateway_system> -o jsonpath='{.status.conditions[?(@.type=="Enforced")].status}'Example outputTrue -
Check the authorization logs by running the following command:
$ oc logs -n mcp-system -l app.kubernetes.io/name=mcp-gateway