# MCP Gateway RBAC tutorial: Add Role-Based Access Control to your AI agent with Auth0 and Apache Cassandra

[Blog](/blog/)&gt;[Technology](/blog/category/technical/)&gt;MCP Gateway RBAC tutorial: Add Role-Based Access Control to your AI agent with Auth0 and Apache Cassandra 

MCP Gateway RBAC tutorial: Add Role-Based Access Control to your AI agent with Auth0 and Apache Cassandra
=========================================================================================================

September 28, 2026 | By [ Ramya Ravi](https://www.instaclustr.com/blog/author/ramya-ravi/)

 

 

 

 



   [ ](https://x.com/intent/tweet?text=MCP%20Gateway%20RBAC%20tutorial:%20Add%20Role-Based%20Access%20Control%20to%20your%20AI%20agent%20with%20Auth0%20and%20Apache%20Cassandra&url=https://www.instaclustr.com/blog/mcp-gateway-rbac-role-based-access-control-auth0-apache-cassandra/) [ ](https://www.linkedin.com/shareArticle?mini=true&url=https://www.instaclustr.com/blog/mcp-gateway-rbac-role-based-access-control-auth0-apache-cassandra/&title=&summary=MCP%20Gateway%20RBAC%20tutorial:%20Add%20Role-Based%20Access%20Control%20to%20your%20AI%20agent%20with%20Auth0%20and%20Apache%20Cassandra&source=) 

This is a follow-up to “[MCP Gateway tutorial: Connect an AI Agent to Apache Kafka and HTTP Server Backends,](https://www.instaclustr.com/blog/mcp-gateway-tutorial-connect-an-ai-agent-to-apache-kafka-and-http-server-backends/)” extends the original support chatbot with Auth0-based login, role-based access control (RBAC), and an Apache Cassandra backend.

In this MCP Gateway RBAC tutorial, you will configure MCP Gateway to enforce role-based access control for an AI agent. The walkthrough adds Auth0 authentication, maps user roles to MCP Gateway Access Control Lists, connects an Apache Cassandra backend, and shows how each persona receives only the tools their role is allowed to use.

The goal is to support least-privilege access for AI agent tools at the gateway layer: Auth0 authenticates the user, MCP Gateway maps the user’s role to an Access Control List, and the agent receives only the tools that role is permitted to call.

**Get the code:** [MCP-Gateway/shoe-store-support-chatbot-rbac](https://github.com/instaclustr/code-samples/tree/main/MCP-Gateway/shoe-store-support-chatbot-rbac)

Recap: where we left off
------------------------

The previous article built a support chatbot for a shoe e-commerce company, backed by one MCP (Model Context Protocol) Gateway Virtual Server (`supportchatbot`) with two backends: ordersapi (HTTP) providing `get_orders`, and `supporttickets` (Apache Kafka) providing `submit_request`. That version had a single, shared set of tools for every user.

This article adds Auth0 login and three distinct roles (`support-agent`, `merchandiser`, and `readonly-auditor`), plus a third backend type, Cassandra, so each persona gets exactly the access their job calls for, configured entirely at the MCP Gateway.

### **Prerequisites**

From the previous tutorial, you should already have:

- An MCP Gateway Virtual Server (`supportchatbot`).
- The ordersapi (HTTP) backend with a `get_orders` tool.
- The supporttickets (Kafka) backend with a `submit_request` tool.

Everything involving Auth0, OAuth, roles, and Access Control Lists (ACLs) is new in this article.

**Tooling:** Python 3.11+, the uv package manager, and an AWS account with Amazon Bedrock model access enabled for the model you’re using, in the region you’re calling (e.g. us-east-1).

### **The use case: a product catalog with tiered cost visibility**

- **`support-agent`** (customer-facing staff) gets product info (name, price, stock) to help shoppers, plus order lookups and ticket filing.
- **`merchandiser`** (buying/purchasing staff) gets supplier cost and margin data to negotiate pricing, commercially sensitive information relevant to their role, distinct from customer-service tooling.
- **`readonly-auditor`** gets full read visibility across both domains for review purposes, with action (filing a ticket) reserved for support-agent.

What is Role-Based Access Control (RBAC), and why does it matter for an AI agent?
---------------------------------------------------------------------------------

Role-Based Access Control means a user’s identity maps to a fixed role, and every access decision is checked against an explicit allow-list. In MCP Gateway, that role determines which tools an AI agent can discover and call, so each persona gets exactly the access their job requires: no more and no less.

This matters especially for an LLM-backed agent, for three reasons:

- It keeps every session’s capabilities predictable, no matter how the conversation goes. The chatbot handles free-text customer input, and however that conversation unfolds, a support-agent session simply can’t reach `catalogdata_get_supplier_cost`: it’s not part of that role’s tool set, so there’s nothing to accidentally or deliberately reach for. The boundary is a property of the session, not a judgment call the model has to get right in the moment.
- It lets you express two independent things: what a role can see, and what it can do. merchandiser can see cost data but can’t file tickets; `readonly-auditor` can see everything but can’t act on any of it. Splitting visibility from action this way maps naturally onto how organizations already assign responsibility.
- It moves enforcement to a layer that’s easy to reason about. Rather than relying on prompt instructions like “don’t answer cost questions for this role,” which work well in the common case but depend on the model’s judgment every time, the MCP Gateway checks the ACL before a tool call is honored, giving you one clear, auditable place where access is decided.

Architecture: Connecting Auth0, MCP Gateway RBAC, and Cassandra tool access
---------------------------------------------------------------------------

![MCP gateway blog supporting image 1]()

Figure 1. Routing role-based AI agent requests to HTTP, Kafka, and Cassandra backends through the MCP Gateway

### **Step 1: Provision the Cassandra cluster**

In the Instaclustr console, **Create Cluster → Apache Cassandra**, choosing a data center/region and node size appropriate for a demo.

Once the cluster reaches **Running**, note its Data Center ID from the Details page for Step 3.

Use the console’s Connection Info tab and default superuser credentials to connect via cqlsh for the next step.

### **Step 2: Set up the Cassandra schema, seed data, and a least-privilege role**































CREATE KEYSPACE IF NOT EXISTS catalog WITH replication = {'class': 'SimpleStrategy', 'replication\_factor': 3}; CREATE TABLE catalog.products ( productid text PRIMARY KEY, name text, description text, category text, listprice decimal, stockstatus text ); CREATE TABLE catalog.supplier\_costs ( productid text PRIMARY KEY, supplierid text, unitcost decimal, marginpercent decimal, suppliercontact text ); CREATE ROLE mcpgateway\_catalog WITH PASSWORD = '&lt;password&gt;' AND LOGIN = true; GRANT SELECT ON catalog.products TO mcpgateway\_catalog; GRANT SELECT ON catalog.supplier\_costs TO mcpgateway\_catalog; INSERT INTO catalog.products (productid, name, description, category, listprice, stockstatus) VALUES ('SHOE-001', 'Midnight Runner', 'Black low-top running shoe', 'running', 129.99, 'in\_stock'); INSERT INTO catalog.products (productid, name, description, category, listprice, stockstatus) VALUES ('SHOE-002', 'Trailblazer Boot', 'Brown waterproof hiking boot', 'hiking', 189.99, 'backordered'); INSERT INTO catalog.supplier\_costs (productid, supplierid, unitcost, marginpercent, suppliercontact) VALUES ('SHOE-001', 'SUP-88', 42.00, 67.7, 'orders@northstarfootwear.com'); INSERT INTO catalog.supplier\_costs (productid, supplierid, unitcost, marginpercent, suppliercontact) VALUES ('SHOE-002', 'SUP-14', 71.50, 62.4, 'sales@trailgearco.com');

   1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37



  CREATE KEYSPACE IF NOT EXISTS catalog

WITH replication = {'class': 'SimpleStrategy', 'replication\_factor': 3};



CREATE TABLE catalog.products (

 productid text PRIMARY KEY,

 name text,

 description text,

 category text,

 listprice decimal,

 stockstatus text

);



CREATE TABLE catalog.supplier\_costs (

 productid text PRIMARY KEY,

 supplierid text,

 unitcost decimal,

 marginpercent decimal,

 suppliercontact text

);



CREATE ROLE mcpgateway\_catalog WITH PASSWORD = '&lt;password&gt;' AND LOGIN = true;



GRANT SELECT ON catalog.products TO mcpgateway\_catalog;



GRANT SELECT ON catalog.supplier\_costs TO mcpgateway\_catalog;



INSERT INTO catalog.products (productid, name, description, category, listprice, stockstatus)

VALUES ('SHOE-001', 'Midnight Runner', 'Black low-top running shoe', 'running', 129.99, 'in\_stock');



INSERT INTO catalog.products (productid, name, description, category, listprice, stockstatus)

VALUES ('SHOE-002', 'Trailblazer Boot', 'Brown waterproof hiking boot', 'hiking', 189.99, 'backordered');



INSERT INTO catalog.supplier\_costs (productid, supplierid, unitcost, marginpercent, suppliercontact)

VALUES ('SHOE-001', 'SUP-88', 42.00, 67.7, 'orders@northstarfootwear.com');



INSERT INTO catalog.supplier\_costs (productid, supplierid, unitcost, marginpercent, suppliercontact)

VALUES ('SHOE-002', 'SUP-14', 71.50, 62.4, 'sales@trailgearco.com');



   

 

 Connect with the credentials and endpoint from your cluster’s Connection Info tab before running this (see Step 1). `mcpgateway_catalog` is read-only and scoped to just these two tables: a second, defense-in-depth layer of access control underneath the MCP Gateway’s own ACLs.

**Notes:**

- Identifiers are written lowercase throughout, matching how Cassandra represents unquoted identifiers internally: table/column names and named bind markers alike. Writing them lowercase from the start keeps the CQL here consistent with the tool configuration in Step 3.
- Remember to replace &lt;password&gt; in the `CREATE ROLE mcpgateway_catalog` statement with a real, strong password before running this; it’s a placeholder, not a literal value. Keep it somewhere secure, since you’ll need to re-enter it in Step 3 when you create the `catalogdata` backend on the MCP Gateway (that’s where these credentials actually get used).

### **Step 3: Create the Cassandra backend and tools on the MCP Gateway**

On the `supportchatbot` MCP Virtual Server → Create Backend → name catalogdata, type `Cassandra`, using the Data Center ID from Step 1 and the `mcpgateway_catalog` credentials from Step 2.

![MCP gateway blog supporting image 2]()

Figure 2. The `catalogdata` Cassandra backend, showing its Cassandra Data Center ID, Cassandra Username (`mcpgateway_catalog`), and its two configured tools.

Open the new `catalogdata` backend and, under Tools, click Create Tool to create the below two tools.

**`get_product_details`:**































SELECT name, description, category, listprice, stockstatus FROM catalog.products WHERE productid = :productid

   1

2

3



  SELECT name, description, category, listprice, stockstatus



FROM catalog.products WHERE productid = :productid



   

 

 - **Parameter:** `productid` (string)
- **Result fields:** `name` (string), `description` (string), `category` (string), `listprice` (string), `stockstatus` (string)
- **Description:** *“Look up a product’s customer-facing details by product ID, including its name, description, category, list price, and current stock status.”*

**`get_supplier_cost`:**































SELECT supplierid, unitcost, marginpercent, suppliercontact FROM catalog.supplier\_costs WHERE productid = :productid

   1

2

3



  SELECT supplierid, unitcost, marginpercent, suppliercontact



FROM catalog.supplier\_costs WHERE productid = :productid



   

 

 - **Parameter:** `productid` (string)
- **Result fields:** `supplierid` (string), `unitcost` (string), `marginpercent` (string), `suppliercontact` (string)
- **Description:** *“Look up internal supplier cost and margin data for a product by product ID. This is sensitive commercial data; only use it when explicitly asked, and only if you have access to this tool.”*

![MCP gateway blog supporting image 3]()

Figure 3. The `get_product_details` tool definition, showing its CQL query, `productid` parameter, and result fields.

**Notes:**

- Declare Result Field Type as string for any Cassandra decimal column.
- Give every tool a real, descriptive description. Beyond helping the model choose the right tool, AWS Bedrock’s Converse API expects every tool in the list to have one, so it’s worth treating as a required field alongside the CQL and parameters.
- Apache Cassandra forces unquoted bind variable names to lowercase: `:productId` is interpreted internally as `:productid`. That’s why every identifier in this tutorial (table/column names and bind markers alike) is written lowercase from the start, keeping the CQL here consistent with the tool configuration above. If you ever need a case-sensitive parameter name, quote it in the query instead, e.g. `:"productId"`.

### **Step 4: Configure Auth0: application, roles claim, and personas**

**4a. Application and Post-Login Action:** Follow the [Auth0 example configuration](https://www.instaclustr.com/support/documentation/mcp-gateway/mcp-gateway-identity-providers/mcp-gateway-auth0-example-configuration/) guide to:

- Create a Regular Web Application and configure its Allowed Callback URIs / Web Origins.
- Create the Post-Login Action that adds a roles claim when the `mcp_roles` scope is requested, add the `MCP_GATEWAY_CLIENT_ID` secret, deploy it, and apply it on the post-login trigger.
- Note the audience (API Identifier), issuer, and JWKS URI as described there; needed in Step 5.

**4b. Create the three Auth0 Roles:** User Management → Roles → Create Role, once each for:

- `support-agent`
- `merchandiser`
- `readonly-auditor`

**4c. Create three test users, one per role:** User Management → Users → Create User (or reuse existing test users), then on each user’s Roles tab, Assign Roles with the matching role from 4b.

**Note:** Keep role names consistent, case-for-case, across three places: the Auth0 Role, the `user_roles` claim value it produces, and the MCP Gateway ACL’s Role name field in Step 6. Matching these exactly is what makes each persona’s tool list show up correctly.

### **Step 5: Enable OAuth on the MCP Virtual Server**

On the `supportchatbot` MCP Virtual Server’s OAuth configuration section (refer to the [documentation](https://www.instaclustr.com/support/documentation/mcp-gateway/using-mcp-gateway/configure-mcp-tool-access/) for more details), enter the values noted in Step 4a:

- Issuer: `https://<domain>/`
- JWKS URI: `https://<domain>/.well-known/jwks.json`
- Audience: your API Identifier
- Scopes Supported: `mcp_roles`
- Roles Claim Name: [`https://mcp_gateway/user_roles`](https://mcp_gateway/user_roles)

Save/apply.

![MCP gateway blog supporting image 4]()

Figure 4. The `supportchatbot` MCP Virtual Server’s OAuth configuration with all three backends.

### **Step 6: Create the three Access Control Lists**

On the MCP Gateway’s ACL screen, you pick each tool by its bare name; the Backend column disambiguates which tool you mean when the same name could theoretically exist on more than one backend:

**support-agent**

ToolBackend`get_orders``ordersapi``submit_request``supporttickets``get_product_details``catalogdata`**readonly-auditor**

ToolBackend`get_orders``ordersapi``get_product_details``catalogdata``get_supplier_cost``catalogdata`**merchandiser**

ToolBackend`get_product_details``catalogdata``get_supplier_cost``catalogdata`![MCP gateway blog supporting image 5]()

Figure 5. The support-agent Access Control List, showing its explicitly allowed tools.

Each role has a distinct tool set tailored to its purpose: merchandiser focuses entirely on catalog data, while readonly-auditor sees everything the other two combined can see, with action reserved for support-agent.

Once exposed through the `supportchatbot` MCP Virtual Server, the agent sees each tool under a backend-prefixed name (e.g. `catalogdata_get_product_details`) to disambiguate tools coming from different backends; that’s the name that shows up in the app’s “Tools discovered” sidebar later, and in Bedrock’s tool-calling requests, even though you select it by its bare name here.

![MCP gateway blog supporting image 6]()

Figure 6. The completed `supportchatbot` MCP Virtual Server, showing its OAuth configuration, all three backends, and the three role-based Access Control Lists.

Why the chatbot’s code needs no changes?
----------------------------------------

None of this required touching the chatbot’s code, because it was built to discover tools dynamically rather than hardcode them: [`mcp_manager.py`](https://github.com/instaclustr/code-samples/blob/main/MCP-Gateway/shoe-store-support-chatbot-rbac/src/rbac_shoe_bot/mcp_manager.py)‘s `connect()` attaches the user’s access token as a Bearer header once, at connection time, and every session it opens rides on that same authenticated connection for the rest of the login; `get_tool_specs()` then simply asks the MCP Gateway which tools this identity can see and turns whatever comes back into Bedrock’s `toolConfig.tools` format, with the MCP Gateway itself deciding (via the caller’s role and its Access Control List) which tools are included; and [`app.py`](https://github.com/instaclustr/code-samples/blob/main/MCP-Gateway/shoe-store-support-chatbot-rbac/src/rbac_shoe_bot/app.py)‘s sidebar just renders that same list, grouping tool names by server with nothing hardcoded. So, when `catalogdata`‘s two tools were added at the MCP Gateway, they simply started showing up for the roles allowed to see them, with zero code changes anywhere in this repo.

Running the application
-----------------------

The full code for this tutorial is available at [`MCP-Gateway/shoe-store-support-chatbot-rbac`](https://github.com/instaclustr/code-samples/tree/main/MCP-Gateway/shoe-store-support-chatbot-rbac) in the `instaclustr/code-samples` repo.































cp config.example.json config.json # fill in your Auth0 tenant + MCP Gateway URL uv sync export AWS\_ACCESS\_KEY\_ID="..." export AWS\_SECRET\_ACCESS\_KEY="..." export AWS\_DEFAULT\_REGION="us-east-1" uv run streamlit run src/rbac\_shoe\_bot/app.py

   1

2

3

4

5

6

7

8

9

10

11



  cp config.example.json config.json \# fill in your Auth0 tenant + MCP Gateway URL



uv sync



export AWS\_ACCESS\_KEY\_ID="..."



export AWS\_SECRET\_ACCESS\_KEY="..."



export AWS\_DEFAULT\_REGION="us-east-1"



uv run streamlit run src/rbac\_shoe\_bot/app.py



   

 

 This opens the app at [http://localhost:8501](http://localhost:8501/). Compared to the shared-access version from the previous tutorial, `config.json` now includes a populated auth0 section, and the app opens on a **Log in with Auth0** screen.

Click Log in with Auth0 and sign in as one of your test users. To try a different persona, click Log out and log in again; each login prompts for credentials fresh, so you can move between test users freely.

![MCP gateway blog supporting image 7]()

Figure 7. A `readonly-auditor` session in the chatbot, showing its discovered tools in the sidebar and a successful `get_supplier_cost` lookup for `SHOE-002`.

**Notes:**

- Enable model access for `bedrock.model_id` in the Bedrock console for your region ahead of time; it’s a quick, one-time opt-in per model/region, and worth doing before your first run.
- The sidebar’s Tools discovered list is the fastest way to confirm your ACL setup is working as intended; a `merchandiser` session should show only the two `catalogdata_*` tools.

**Demo script**

Using the two seeded products:

- `support-agent`: “What’s the price and stock status of product SHOE-001?” → succeeds. “What do we pay our supplier for it?” → no tool available for this role.
- `merchandiser`: “What’s our cost and margin on SHOE-001?” → succeeds. “Show me my recent orders.” → no tool available for this role.
- `readonly-auditor`: sees both cost and orders, with ticket filing reserved for `support-agent`.

**Note:** These prompts reference product IDs directly rather than descriptions like “black shoes,” since `get_product_details` looks up by exact `productid`. Referencing known IDs keeps the demo focused on RBAC rather than search.

Why this matters beyond the demo
--------------------------------

Authentication, three distinct roles, and a whole new backend technology were all added here through configuration: Auth0 setup, MCP Gateway OAuth config, ACLs, and the Cassandra backend/tools, without changing the chatbot’s own code. That’s the strength of centralizing access control at the MCP Gateway layer: the same access rules apply consistently, however a conversation unfolds, because the boundary lives outside the agent’s own reasoning.

Try it yourself
---------------

If you want to add role-based access control to your own AI agent, you can spin up MCP Gateway and an Apache Cassandra cluster today: [start a free Instaclustr trial](https://console2.instaclustr.com/signup), no credit card required. From there:

- Follow the [MCP Gateway documentation](https://www.instaclustr.com/support/documentation/mcp-gateway/) to provision a Virtual Server and connect your first backend.
- Use the [Auth0 example configuration guide](https://www.instaclustr.com/support/documentation/mcp-gateway/mcp-gateway-identity-providers/mcp-gateway-auth0-example-configuration/) to wire up your own identity provider.
- Clone [MCP-Gateway/shoe-store-support-chatbot-rbac](https://github.com/instaclustr/code-samples/tree/main/MCP-Gateway/shoe-store-support-chatbot-rbac) and swap in your own tools, roles, and backends: the same three steps (Auth0 roles claim, Gateway OAuth config, per-role ACLs) apply no matter what your agent actually does.

If you’d rather start from the simpler, single-role version, the [previous tutorial](https://www.instaclustr.com/blog/mcp-gateway-tutorial-connect-an-ai-agent-to-apache-kafka-and-http-server-backends/) walks through connecting an agent to Kafka and HTTP backends without the auth layer.

 

### About the author

**[Ramya Ravi](https://www.instaclustr.com/blog/author/ramya-ravi/)** | Developer Advocate

Ramya Ravi is a Developer Advocate at NetApp, specializing in open source and AI technologies. She connects Instaclustr supported technologies with emerging AI use cases, creating technical content, demos, and guides to help developers build and deploy AI applications. She has previously worked for Intel and TCS. In her previous role at Intel, she drove AI/ML ecosystem growth by developing tutorials and resources that supported thousands of AI developers across Reddit, the PyTorch community, and developer ecosystem forums.

 

 [ Add Instaclustr as a preferred source on Google ](https://google.com/preferences/source?q=instaclustr.com)



 

 ![mail icon]()#### Get the latest articles for open sourceIn your inbox

 <a class="btn btn-primary btn-popup text-dark" href="">Sign up now</a> 

 

 

 

  ### Related content

 [ Zero Downtime Migration to Instaclustr 

 

 Yes, we can migrate existing Cassandra clusters to Instaclustr without any downtime. Here's what to expect from the process... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/zero-downtime-migration-to-instaclustr/) 

 [ Workflow Comparison: Uber Cadence vs Netflix Conductor 

 

 When choosing what’s right for your company’s opensource workflow needs it is important to know the difference and similarities ... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/workflow-comparison-uber-cadence-vs-netflix-conductor/) 

 [ Will Your Cassandra Database Project Succeed?: The New Stack 

 

 Open source Apache Cassandra® continues to stand out as an enterprise-proven solution for organizations seeking high availability... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/will-your-cassandra-database-project-succeed-the-new-stack/) 

 

  <a class="close-modal" href="">×</a>Sign upto ourNewsletter
-----------------------
