🤖 Secure your AI agents from pilot to production with Maverics. (Try the Sandbox)

OPA Authorization

Prev Next

Rego Policies

The Orchestrator uses Open Policy Agent (OPA) to evaluate Rego policies. Rego policies can be utilized across different application types in order to provide fine grained access to protected resources.

Rego policy on OIDC apps can be used to validate user identity, client permissions, and requested scopes against organizational access policies. Rego policies enforce 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 Proxy app and MCP Bridge app use 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. This ensures agents can only access authorized tools and data within approved workflows.

Rego policies can be defined in:

Example Rego Policies

OIDC App Example

The below policy ensures that scopes are only reduced during token exchange flows. That is, the requested scopes in the token exchange request must be a sub-set of the scopes in the subject_token.

This policy only applies to token exchange grants. All other grant types are allowed without restriction.

package orchestrator

default result["allowed"] := false

# Allow all non-token-exchange grant types.
result["allowed"] if {
	input.request.oauth.grant_type != "urn:ietf:params:oauth:grant-type:token-exchange"
}

# Parse scopes from space-separated strings into sets.
requested_scopes := {s | some s in split(input.request.oauth.scope, " "); s != ""}
subject_scopes := {s | some s in split(input.request.oauth.subject_token.claims.scope, " "); s != ""}

# For token exchange grants, allow if requested scopes are a subset of subject token scopes.
result["allowed"] if {
	input.request.oauth.grant_type == "urn:ietf:params:oauth:grant-type:token-exchange"
	print("requested_scopes:", requested_scopes)
	print("subject_scopes:", subject_scopes)
	count(requested_scopes - subject_scopes) == 0
}

result["internal_message"] := "requested scopes exceed subject_token scopes" if {
	not result.allowed
}

result["external_message"] := "invalid_scope" if {
	not result.allowed
}

MCP Proxy App & MCP Bridge App Example

The below sample policy grants access to the get_ticket_price tool if the authorization header on the request includes the tickets:read scope.

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)
}

# Allows access to the get_ticket_price tool if the token contains the tickets:read scope.
# 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)
	contains(jwt_payload.scope, "tickets:read")
	
	print("access granted to subject:", jwt_payload.sub)
}

result["internal_message"] := "access is only permitted to the 'get_ticket_price' or 'list_available_seats' tools" if {
	not result.allowed
}

result["external_message"] := "unauthorized, contact support@example.com" if {
	not result.allowed
}

Input Schema for Rego Policies

The following data is available to policy and can be found under the input param. The values used for each key below are demonstrative.

OIDC App Policy Schema

{
  "source": {
    "ip": "192.168.1.100"
  },
  "request": {
    "http": {
      "host": "mcp.example.com",
      "method": "POST",
      "path": "/token",
      "headers": {
        "Content-Type": "application/x-www-form-urlencoded",
        "User-Agent": "Go-http-client/1.1"
      }
    },
    "oauth": {
      "client_id": "325859cf-5bbe-492f-b9e7-929f2bed1230",
      "scopes": "read:users write:settings",
      "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
      "subject_token": {
        "claims": {
          "scope": "api:read api:write"
        }
      },
      "actor_token": {
        "claims": {
          "scope": "openid"
        }
      },
      "response": {
        "access_token": {
          "claims": {
            "scope": "read:users write:settings",
            "aud": "https://api.example.com",
            "client_id": "325859cf-5bbe-492f-b9e7-929f2bed1230"
          }
        },
        "scope": "read:users write:settings",
        "expires_in": 3600
      }
    }
  }
}

MCP Proxy App and MCP Bridge App Policy Schema

{
  "source": {
    "ip": "192.168.1.100"
  },
  "request": {
    "http": {
      "host": "mcp.example.com",
      "method": "POST",
      "path": "/mcp",
      "headers": {
        "Authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "Content-Type": "application/json",
        "User-Agent": "MCP-Client/1.0"
      }
    },
    "mcp": {
      "type": "tool",
      "tool": {
        "params": {
          "name": "listUsers",
          "arguments": {
            "exampleBodyKeyAlpha": 10,
            "exampleBodyKeyBeta": true
          }
        }
      }
    }
  }
}

Output Schema

Rego policies should return data using the below output schema:

  • result.allowed represents whether access should be granted or not

  • result.internal_message specifies details about why the decision was allowed or denied. These details will only show up in log messages.

  • result.external_message specifies 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"
  }
}