---
title: "Integrating Claude Code with Auth0 APIs"
description: "Pair Claude Code with the Auth0 MCP Server to audit JWT configs, scaffold M2M apps, and refactor auth code from the terminal — with scoped access and security guardrails."
authors:
  - name: "Manish Hatwalne"
    url: "https://auth0.com/blog/authors/manish-hatwalne/"
date: "Sep 29, 2026"
category: "AI"
tags: ["mcp server", "mcp"]
url: "https://auth0.com/blog/integrating-claude-code-with-auth0-apis/"
---

# Integrating Claude Code with Auth0 APIs

<style>
    
  /* Increases spacing between bullet points */   
    li {padding-bottom: .7em; }

</style>
Authentication and authorization are a core part of nearly every web application, and it is also one of the areas that changes most often. Developers regularly update token expiry settings, callback URLs, or API permissions for new features. The changes are usually small, but the workflow around them is not. You end up bouncing between your editor, the Auth0 dashboard, and the docs just to push through a minor update. Increasingly, that editor is a coding agent like Claude Code, which raises the question: what if you could handle those updates without ever leaving Claude CLI?

Claude Code is a terminal-based AI agent (also available as a VS code extension) that you can pair with the Auth0 MCP Server, which exposes Auth0's Management API as native third-party tools. This pairing gives you an assistant that can scaffold, refactor, and update authentication integrations with full context across your codebase and Auth0 tenant. But giving an AI agent access to sensitive authentication infrastructure raises a fair question: *"How do you do that without opening up new security risks?"*

In this tutorial, you will set up Claude Code with the Auth0 MCP Server, create a scoped machine-to-machine credential flow and set up security guardrails that let an AI agent interact safely with your authentication layer.

## How Claude Code and Auth0 MCP Work Together

Based on your goal, [Claude Code](https://claude.com/product/claude-code) works through multiple steps (like reading/writing code, running shell commands etc.) autonomously, and asks for confirmation at decision points that warrant human review.

[The Auth0 MCP Server](https://auth0.com/docs/get-started/auth0-mcp-server) implements the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) to expose [Auth0's Management API](https://auth0.com/docs/api/management/v2) as a set of tools that any compatible AI agent can invoke directly. Instead of constructing raw HTTP requests or relying on potentially stale training data about APIs, Claude Code calls well-defined tools with typed inputs and outputs. The MCP Server handles calls to the Management API, and the agent operates strictly within the permissions you grant. If you run it in the `read-only` mode, the coding agent will not be able to make any changes in your Auth0 tenant.

Together, they bring authentication workflows closer to your code. Claude Code can understand both your application and your Auth0 tenant, then work across them in a single session. 

## Setting Up the Auth0 MCP Server with Scoped Access

In addition to [Claude Desktop, Cursor, and Windsurf](https://auth0.com/docs/get-started/auth0-mcp-server/getting-started-with-auth0-mcp-server#installation-and-configuration), Auth0 MCP server also works with Claude seamlessly.

### Prerequisites

You will need the following to complete this tutorial: 

- [Node.js](https://nodejs.org/en) v18 or higher (`node --version` to check)  
- [Claude Code CLI](https://claude.com/product/claude-code) installed  
- An active Auth0 account with **administrative permissions** on the target tenant  
- Your tenant domain handy (for example, `dev-xxxx.us.auth0.com`)  
- Demo code from this [GitHub repo](https://github.com/manishh/Claude-Code-With-Auth0-MCP/tree/suboptimal-implementation) for your reference (optional).

### Install and initialize

Create a dedicated working directory:

```bash  
mkdir Claude-Code-With-Auth0-MCP && cd Claude-Code-With-Auth0-MCP  
```

Then initialize the Auth0 MCP Server:

```bash  
npx @auth0/auth0-mcp-server init  
```

While this tutorial selected all MCP scopes for the demo, you should *only* use the scopes you actually need. After install and initialization, it kicks off a browser-based Auth0 authentication flow as shown below:

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/1x6foO3Qs6YbnbD9MZfq5d/fd5d15db1262f37fd5016a4f644ba7cf/Auth0_MCP_Installation.png" alt="Auth0 MCP Installation" style="width:80%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

Confirm the code displayed in the browser is same as the one shown in your terminal: 

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/75Ox43lNQCSY2ATNXMN3Le/6eb4a2aec80651cb43a5fdaa57fb2c49/Auth0_Device_Authorization.png" alt="Auth0 Device Authorization" style="width:80%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

Grant requested permissions:

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/2x5eKXFGHQRQfBvEoIYO7i/ecaf791b34c20583ff4f972e52cf3c58/Auth0MCPPermissions.png" alt="Auth0 MCP Permissions" style="width:80%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

Once authorized, credentials are stored securely in your OS keychain, never in plain text or project files.

### Why use device authorization flow instead of static secrets

The Auth0 MCP Server uses the [OAuth 2.0 Device Authorization Flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow) (RFC [8628](https://www.rfc-editor.org/info/rfc8628/)) rather than a static API key or client secret. Static secrets have to live somewhere (usually a config or `.env` file), which risks exposing them in source control. OAuth access tokens sidestep this: they represent a delegated authorization, carry restricted scopes, and expire automatically. The Device Authorization Flow adds a human login step to token issuance, giving you an audit trail tied to an interactive session and instant revocation from the Auth0 dashboard. The MCP server stores the resulting token in your OS keychain, so it never touches source control.

The one trade-off is that the flow requires a browser-based login step, so it is unsuitable for CI pipelines. For non-interactive access, a scoped M2M application with client credentials is more suitable.

### Scoping the MCP server

The Auth0 MCP Server grants no scopes by default. You request them explicitly at init time using the `--scopes` flag. For this tutorial demo, all scopes were selected during `init`. For a more targeted setup, you can specify exactly what you need::

```bash  
# Grant all read permissions  
npx @auth0/auth0-mcp-server init --scopes 'read:*'

# Grant a targeted mix of permissions  
npx @auth0/auth0-mcp-server init --scopes create:clients,update:actions'  
```

Scopes map directly to Management API operations. For example, `read:clients` lets the agent call `auth0_get_application`, while `create:actions` lets it call `auth0_create_action`. Some scopes carry significant implications: `update:actions` can push custom code into production, and `read:logs` exposes detailed user activity and authentication events. See the [scopes reference](https://auth0.com/docs/get-started/auth0-mcp-server/auth0-mcp-server-guides/understanding-scopes) for more detail.

For this tutorial, all scopes were granted during `init` for the demo.  However, while coding your applications, you must follow the principle of least privilege and grant only what your workflow actually needs.

### Claude Code and Auth0 integration

To integrate the Auth0 MCP Server with Claude Code, run the following command from within your `Claude-Code-With-Auth0-MCP` directory:

```bash  
$ claude mcp add auth0 -- npx -y @auth0/auth0-mcp-server run  
```

You should see output similar to the following:

```bash  
Added stdio MCP server auth0 with command: npx -y @auth0/auth0-mcp-server run to local config  
File modified: /home/<user-name>/.claude.json [project: /path-to/Claude-Code-With-Auth0-MCP]  
```

<div class="alert alert-info alert-icon">
  <i class="icon-budicon-487 icon-info"></i>
  <b>Note: The --scope local/project/user, which is a <a href="https://code.claude.com/docs/en/mcp-quickstart#find-your-configuration-on-disk">Claude Code flag</a> controls where the MCP server config is visible: the current project only, shared across your team, or all your projects. For this tutorial, local (the default) is the right choice.</b><br> 
</div>

This adds the Auth0 MCP Server configuration block to your `~/.claude.json`:

```json  
"mcpServers": {  
    "auth0": {  
        "type": "stdio",  
        "command": "npx",  
        "args": ["-y", "@auth0/auth0-mcp-server", "run"],  
        "env": {}  
    }  
}  
```

Alternatively, you can create a [configuration JSON](https://auth0.com/docs/get-started/auth0-mcp-server/getting-started-with-auth0-mcp-server#other-mcp-clients) manually and register it with [`claude mcp add-json`](hhttps://code.claude.com/docs/en/mcp#add-mcp-servers-from-json-configuration).

Credentials are *not* stored in this JSON. The MCP server reads your token from the OS keychain at startup. To enable debug logging, add `"DEBUG": "auth0-mcp"` to the `env` block.

### Verify your integration

From your `Claude-Code-With-Auth0-MCP` directory, launch `claude` CLI and run `/mcp` to confirm `auth0` is listed with a tool count. Then try this read-only prompt:

```markdown  
Show me all applications in my Auth0 tenant.  
```

On the first Auth0 tool call, Claude Code will ask for your permission. You can approve it once or allow it permanently for the project.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/3SWZ1UxnLFMS3QRdQIkHaP/b3d539526fbc671f4b8e424ea5273f46/Claude_Code_permission_for_using_Auth0_tool.png" alt="Claude Code permission for using Auth0 tool" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

## Giving Claude Code Project Context with CLAUDE.md

`CLAUDE.md` is a markdown file at your project root that Claude Code reads at the start of every session. It tells the agent what kind of project it is working in, what constraints apply, and where the sensitive files are. Without it, Claude Code might make technically correct changes that are contextually wrong, like updating a callback URL shared across environments or granting a broader scope than your security policy allows.

A good `CLAUDE.md` for an Auth0 project should cover:

```markdown  
## Auth0 Context  
- Active tenant: dev-xxxx.us.auth0.com (development only)  
- Auth-sensitive files: src/auth/config.ts, .env.local  
- Existing applications: one SPA (authorization code flow with PKCE), one M2M (client credentials)  
- Permitted scopes: read:users, update:users  
- Hard constraints: never modify production callback URLs without explicit confirmation  
```

With this in place, Claude Code operates with the same guardrails a human teammate would naturally apply. See the demo project's [`CLAUDE.md`](https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/master/CLAUDE.md) for a practical example.

## Refactoring an Auth0 Integration from Claude CLI

To demonstrate this workflow in practice, this example uses a minimal [Flask app](https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation/app.py) with a single protected endpoint (`/api/protected`). It validates Auth0 JWT tokens using `python-jose`. It works, but it has several problems common in real codebases: JWKS fetched once at startup and never refreshed, no `kid` matching when selecting a signing key, a bare `except` that swallows all validation errors identically, and Auth0 config stored as module-level globals.

```python  
# JWKS fetched once at startup — never refreshed  
JWKS = requests.get(f"https://{AUTH0_DOMAIN}/.well-known/jwks.json").json()

def validate_token(token):  
    # No kid matching — always uses the first key regardless  
    # of which key signed the token  
    rsa_key = {  
        "kty": JWKS["keys"][0]["kty"],  
        "kid": JWKS["keys"][0]["kid"],  
        "use": JWKS["keys"][0]["use"],  
        "n":   JWKS["keys"][0]["n"],  
        "e":   JWKS["keys"][0]["e"],  
    }

    # No clock skew tolerance, no error differentiation  
    return jwt.decode(  
        token,  
        rsa_key,  
        algorithms=ALGORITHMS,  
        audience=API_AUDIENCE,  
        issuer=f"https://{AUTH0_DOMAIN}/"  
    )  
```

You can view this [full (suboptimal/naive) implementation on GitHub]([https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation](https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation)) that we will fix and improve iteratively with Claude Code and Auth0 MCP.

### Auth0 setup and protected API

On the Auth0 side, you need one [API](https://auth0.com/docs/get-started/auth0-overview/set-up-apis) (resource server) registered with an identifier (`https://mh-test-api.example.com`. This identifier URI should be unique in your app, but it does not need to resolve).

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/6Ud6f4PCL7ebeZL3ihfNAE/a087d73acde6664634a5cc9955314e6d/Auth0Claude_Demo_API.png" alt="Auth0: Claude Demo API" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

When you register an API, Auth0 automatically creates a test M2M application named **"[Your API Name] (Test Application),"** already authorized against your API. You will find it under Applications.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/4EawU1pRgZwxZS9z2UCwhj/2f0e0cfd8745fb2dd595f6a4ee88bb90/Auth0M2M_Test_Application.png" alt="Auth0: M2M Test Application" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

That is the minimum setup needed for this demo for Claude Code to audit and provision against something real.

#### Obtaining an Auth0 token

Use this cURL command to obtain your Auth0 token:

```bash  
curl --request POST   
  --url "https://YOUR_AUTH0_DOMAIN.us.auth0.com/oauth/token"   
  --header 'content-type: application/json'   
  --data '{  
    "client_id":"<client-ID>",  
    "client_secret":"<client-secret>",  
    "audience":"https://mh-test-api.example.com",  
    "scope":"",  
    "grant_type":"client_credentials"  
  }'

{"access_token":"eyJhbGc...","expires_in":86400,"token_type":"Bearer"}  
```

#### Calling your protected API

Download the [starter code](https://github.com/manishh/Claude-Code-With-Auth0-MCP/tree/suboptimal-implementation) into your `Claude-Code-With-Auth0-MCP` directory, create a virtual environment (`python3 -m venv ./.venv`), populate [`.env`](https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation/.env.example) file, install dependencies (`pip install -r requirements.txt`).

Run the Flask server:

```bash  
python app.py  
```

Then call the protected endpoint with your Auth0 token:

```bash  
TOKEN="eyJhbGc..."  
curl -H "Authorization: Bearer $TOKEN" http://localhost:5000/api/protected  
```

You should see a response like this:

```bash  
{  
  "message": "Access granted to a protected resource",  
  "user": "UFIv...@clients"  
}  
```

### Running the audit prompt

The naive implementation appears to work at this point, but we can double-check it with Claude Code to ensure there are no errors and nothing crucial is missing.

<div class="alert alert-info alert-icon">
  <i class="icon-budicon-487 icon-info"></i>
  <b>Note: Some prompts in the steps below are intentionally imprecise or technically inaccurate. The goal is to test whether Claude Code (backed by live Auth0 tenant access via the MCP Server), can identify the correct implementation approach even when the instructions do not perfectly describe it.</b><br> 
</div>

Inside the `claude` session connected to Auth0 MCP, this prompt was run: 

```markdown  
Claude "Audit" Prompt:  
-----------------------

Review app.py and audit the Auth0 JWT validation setup. Cross-reference it against 
our actual tenant configuration using the Auth0 MCP tools. Identify any mismatches 
between what the tenant is configured to issue and what the app is validating 
against, and list all security or reliability problems you find. Do not make any 
changes yet.  
```

Claude Code reviewed the codebase, asked for relevant permissions (human in the loop) and showed this:

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/5lw1ShGPQBIxEoulbjiGUy/1320c30a0a39ed55284b7410b0fa67c3/Claude_Code_Audit.png" alt="Claude Code Audit" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

Using the Auth0 MCP Server, Claude Code pulled live tenant data and cross-referenced it against `app.py` directly for this audit. The most critical finding would never surface in static analysis: the tenant is currently publishing two rotating RS256 signing keys, while the app validates every token against only the first one. A linter can flag the missing `kid` matching logic, but a live `auth0_list_applications` call reveals that key rotation is already in progress and valid tokens are failing right now. The remaining findings cover robustness and hardening gaps, but this one mismatch makes the case for Auth0 MCP integration clearly: live tenant access surfaces the subtle bugs that static code review would miss.

Here is Claude Code audit summary: 

```markdown  
Claude "Audit" Response:  
-------------------------

※ recap: Audited app.py's Auth0 JWT validation against the live tenant and found 
the key bug: the app validates every token against only the first JWKS key while
 the tenant publishes two, plus reliability issues. No changes made yet; next 
action is drafting fixes if you want them. (disable recaps in /config)  
```

Importantly, Claude Code made no changes during this step *as instructed*. 

### Running the provision prompt

Once the audit was complete, the provision prompt asked Claude CLI to create a new M2M application (`"Claude-Auth0 MCP API Client"`) and a `read:data` scoped client grant in a single instruction:

```markdown  
Claude "Provision" Prompt:  
---------------------------

Create a new M2M application in our Auth0 tenant called "Claude-Auth0 MCP 
API Client". Set the token lifetime to 3600 seconds. Then create a client 
grant authorizing it against our API audience (https://mh-test-api.example.com) 
with `read:data` scope only. Use the credentials returned to update 
the `.env` file. Do not touch app.py yet.  
```

Before proceeding, Claude Code flagged a real Auth0 misconception worth noting: token lifetime is a resource server setting that applies to all clients of an API, not a per-application property. Rather than applying a silent global change, it surfaced this as a clarifying question with explicit options. That pushback, grounded in live tenant data, is exactly what makes Auth0 MCP integration useful.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/3WmoSTqdCjJMxpaCydjWeG/2d8599b3ce08089b4e124fdf19b46bd5/Claude_Provision_Clarification.png" alt="Claude Provision Clarification" style="width:80%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

After confirming the token lifetime should remain unchanged and approving the MCP tool calls, Claude Code invoked `auth0_create_application` and `auth0_create_client_grant` in sequence, updated `.env` with the returned credentials (client ID and secret), and left `app.py` untouched as instructed.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/45i8XRnIABajSNC7h2CMbV/a5457950f00cf27397b33b3a750d83ae/Claude_Provision_Completed.png" alt="Claude Provision Completed" style="width:80%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

The Auth0 dashboard confirmed the new `Claude-Auth0 MCP API Client` application was added:

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/5JHJTZVlYdyWLfUO0ovxeZ/67fc2abc3de50fd6e4af6be8288617cb/Auth0Application_added_by_Claude.png" alt="Auth0: Application added by Claude" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

And the API confirmed the `read:data` permission was in place:

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/5QmGrLdykX12JKLfoLyBaG/608d22d421a6d420733955870fc34798/Auth0API_permission_added_by_Claude.png" alt="Auth0: API permission added by Claude" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

Here is how Claude Code summarized this session:

```markdown  
Claude "Provision" Response:  
---------------------------

※ recap: Goal: secure the Auth0 JWT setup. Audited app.py 
(key bug: ignores kid, breaks on rotation) and created the M2M app, 
read:data scope, grant, plus .env credentials. Next action: fix app.py's 
validation issues when you're ready. (disable recaps in /config)  
```

### Running the implementation prompt

This prompt gave Claude Code a single instruction to fix everything surfaced in the audit:

```markdown  
Claude "Refactor" Prompt:  
---------------------------

Now refactor `app.py` to fix all the issues found in the audit. Use 
the credentials and tenant configuration we just provisioned: fetch 
the JWKS URI, issuer, and audience dynamically from the tenant rather 
than hardcoding them. Fix the kid matching, add proper error handling, and 
make sure the app stays in sync with what the tenant is configured to issue.  
```

With the audit findings and provisioned credentials already in context, Claude Code asked for confirmation as specified in `CLAUDE.md`, then rewrote `app.py` without needing to be walked through each fix individually.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/u5NwB5S2S4XEfvdyIg7g8/a4344c519b89c2b712e717b3508a5ee2/Claude_App_Refactoring.png" alt="Claude App Refactoring" style="width:80%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

The refactored code introduced an `OIDCProvider` class that discovers the issuer and JWKS URI dynamically from the tenant's OIDC discovery document, caches the JWKS with a configurable TTL, and handles key rotation by retrying with a forced refresh on an unknown `kid`. The minimal `except` was replaced with typed exception handling that differentiates expired tokens, claim mismatches, and signature failures. Scope enforcement was added via a `require_scope` decorator (see refactored code: [**app.py**](https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/master/app.py)):

```python  
def get_signing_key(self, kid):  
    """Return the JWK matching ``kid`` (audit F1), refreshing if stale/rotated."""  
    stale = (self._jwks is None  
              or time.monotonic() - self._jwks_fetched_at > self._jwks_ttl)  
    if stale:  
        self._refresh_jwks()

    key = self._find_key(kid)  
    if key is None and not stale:  
        # kid unknown against a cache we didn't just refresh: keys may have  
        # rotated. Refresh once and retry before giving up.  
        self._refresh_jwks()  
        key = self._find_key(kid)  
    return key

def validate_token(token):  
    # ...  
    try:  
        return jwt.decode(  
            token, rsa_key,  
            algorithms=ALGORITHMS,  
            audience=API_AUDIENCE,  
            issuer=oidc.issuer,  
            options={"leeway": LEEWAY_SECONDS},  
        )  
    except ExpiredSignatureError as e:  
        raise AuthError("token_expired", 401) from e  
    except JWTClaimsError as e:  
        raise AuthError("invalid_claims", 401) from e  
    except JWTError as e:  
        raise AuthError("invalid_token", 401) from e

def require_scope(required_scope):  
    """Authenticate the request and enforce a scope (audit F8)."""

    def decorator(fn):  
        @wraps(fn)  
        def wrapper(*args, **kwargs):  
            payload = validate_token(_get_bearer_token())  
            granted = payload.get("scope", "").split()  
            if required_scope not in granted:  
                raise AuthError("insufficient_scope", 403)  
            g.jwt_payload = payload  
            return fn(*args, **kwargs)  
        return wrapper  
    return decorator  
```

A separate [`get_token.py`](https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/master/get_token.py) utility was also produced for minting test tokens using the client credentials flow, correctly kept separate from `app.py` since a resource server should not hold a client secret. 

This refactored demo can be run with:

```bash  
TOKEN=$(python get_token.py)  
curl -H "Authorization: Bearer $TOKEN" http://localhost:5000/api/protected  
```

Which produces the expected response:

```json  
{"message":"Access granted to a protected resource","sub":"l5rDOCiobYJWeRiYEImEbA8FzFTHkqGc@clients"}  
```

Throughout the refactor, Claude Code used the credentials it had just provisioned via the Auth0 MCP Server rather than asking for them again, keeping the codebase and tenant configuration in sync from the start.

<picture>
<img src="https://images.ctfassets.net/23aumh6u8s0i/5DU9XuuqVzmPU4CLFtT8aj/5026f1fba7c2dcfe758320a83b9e9dc3/Claude_Refactoring_Completed.png" alt="Claude Refactoring Completed" style="width:100%; margin: 1em auto; border: solid black 0px; border-radius: 0px;">
</picture>

Here is how Claude Code summarized the session:

```markdown  
Claude "Refactor" Response:  
-----------------------------

※ recap: Goal: harden the Auth0 JWT validation in this Flask app. 
Done: audited app.py, provisioned the M2M client plus read:data 
grant, and refactored app.py to fix all findings, verified live. 
Next action: optionally migrate from python-jose to PyJWT, or 
stop here. (disable recaps in /config)  
```

<div class="alert alert-info alert-icon">
  <i class="icon-budicon-487 icon-info"></i>
  <b>Pro Tip: For production use, consider migrating to PyJWT's PyJWKClient, which subsumes the OIDCProvider plumbing built here into a well-maintained abstraction. Claude Code flagged this during the refactor but correctly held off since it is a dependency change, not an in-file fix, and out of scope for this tutorial.</b><br> 
</div>

### Observations: Claude Code with Auth0 MCP server

A few things stood out running this end-to-end. Audit findings were grounded in live tenant state, not static code analysis alone. The two-key rotation mismatch only surfaced because the Auth0 MCP Server pulled the actual JWKS from the tenant and compared it against what `app.py` was doing. Without that, the finding would have been *"no `kid` matching logic"* rather than *"key rotation is already in progress and valid tokens are failing right now."* That distinction matters in production.

The token lifetime clarification was similarly useful. Seeing `token_lifetime: 86400` as a tenant-wide setting let Claude Code correctly flag that changing it would affect all clients, not just the new M2M application. A code-only agent would have missed that entirely.

The refactored code also went further than the prompt asked. The `OIDCProvider`, scope enforcement, and separation of `get_token.py` all emerged from the audit findings without individual prompting. Throughout, Claude Code asked for permission before invoking any Auth0 MCP tools, creating applications, or running bash commands.

That last point addresses the question raised in the introduction. Scoped tools, a `CLAUDE.md` that encodes your constraints, and consistent human-in-the-loop confirmation keep the agent working within a well-defined boundary. It has live tenant access, but only as far as you explicitly allow.

You can compare the [**naive implementation**](https://github.com/manishh/Claude-Code-With-Auth0-MCP/tree/suboptimal-implementation) with the [**refactored implementation**](https://github.com/manishh/Claude-Code-With-Auth0-MCP) directly.

## Security Guardrails

Giving an AI agent access to your Auth0 tenant is similar to giving a new team member API access. The same principles apply:

* **Keep the MCP server scoped to development tenants only.** A practical way to enforce this is maintaining separate `CLAUDE.md` files per environment, each pointing to the right tenant domain. That way there is no path from a local Claude Code session to production credentials by default. If a session does need to touch production, make it a deliberate, separate setup with explicit sign-off.

* **Treat the device auth token like any other API credential.** After finishing a focused session, revoke it from the Auth0 dashboard rather than leaving it active. In fact, using a short access token lifetime helps reduce security risks. If your team uses Auth0 log streaming, pipe those logs to your monitoring tool of choice so MCP-driven Management API calls show up in the same audit trail as everything else.

* **Client secrets belong in `.env`, not in `CLAUDE.md` or inline prompts.** It is easy to paste a secret into a prompt for convenience and forget it now lives in your shell history and potentially in Claude Code's session context. Let the MCP server handle authentication; your prompts should never carry credentials.

<div class="alert alert-info alert-icon">
  <i class="icon-budicon-487 icon-info"></i>
  <b>Note: Agentic changes are still code changes. The refactored app.py in this tutorial went through manual review before being accepted. All auth-critical paths deserve the same PR process, regardless of who or what wrote them.</b><br> 
</div>

## Code and Auth, in Sync

In this tutorial, you connected Claude Code to Auth0's Management API via the Auth0 MCP Server, built a scoped M2M credential flow, and refactored a suboptimal JWT validation setup to correctly handle key rotation, typed errors, and scope enforcement. The security principles applied throughout were practical: least-privilege scopes, device auth over static secrets, environment-specific configs, and keeping agentic changes in the normal PR review flow.

The entire workflow, from auditing a live tenant to provisioning an application to rewriting the validation code, happened in a single terminal session. The Auth0 dashboard was only needed to verify the changes Claude Code had made. Which answers the question we started with. You can give an AI agent access to sensitive authentication infrastructure securely with Auth0 MCP: secure access, scoped tools, and human-in-the-loop confirmation keep the agent within well-defined boundaries, without opening up new security risks.

Auth0's Management API exposes the full surface area of your tenant as programmable, auditable operations to any AI agent or automation you want to build on top of it. If you want to extend what you built here, the [Auth0 MCP Server tools reference](https://auth0.com/docs/get-started/auth0-mcp-server/auth0-mcp-tools-reference) is a good next stop.