OPA Authorization
Rego Policies
This Rego policy governs OIDC authentication by validating user identity, client permissions, and requested scopes against organizational access policies. It enforces context-aware rules based on attributes like user groups, client registration status, and request origin, returning authorization decisions that ensure only compliant authentication requests are honored. The MCP provider uses Rego policies to evaluate every agent tool call in real-time, inspecting the requested MCP resource, the agent's identity, and contextual signals to dynamically determine whether the operation should be permitted, ensuring agents can only access authorized tools and data within approved workflows.
You can define Rego policies in two areas:
OIDC flows for access token policies
MCP Bridge for policies on API calls
Example Rego policies
package orchestrator
# Default deny policy - all requests are denied unless explicitly allowed
default result["allowed"] := false
# Helper rule to extract and decode JWT from Authorization header
# Parses the Bearer token and returns the decoded payload for policy evaluation
jwt_payload := payload if {
auth_header := input.request.http.headers.Authorization
startswith(auth_header, "Bearer ")
token := substring(auth_header, 7, -1)
[_, payload, _] := io.jwt.decode(token)
}
# Following examples can be used when defining a access token policy in the OIDC app
# OAuth scope-based authorization policy
# Allows access if the issued access token contains the tickets:read scope
# Enforces scope-based access control for ticket-related operations
result := {"allowed": true} if {
contains(input.request.oauth.response.access_token.claims.scope, "tickets:read")
}
# OAuth grant type authorization policy
# Allows all authorization_code grant type requests
# Permits standard OAuth 2.0 authorization code flow for user authentication
result := {"allowed": true} if {
input.request.oauth.grant_type == "authorization_code"
}
# Following examples from the Maverics Sandbox found on https://maverics.ai
# MCP tool authorization policy for get_ticket_price & list available seats.
# Allows access to the get_ticket_price tool if the user has admin or user role
# Logs the tool name, client ID, and subject for audit purposes
result["allowed"] if {
print("request made to tool: ", input.request.mcp.tool.params.name)
input.request.mcp.tool.params.name == "get_ticket_price"
print("request made with client of: ", jwt_payload.client_id)
jwt_payload.role in ["admin", "user"]
print("access granted to subject:", jwt_payload.sub)
}
# Allows access to the list_available_seats tool if the user has admin or user role
# Logs the tool name, user role, and subject for audit purposes
result["allowed"] if {
print("request made to tool: ", input.request.mcp.tool.params.name)
input.request.mcp.tool.params.name == "list_available_seats"
print("request made with role of:", jwt_payload.role)
jwt_payload.role in ["admin", "user"]
print("access granted to subject: ", jwt_payload.sub)
}
Input Schema for Rego Policies
When writing Rego policies for MCP Bridge apps, the following input data structure is
available under the input param. The values used for each key below are demonstrative.
{
"source": {
"ip": "192.168.1.100"
},
"request": {
"mcp": {
"type": "tool",
"tool": {
"params": {
"name": "listUsers",
"arguments": {
"exampleBodyKeyAlpha": 10,
"exampleBodyKeyBeta": true
}
}
}
},
"http": {
"host": "mcp.example.com",
"method": "POST",
"path": "/mcp",
"headers": {
"Authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"Content-Type": "application/json",
"User-Agent": "MCP-Client/1.0"
}
},
"oauth": {
"client_id": "mcp-client-123",
"scopes": "read:users write:settings",
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"response": {
"access_token": {
"claims": {
"scope": "read:users write:settings"
}
},
"id_token": {
"claims": {
"sub": "user@example.com"
}
},
"scope": "read:users write:settings",
"expires_in": 3600
}
}
}
}
Output Schema
Rego policies should return data using the below output schema:
result.allowedrepresents whether access should be granted or notresult.internal_messagespecifies details about why the decision was allowed or denied. These details will only show up in log messages.result.external_messagespecifies details about why the decision was allowed or denied. These details will be returned to clients and will also be logged.
{
"result": {
"allowed": true,
"internal_message": "user does not have necessary group membership: missing admin membership",
"external_message": "unauthorized"
}
}