<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Auth0</title>
    <description>The latest articles on DEV Community by Auth0 (@auth0).</description>
    <link>https://dev.to/auth0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F634%2Fc6bfc78f-136d-456b-96dc-bcc4be1c88f9.jpg</url>
      <title>DEV Community: Auth0</title>
      <link>https://dev.to/auth0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/auth0"/>
    <language>en</language>
    <item>
      <title>Your Team's Productivity Metric is Probably Useless</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Wed, 20 May 2026 13:38:00 +0000</pubDate>
      <link>https://dev.to/auth0/your-teams-productivity-metric-is-probably-useless-3i6</link>
      <guid>https://dev.to/auth0/your-teams-productivity-metric-is-probably-useless-3i6</guid>
      <description>&lt;p&gt;If your team is measuring developer productivity by lines of code or number of commits, you're measuring the wrong thing. So what should you be looking at instead?&lt;/p&gt;

&lt;p&gt;In Episode 5 of Making Software, I talked to &lt;strong&gt;Dennis Henry&lt;/strong&gt;, Productivity Architect at Okta. Dennis has a master's degree in human factors, and he explained to me how he applies it to software engineering every day.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The science of failure, applied to your codebase.&lt;/strong&gt; Human factors studies how people interact with systems and how things go wrong. Dennis explains why "human error" is never the real root cause in production incidents, and what you should be looking for instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A multi-model AI gateway for an entire company.&lt;/strong&gt; Dennis built an internal platform that gives every Okta employee secure access to any Anthropic, OpenAI, and more models, that lives behind SSO with PII guardrails and MCP call auditing. He walks through why locking into one vendor right now is a mistake and how to architect around that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improving Engineering Productivity&lt;/strong&gt; Dennis constantly asks engineers: "What pisses you off? What makes you want to throw your keyboard across the room?" He makes the case that happy people make the best software, and that measuring sentiment matters more than measuring output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What to actually track.&lt;/strong&gt; Lines of code? Terrible. Commits? Meaningless. Dennis argues for reliability, bug resolution time, customer experience, and whether your team feels like they have the tools to do their job. Qualitative over quantitative.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Productivity isn't about squeezing more output out of people. It's about removing the things that get in the way of meaningful work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the worst productivity metric you've seen used on a team?&lt;/strong&gt; Let me know in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  Listen to the full episode
&lt;/h2&gt;

&lt;p&gt;Available on &lt;a href="https://www.youtube.com/playlist?list=PLZ14qQz3cfJKRDmX3yasmbwoC4kipeQfu" rel="noopener noreferrer"&gt;&lt;strong&gt;YouTube&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://podcasts.apple.com/us/podcast/making-software/id1872107131" rel="noopener noreferrer"&gt;&lt;strong&gt;Apple Podcasts&lt;/strong&gt;&lt;/a&gt;, and &lt;a href="https://open.spotify.com/show/6J856S2fijMvP3rzFkRnBi" rel="noopener noreferrer"&gt;&lt;strong&gt;Spotify&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading! 👋&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>security</category>
    </item>
    <item>
      <title>What AI Tools, MCP Servers, and Skills Actually Do</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Tue, 19 May 2026 13:39:13 +0000</pubDate>
      <link>https://dev.to/auth0/what-ai-tools-mcp-servers-and-skills-actually-do-7ep</link>
      <guid>https://dev.to/auth0/what-ai-tools-mcp-servers-and-skills-actually-do-7ep</guid>
      <description>&lt;p&gt;I remember being very confused when I first heard about an LLM's ability to request code execution. This feature has been called various names: tool, action, plugin, function. Now the terminology is settling on a single name: tool. However, talking to other developers and reading comments online, I see the confusion has shifted elsewhere. Some argue that, &lt;a href="https://www.linkedin.com/posts/jason-lopatecki-9509941_why-skills-are-exploding-and-mcp-is-already-activity-7443091557408219136-R617" rel="noopener noreferrer"&gt;with the introduction of skills by Anthropic, MCP no longer makes sense&lt;/a&gt;, and &lt;a href="https://www.reddit.com/r/mcp/comments/1kkyajj/could_you_explain_how_mcps_are_different_and/" rel="noopener noreferrer"&gt;others aren't convinced of the usefulness of MCP&lt;/a&gt; compared to direct tool calls.&lt;/p&gt;

&lt;p&gt;The confusion is architectural. Tools, MCP servers, and skills solve different problems at different layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Tools Are the Model's Swiss Army Knife
&lt;/h2&gt;

&lt;p&gt;At the most fundamental level, an AI tool is a function that a language model can decide to call. Not a metaphor for "capability" in general, but a specific, callable function with a defined input schema and a predictable output.&lt;/p&gt;

&lt;p&gt;When a model needs information or an action it can't produce from training alone, it generates a structured call to a tool: something like "call &lt;code&gt;get_customer_record&lt;/code&gt; with &lt;code&gt;customer_id: 12345&lt;/code&gt;." The host application intercepts that call, runs the actual function, and returns the result to the model. The model then incorporates it and continues.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While the tool calling concept is the same across the different LLMs, each one has its own specifications to invoke tools. Check out &lt;a href="https://developers.openai.com/api/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview" rel="noopener noreferrer"&gt;Claude&lt;/a&gt;, or &lt;a href="https://ai.google.dev/gemini-api/docs/function-calling" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; documentation for some specific examples.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tools are what give AI its "hands." Without them, a model is impressively articulate but isolated from live data and the systems where real work happens. With them, it can search databases, call APIs, send messages, or trigger business logic in response to what a user needs.&lt;/p&gt;

&lt;p&gt;The key property to hold onto: &lt;strong&gt;a tool should know how to execute one thing&lt;/strong&gt;. It doesn't decide when to be called. That judgment belongs to the model, or in more complex systems, to something above it.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in code. Using the &lt;a href="https://docs.anthropic.com/en/api/getting-started" rel="noopener noreferrer"&gt;Anthropic SDK&lt;/a&gt;, you define a tool as a JSON schema: its name, a description the model uses to decide when to invoke it, and the inputs it expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_customer_record&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retrieve a customer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s account details by ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The unique customer identifier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
       &lt;span class="p"&gt;},&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Look up customer C-12345&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# When the model decides to use the tool, it returns a structured tool_use block
&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# get_customer_record
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_use&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# {"customer_id": "C-12345"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;description&lt;/code&gt;:&lt;/strong&gt; This is what the model reads to decide whether to call the tool. A clear, accurate description matters more than the schema itself.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;input_schema&lt;/code&gt;:&lt;/strong&gt; The structured contract. The model generates arguments that conform to this schema when it decides to invoke the tool.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tool_use&lt;/code&gt; block:&lt;/strong&gt; What the model provides when it wants to call a tool. Your application handles the actual execution and passes the result back in the next turn to continue the conversation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model never runs the function itself. It produces a structured intent. Your application does the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Servers Are the Universal Translator
&lt;/h2&gt;

&lt;p&gt;At first glance, an MCP server might look like a more elaborate way to define tools. In practice, it solves a different problem.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; is an open standard that introduces a protocol layer between AI applications and the services they connect to. Think of it as USB-C for AI: a universal connector that lets any compliant client communicate with any compliant server, regardless of which model runs underneath.&lt;/p&gt;

&lt;p&gt;Without MCP, every AI application builds its own integrations: a custom function schema for one client, a different one for another, yet another for whichever agent framework a team has chosen. The same capability gets re-implemented for each consumer. MCP exists to eliminate that duplication.&lt;/p&gt;

&lt;p&gt;Here's how the architecture works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP clients&lt;/strong&gt; are the components that &lt;em&gt;hosts&lt;/em&gt; (i.e., AI applications such as Claude Desktop, a VS Code extension, a custom agent, etc.) instantiate to initiate connections to servers.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP servers&lt;/strong&gt; expose three types of primitives:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools:&lt;/strong&gt; actions the AI can invoke.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources:&lt;/strong&gt; data the AI can read.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts:&lt;/strong&gt; pre-built interaction templates.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The MCP transport layer is equally flexible. The protocol runs over standard I/O for local processes, or HTTP with Server-Sent Events for remote services, using OAuth 2.1 for authentication on remote connections.&lt;/p&gt;

&lt;p&gt;What makes MCP structurally different from direct tool definitions is &lt;strong&gt;decoupling&lt;/strong&gt;. A tool definition embedded in a model API call is tightly coupled to that application. An MCP server is portable: the same server works across any compliant client. Build a GitHub integration once, connect it everywhere.&lt;/p&gt;

&lt;p&gt;There's also the matter of bidirectionality: an MCP server can &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/client/sampling" rel="noopener noreferrer"&gt;request that the AI client sample from the language model&lt;/a&gt;, enabling interactions that go beyond a simple request-response pattern like an API. It's a more symmetric relationship than direct function calling, which flows strictly from model to function.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Skills Direct the Work
&lt;/h2&gt;

&lt;p&gt;Skills occupy a different layer from tools and MCP servers entirely.&lt;/p&gt;

&lt;p&gt;Where a tool is a capability and an MCP server is the infrastructure for exposing it, a skill is closer to a recipe: &lt;strong&gt;higher-level instructions that tell an AI when and how to use its available capabilities to accomplish a complex goal&lt;/strong&gt;. A skill doesn't replace tools. It orchestrates them.&lt;/p&gt;

&lt;p&gt;In practice, a skill combines three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;System prompts:&lt;/strong&gt; Instructions that define a persona and a set of constraints.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logic/Workflows:&lt;/strong&gt; A series of steps the model should follow.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool selection strategy:&lt;/strong&gt; Knowing which tool to pick in a specific sequence to achieve a complex goal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a customer support scenario. Handling a refund request might involve checking the customer record, looking up the original transaction, verifying return eligibility based on policy, and then either initiating the refund or escalating to a human, as shown in the following diagram: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqvpui78miuwwcho9vr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqvpui78miuwwcho9vr5.png" alt="Diagram showing the flow for a purchase refund AI skill" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each step might invoke a different tool. The skill is the framework that defines the sequence, the conditions, and the decision points. Skills carry the domain knowledge and reasoning structure that raw tool access doesn't provide.&lt;/p&gt;

&lt;p&gt;Anthropic formalized the skills concept for Claude Code, and the format is now an &lt;a href="https://agentskills.io/" rel="noopener noreferrer"&gt;open standard&lt;/a&gt; gaining adoption across AI coding tools. The refund scenario above could be expressed as in the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handle-refund-request&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Handle&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refund&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;request&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;user."&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are a customer support agent for Acme Corp.
You have access to: &lt;span class="sb"&gt;`get_customer_record`&lt;/span&gt;, &lt;span class="sb"&gt;`get_transaction`&lt;/span&gt;,
&lt;span class="sb"&gt;`initiate_refund`&lt;/span&gt;, &lt;span class="sb"&gt;`escalate_to_human`&lt;/span&gt;.

When a user requests a refund:
&lt;span class="p"&gt;
1.&lt;/span&gt; Verify the account with &lt;span class="sb"&gt;`get_customer_record`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Retrieve the purchase details with &lt;span class="sb"&gt;`get_transaction`&lt;/span&gt;
&lt;span class="p"&gt;3.&lt;/span&gt; If the purchase was within 30 days and its amout is less than $500, call &lt;span class="sb"&gt;`initiate_refund`&lt;/span&gt;
&lt;span class="p"&gt;4.&lt;/span&gt; For amounts over $500, always escalate regardless of purchase date
&lt;span class="p"&gt;5.&lt;/span&gt; If ineligible, call &lt;span class="sb"&gt;`escalate_to_human`&lt;/span&gt; and explain why
​
Never approve a refund without completing steps 1 and 2 first.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare this to the tool definition from the previous section. The tool schema said nothing about when to be called, what order to follow, or what to do when an amount exceeds a threshold. The skill encodes all of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Available tools:&lt;/strong&gt; The skill names the specific tools relevant to this task, not everything the agent has access to.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence and conditions:&lt;/strong&gt; Steps 1–5 define the order and the branching logic.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard rails:&lt;/strong&gt; "Never approve without completing steps 1 and 2" is judgment the tools themselves can't encode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tools handle execution. The skill handles when, in what order, and under what conditions.&lt;/p&gt;

&lt;p&gt;The distinction from tools becomes clearest at the boundary between capability and judgment. A tool knows how to execute a database query. A skill knows when a query is the right mechanism versus, say, asking a user for clarification first. That's the bridge skills provide: from what an AI can do to what it should do in a given scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Differences at a Glance
&lt;/h2&gt;

&lt;p&gt;The distinction between tools, MCP servers, and skills lies in their architectural layer, scope, and security considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition and scope:&lt;/strong&gt; A &lt;strong&gt;tool&lt;/strong&gt; is a fundamental capability, defined as a single, specific callable function that the model can invoke via its API schema. Above this is the &lt;strong&gt;MCP server&lt;/strong&gt;, which acts as a standardized protocol server. It exposes a collection of capabilities, including actions, data, and prompts, which are used by AI clients via the MCP protocol. The highest layer is the &lt;strong&gt;skill&lt;/strong&gt;, which is not a capability but a set of instructions guiding AI behavior toward a complex goal, managing a multi-step workflow or domain reasoning through the agent's orchestration layer.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coupling and portability:&lt;/strong&gt; Another key difference is portability. &lt;strong&gt;Tools&lt;/strong&gt; are tightly coupled to the application they are defined within. In contrast, &lt;strong&gt;MCP servers&lt;/strong&gt; are portable, designed to work across any compliant client, making them a universal connector. &lt;strong&gt;Skills&lt;/strong&gt; are specific to the particular task or domain they are designed for.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security surface:&lt;/strong&gt; Each layer presents a distinct security surface. Security for &lt;strong&gt;tools&lt;/strong&gt; focuses on function-level permissions. &lt;strong&gt;MCP servers&lt;/strong&gt; manage risk through protocol-level consent and capability negotiation. &lt;strong&gt;Skills&lt;/strong&gt; introduce behavioral guardrails and decision boundaries, controlling the overall logic and sequence of actions to ensure appropriate conduct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following picture summarizes these distinctions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3rviwmmxo9xfa5sztfc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3rviwmmxo9xfa5sztfc.png" alt="Table showing the architectural layer, scope, and security considerations for AI tools, MCP servers, and skills." width="800" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The relationship is hierarchical: skills direct what to accomplish, tools do the work, and MCP servers are the infrastructure that makes tools available consistently. None of the three replaces the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Security Perspective
&lt;/h2&gt;

&lt;p&gt;Each layer introduces distinct security considerations, and they compound when combined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At the tool layer&lt;/strong&gt;, the core risk is over-permission. A tool that can read and write to a database, when the AI only needs to read, creates unnecessary exposure. &lt;a href="https://owasp.org/www-community/attacks/PromptInjection" rel="noopener noreferrer"&gt;Prompt injection attacks&lt;/a&gt; (where malicious content in retrieved data tricks a model into taking unintended actions) are most damaging when tools have broad permissions. The principle of least privilege applies here as directly as it does anywhere in security engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At the MCP server layer&lt;/strong&gt;, the attack surface expands because MCP servers are external processes, often network-accessible, and connected to by multiple clients. The risks here include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://owasp.org/www-community/attacks/MCP_Tool_Poisoning" rel="noopener noreferrer"&gt;&lt;strong&gt;Tool poisoning&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; A malicious or compromised server exposes functions that look legitimate but behave maliciously when called.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unauthorized access:&lt;/strong&gt; Without proper authentication, any client could invoke a server's capabilities, including sensitive ones.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope creep:&lt;/strong&gt; Servers that expose more than intended, by design or misconfiguration, give connected clients broader access than warranted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/specification" rel="noopener noreferrer"&gt;MCP specification&lt;/a&gt; addresses this through explicit consent requirements and capability negotiation, but correct implementation is the server author's responsibility. Remote MCP servers should require OAuth 2.1-based authentication, and permissions granted to any connection should follow the least-privilege principle.&lt;/p&gt;

&lt;p&gt;To mitigate risks in both tools and MCP servers, apply the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict Scoping:&lt;/strong&gt; Never give an AI tool a "God Mode" API key. Use scoped tokens that only allow the specific actions required for that tool.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-Loop:&lt;/strong&gt; For high-stakes tools (like making a payment or deleting data), always require an approval from the user before the application executes the model's tool call.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input Validation:&lt;/strong&gt; Treat every argument generated by an LLM as untrusted user input. Validate the types, ranges, and permissions before hitting your backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;At the skills layer&lt;/strong&gt;, the risks become behavioral. Skills that don't account for adversarial inputs, that allow irreversible actions without human confirmation, or that chain too many autonomous steps without a checkpoint are dangerous in a specific way. By the time someone can intervene, the damage is done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/ai/docs" rel="noopener noreferrer"&gt;Auth0 for AI Agents&lt;/a&gt; addresses several of these challenges at the identity layer. Based on standards, it provides support for user authentication, delegation patterns for acting on a user's behalf, and asynchronous authorization for scenarios where sensitive operations need human sign-off before proceeding. Auth0's MCP support, &lt;a href="https://auth0.com/blog/auth0-auth-for-mcp-servers-generally-available" rel="noopener noreferrer"&gt;now generally available&lt;/a&gt;, provides OAuth 2.1-based authentication for MCP servers.&lt;/p&gt;

&lt;p&gt;The underlying principle is familiar from traditional IAM: identity infrastructure doesn't fundamentally change because the client is an AI, but the patterns for applying it need to account for how agentic systems behave differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Layers, One System
&lt;/h2&gt;

&lt;p&gt;The shift from AI as a generator to AI as an actor is real, and the architecture that supports it is becoming clearer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tools give AI a way to act on the world.
&lt;/li&gt;
&lt;li&gt;MCP servers give it a consistent, portable way to access those actions.
&lt;/li&gt;
&lt;li&gt;Skills give it the reasoning framework to act appropriately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each layer has its own design considerations, its own security surface, and its own place in a well-structured AI system. Understanding the distinctions is foundational to building systems that aren't just capable, but trustworthy. As the tooling matures, the teams that understand the layers will be the ones building systems they can actually reason about.&lt;/p&gt;

&lt;p&gt;For a closer look at the vulnerabilities that emerge in agentic AI, &lt;a href="https://auth0.com/blog/how-to-fix-five-critical-ai-agent-security-risks" rel="noopener noreferrer"&gt;five critical AI agent security risks&lt;/a&gt; is a practical next step. If you're working through identity for MCP servers specifically, the &lt;a href="https://auth0.com/ai/docs" rel="noopener noreferrer"&gt;Auth0 AI documentation&lt;/a&gt; covers authentication, delegation, and human-in-the-loop authorization patterns.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>llm</category>
    </item>
    <item>
      <title>AI Agents Have Two Souls. You Only Control One</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 08 May 2026 16:04:16 +0000</pubDate>
      <link>https://dev.to/auth0/ai-agents-have-two-souls-you-only-control-one-3bm4</link>
      <guid>https://dev.to/auth0/ai-agents-have-two-souls-you-only-control-one-3bm4</guid>
      <description>&lt;p&gt;Everyone seems to be building AI agents now. But ask ten developers what an AI agent actually is, and you'll get ten different answers. Some say it is any LLM with tool access. Others define it by the ability to autonomously take actions in the world. A few will point at an existing chatbot and call it an agent.&lt;/p&gt;

&lt;p&gt;This definitional vagueness is not just an academic problem. It leads to a security problem. How can you protect a system you cannot describe precisely?&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking for an AI Agent Definition
&lt;/h2&gt;

&lt;p&gt;Beyond the generic definitions that emphasize the level of autonomy in making decisions, I'd like to point out a slightly more technical one that I prefer. It comes from &lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ai-agents/#what-is-an-ai-agent" rel="noopener noreferrer"&gt;Microsoft&lt;/a&gt; and seems to be quite consistent with the &lt;a href="https://genai.owasp.org/glossary/" rel="noopener noreferrer"&gt;OWASP definition&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;"&lt;em&gt;An AI agent is a flexible software program that uses generative AI models to interpret inputs, [...] reason through problems, and decide on the most appropriate actions. [...] Agents are built on five core components:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Generative AI model&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;serves as the agent's reasoning engine. It processes instructions, integrates tool calls, and generates outputs, either as messages to other agents or as actionable results.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Instructions&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;define the scope, boundaries, and behavioral guidelines for the agent. Clear instructions prevent scope creep and ensure the agent adheres to business rules.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Retrieval&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;provides the grounding data and context required for accurate responses. Access to relevant, high-quality data is critical for reducing hallucinations and ensuring relevance.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Actions&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;are the functions, APIs, or systems the agent uses to perform tasks. Tools transform the agent from a passive information retriever into an active participant in business processes.&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Memory&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;stores conversation history and state. Memory ensures continuity across interactions, allowing the agent to handle multi-turn conversations and long-running tasks effectively.&lt;/em&gt;"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents differ from traditional applications, which are based on fixed rules. By dynamically orchestrating workflows according to real-time context, agents gain adaptability that allows them to manage ambiguity and complexity beyond the capability of traditional software.&lt;/p&gt;

&lt;p&gt;We can visualize this definition into the following diagram:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2FuKLVsROcv4NHKZZ6QvuTA%2F9036537f8d4d4dca7deee1e7622eac84%2FAIAgentDefinitionDiagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2FuKLVsROcv4NHKZZ6QvuTA%2F9036537f8d4d4dca7deee1e7622eac84%2FAIAgentDefinitionDiagram.png" alt="Diagram of an AI agent architecture showing the deterministic Agent Core, Tools, and the probabilistic LLM reasoning engine." width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;
Aside from input, output, and external resources, the heart of an AI agent is a collection of instructions, the LLM, and the Agent Core.&lt;/p&gt;

&lt;p&gt;The LLM is, as we know, the brain of the agent, the component that performs the necessary reasoning and makes decisions. The Agent Core is the software component that orchestrates the interactions between the LLM and the rest of the world. The Agent Core is the code in your Python, JavaScript, C#, etc. application that interacts with the LLM.&lt;/p&gt;

&lt;p&gt;Within the Agent Core, I highlighted two components: the Agent Control and the Tools. The Agent Control is the heart of the agent, the part that coordinates all the interactions between the LLM and the external world. The Tools component represents all the functionality made available to the LLM, such as calculation functions, file system access, etc. This component is also the interface to external tools and resources. The combination of the Agent Control and the Tools components builds the Agent Core: just plain old-style code, with no &lt;em&gt;intelligent&lt;/em&gt; functionality.&lt;/p&gt;

&lt;p&gt;Looking at the diagram, we notice two interesting things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The two true components of an AI agent are the Agent Core, which is deterministic, and the LLM, which is not deterministic.
&lt;/li&gt;
&lt;li&gt;The Agent Core is the only component that interacts with the LLM. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two simple observations are fundamental to understanding the nature of an AI agent and how we can secure it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Souls of an AI Agent
&lt;/h2&gt;

&lt;p&gt;The first observation highlights that the two core components of an AI agent are a traditional deterministic application and an LLM, which is not deterministic.&lt;/p&gt;

&lt;p&gt;The Agent Core sends the input to the LLM, provides functionalities to it, processes the output, etc. by running deterministic code. You can analyze it, test it, you know how it works and you know that for a given input you will always get the same output.&lt;/p&gt;

&lt;p&gt;The LLM model has a different nature: it is not deterministic. Given the same input on two different occasions, a generative AI model may produce different outputs. It can reason in unexpected directions, interpret ambiguous instructions in ways you did not anticipate, and combine information from its context in ways that surprise even the people who built it. This is at the same time the power and the problem with LLMs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Saying that LLMs &lt;strong&gt;are not deterministic is not the same as saying it is non-deterministic&lt;/strong&gt;. While commonly LLMs are considered to be non-deterministic, this is not correct in computational terms. See &lt;a href="https://theturingmachine.net/generative-ai-and-non-determinism" rel="noopener noreferrer"&gt;this article to learn why LLMs are not non-deterministic&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, in the architecture of an AI agent, we can identify what I call two souls: a &lt;strong&gt;deterministic soul&lt;/strong&gt; (the Agent Core) and a &lt;strong&gt;probabilistic soul&lt;/strong&gt; (the LLM).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are a philosophy enthusiast, you might notice a certain reference to the dualities of the human soul: from &lt;a href="https://en.wikipedia.org/wiki/Phaedrus_(dialogue)#Chariot_allegory" rel="noopener noreferrer"&gt;Plato's myth of the winged chariot&lt;/a&gt; to &lt;a href="https://en.wikipedia.org/wiki/Augustine_of_Hippo" rel="noopener noreferrer"&gt;St. Augustine&lt;/a&gt;'s two wills to Nietzsche's distinction between the &lt;a href="https://en.wikipedia.org/wiki/Apollonian_and_Dionysian" rel="noopener noreferrer"&gt;Apollonian and Dionysian concepts&lt;/a&gt;.&lt;br&gt;&lt;br&gt;&lt;br&gt;
The tension is the same: one controlled, one wild. And the wild one gets all the attention.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Traditional software security is built almost entirely on the assumption of determinism. You know what inputs are valid, you know what outputs to expect, and you can test edge cases exhaustively. &lt;em&gt;AI agents shatter this assumption&lt;/em&gt;. The probabilistic soul introduces a category of behavior that no test suite can fully cover.&lt;/p&gt;

&lt;p&gt;The implication for security follows directly: you cannot secure the model itself. What you can do is &lt;strong&gt;architect the deterministic soul to constrain what the probabilistic soul can reach&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Laws of AI Security Applied
&lt;/h2&gt;

&lt;p&gt;Some time ago, I wrote an &lt;a href="https://auth0.com/blog/three-laws-ai-security/" rel="noopener noreferrer"&gt;article about the three laws of AI security&lt;/a&gt;. Paraphrasing Asimov's three laws of robotics, I defined similar laws to control the less deterministic part of AI. Similar to what I have analyzed in this article, I observed that the fundamental problem in building secure AI-powered applications is &lt;a href="https://auth0.com/blog/three-laws-ai-security/#The-Control-Problem-with-AI" rel="noopener noreferrer"&gt;the loss of that control&lt;/a&gt; we have become accustomed to with deterministic software.&lt;/p&gt;

&lt;p&gt;Based on the discussion we have had so far, we say that in the architecture of an AI agent there is a component that makes decisions and is not deterministic (LLM) and one that executes orders in a deterministic way (Agent Core).&lt;/p&gt;

&lt;p&gt;Paradoxically, the decision-making part is beyond our control: it is not deterministic, we have no tools to predict with certainty whether it will make the decision we expect or not.&lt;/p&gt;

&lt;p&gt;But in this scenario we forget one important thing: &lt;em&gt;it is not only the LLM that makes decisions&lt;/em&gt;. The Agent Core can make decisions as well. And that is not all. Our earlier second observation tells us that the Agent Core is the only component that interacts with the LLM. All the input coming from the user, other agents, external tools, and resources is filtered by the Agent Core before going to the LLM. All the output going to the users, other agents, external tools and resources comes through the Agent Core. &lt;strong&gt;The LLM cannot directly interact with the external world&lt;/strong&gt;. That is a great thing in terms of security!&lt;/p&gt;

&lt;p&gt;Let’s see how to apply each law to the soul-based architecture of an AI agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Control Law
&lt;/h2&gt;

&lt;p&gt;The first law is about gaining control over data. It states:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An AI agent must safeguard all data entrusted to it and shall not, through action or inaction, allow this data to be exposed to any unauthorized user.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Translating this law in terms of the AI agent architecture, we can say that, when acting on behalf of a user, &lt;strong&gt;the probabilistic soul must never access data that the user is not authorized to access&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To implement this law, make sure your Agent Core has control over any data going to the LLM and to the user/other agents. Make sure that private data remains private. Filter data before sending it to the LLM, the user, or other agents. Apply access control according to your use case.&lt;/p&gt;

&lt;p&gt;A typical example of the need for data control is in retrieval algorithms implementation, such as in RAG systems. You specialize your agent’s knowledge with an external source of data, such as a vector database, and want to prevent the user from accessing data they are not authorized to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The deterministic soul of the agent has the responsibility to filter the data before passing it to the LLM&lt;/strong&gt;. Without this filter, a user asking “summarize my documents” could inadvertently receive documents belonging to other users: a data leak the LLM itself would never catch.&lt;/p&gt;

&lt;p&gt;Take a look at the following blog posts to see how to implement the data control law for RAG systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/building-a-secure-rag-with-python-langchain-and-openfga/" rel="noopener noreferrer"&gt;Building a Secure RAG with Python, LangChain, and OpenFGA&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/genai-langchain4j-java-openfga-rag/" rel="noopener noreferrer"&gt;Secure Java AI Agents: Authorization for RAG Using LangChain4j and Auth0 FGA&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/secure-dotnet-rag-system-with-auth0-fga/" rel="noopener noreferrer"&gt;Secure a .NET RAG System with Auth0 FGA&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/genai-llamaindex-js-fga/" rel="noopener noreferrer"&gt;Build a Secure RAG Agent Using LlamaIndex and Auth0 FGA on Node.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Command Control Law
&lt;/h2&gt;

&lt;p&gt;The second law focuses on controlling the command flow and making sure that an AI agent does what it is called to do. Nothing more. The law says:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An AI agent must execute its functions within the narrowest scope of authority necessary. It shall not escalate its own privileges, share secrets, or obey any order that would conflict with the First Law.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s translate this law in terms of the agent architecture.&lt;/p&gt;

&lt;p&gt;If you want to prevent an agent from sharing secrets, do not share secrets with the agent. Or better, &lt;strong&gt;the probabilistic soul (LLM) must never access secrets or tokens&lt;/strong&gt;. The deterministic soul can manage tokens, but you must take steps to minimize the likelihood of these falling into the wrong hands. For example, you should use tokens with a short life, but this requires frequent renewals with &lt;a href="https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/" rel="noopener noreferrer"&gt;refresh tokens&lt;/a&gt;. If your agent interacts with multiple third-party services (e.g., Gmail, Slack, Stripe, etc.), do not store long-lived tokens locally for each third-party service. Use a &lt;a href="https://auth0.com/features/token-vault" rel="noopener noreferrer"&gt;token vault&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;Check out these articles to learn what is the best approach to protect the tokens used by your agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/ai-assistant-langgraph-nextjs-slack-github/" rel="noopener noreferrer"&gt;Build an AI Assistant with LangGraph, Next.js, and Auth0 Connected Accounts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/third-party-tool-calling-llamaindex-auth0-python/" rel="noopener noreferrer"&gt;Secure Third-Party Tool Calling in LlamaIndex Using Auth0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/microsoft-agent-framework-python-auth0-token-vault/" rel="noopener noreferrer"&gt;MS Agent Framework and Python: Use the Auth0 Token Vault to Call Third-Party APIs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To give your agent only the permissions it needs, use scopes in your access tokens and &lt;a href="https://auth0.com/blog/oauth2-access-tokens-and-principle-of-least-privilege/" rel="noopener noreferrer"&gt;apply the principle of least privilege&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But threats to the second law do not just come from token and permission management. These can arise from the input received by the agent: consider prompt injection, which could fool the probabilistic soul of your agent and bypass certain constraints.&lt;/p&gt;

&lt;p&gt;The output generated by the LLM can also pose a threat, especially when it is intended to invoke a tool.&lt;/p&gt;

&lt;p&gt;In other words, &lt;strong&gt;the deterministic soul is responsible for sanitizing LLM’s input and output&lt;/strong&gt;. To learn some techniques for preventing prompt injections and handling improper output, read the following articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/prompt-injection-ai-browser/#Indirect-Prompt-Injections" rel="noopener noreferrer"&gt;Hiding Prompts in Plain Sight: A New AI Security Risk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/owasp-llm05-improper-output-handling/" rel="noopener noreferrer"&gt;Trusting AI Output? Why Improper Output Handling is the New XSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Decision Control Law
&lt;/h2&gt;

&lt;p&gt;The third law is about decision control and says:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An AI agent must cede final authority for any critical or irreversible decision to its human operator, as long as this deference does not conflict with the First or Second Law.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In traditional software, the paths between a given input and its output are somehow predetermined. In an agent, you have an unlimited variety of combinations between inputs and outputs, determined almost entirely by the decisions made by the LLM in interpreting the prompt.&lt;/p&gt;

&lt;p&gt;In the AI agent architecture, we are delegating to the probabilistic soul the task of interpreting the input and determining what tools to use to achieve a given goal. This gives the agent enormous flexibility and, at the same time, a great responsibility.&lt;/p&gt;

&lt;p&gt;To implement the decision control law, you should limit the responsibility of your agent by identifying what are the most critical actions that it can do on your behalf and ask confirmation for executing those actions. &lt;strong&gt;The deterministic soul is responsible for involving the user in critical decisions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Depending on the type of agent you are building, these confirmation requests can be interactive or asynchronous. Think of interactive requests like those made by Claude Code or GitHub Copilot when they ask for permission to write to a folder or modify your code. Asynchronous permission requests are those that an unattended agent can send you via email or push notifications.&lt;/p&gt;

&lt;p&gt;Whatever your case, engaging the user is crucial to preventing potentially irreparable damage.&lt;/p&gt;

&lt;p&gt;Here are a few examples of how you can deal with asynchronous authorization using Auth0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/mitigate-excessive-agency-ai-agents/" rel="noopener noreferrer"&gt;Securing AI Agents: Mitigate Excessive Agency with Zero Trust Security&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/secure-human-in-the-loop-interactions-for-ai-agents/" rel="noopener noreferrer"&gt;Secure “Human in the Loop” Interactions for AI Agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/blog/async-ciba-python-langgraph-auth0/" rel="noopener noreferrer"&gt;Implementing Asynchronous Human-in-the-Loop Authorization in Python with LangGraph and Auth0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/use-ciba-authentication-with-auth0-dotnet/" rel="noopener noreferrer"&gt;Use CIBA Authentication with Auth0 and .NET&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your Takeaways for Securing AI Agents
&lt;/h2&gt;

&lt;p&gt;The “two souls” model gives you a concrete mental model for AI agent security. The security insight here is counterintuitive: the component you control least (the LLM) is not where you focus your security effort. &lt;strong&gt;Security must focus on architecting the deterministic soul to constrain the probabilistic one&lt;/strong&gt;. This perspective allows you to apply the &lt;a href="https://auth0.com/blog/three-laws-ai-security/" rel="noopener noreferrer"&gt;Three Laws of AI Security&lt;/a&gt; by enforcing the Agent Core's responsibility for filtering data, sanitizing inputs/outputs, and managing critical human-in-the-loop decisions.&lt;/p&gt;

&lt;p&gt;Here are the main takeaways that I want to highlight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security is in the code:&lt;/strong&gt; You cannot secure the LLM directly; secure the deterministic Agent Core that wraps and controls it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraint is control:&lt;/strong&gt; Use the Agent Core to strictly enforce boundaries on the LLM's access to data, secrets, tokens, and external actions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human override:&lt;/strong&gt; Implement "Human in the Loop" protocols in the deterministic soul for all critical or irreversible decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing all of this from scratch can be challenging and risky. &lt;a href="https://auth0.com/ai" rel="noopener noreferrer"&gt;Auth0 for AI Agents&lt;/a&gt; helps you handle the deterministic security layer for you (token management, access control, and human-in-the-loop flows) so you can focus on what your agent actually does.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Manage Your Auth0 Tenants Faster with the Gemini CLI Extension</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Wed, 29 Apr 2026 21:01:35 +0000</pubDate>
      <link>https://dev.to/auth0/manage-your-auth0-tenants-faster-with-the-gemini-cli-extension-2dh8</link>
      <guid>https://dev.to/auth0/manage-your-auth0-tenants-faster-with-the-gemini-cli-extension-2dh8</guid>
      <description>&lt;p&gt;Stop switching between your browser and terminal to manage identity. In this video, I'll show you how to integrate the &lt;em&gt;Auth0 MCP Server&lt;/em&gt; directly into your &lt;em&gt;Gemini CLI&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Learn how to leverage AI to query your applications, initialize tenants, and manage APIs using natural language commands without leaving your development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to find the Auth0 extension on the official Gemini page.&lt;/li&gt;
&lt;li&gt;The one-command installation process for the Auth0 MCP server.&lt;/li&gt;
&lt;li&gt;Authenticating and initializing your Auth0 environment via CLI.&lt;/li&gt;
&lt;li&gt;Using natural language to list applications and create new API resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/auth0/auth0-mcp-server" rel="noopener noreferrer"&gt;💻 Auth0 MCP Server GitHub Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/docs/get-started/auth0-mcp-server" rel="noopener noreferrer"&gt;🛠️ Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/auth0-mcp-server-in-gemini-cli-extensions" rel="noopener noreferrer"&gt;📝 Auth0 Blog Post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tooling</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Things Developers Get Wrong About the Backend for Frontend Pattern</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 24 Apr 2026 15:51:18 +0000</pubDate>
      <link>https://dev.to/auth0/things-developers-get-wrong-about-the-backend-for-frontend-pattern-10n1</link>
      <guid>https://dev.to/auth0/things-developers-get-wrong-about-the-backend-for-frontend-pattern-10n1</guid>
      <description>&lt;p&gt;Since I published my &lt;a href="https://auth0.com/blog/the-backend-for-frontend-pattern-bff" rel="noopener noreferrer"&gt;overview of the Backend for Frontend (BFF) pattern&lt;/a&gt;, the questions I've received fall into surprisingly consistent patterns. The same misunderstandings come up again and again, from developers who genuinely want to build secure apps.&lt;/p&gt;

&lt;p&gt;Most of these misconceptions aren't just academic. They lead teams to ship apps with real security gaps while believing they've done the right thing. Let me address the ones I see most often.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PKCE Isn't a Replacement for BFF
&lt;/h2&gt;

&lt;p&gt;This is the one I encounter most, and it's the most consequential.&lt;/p&gt;

&lt;p&gt;The OAuth working group deprecated the &lt;a href="https://oauth.net/2/grant-types/implicit/" rel="noopener noreferrer"&gt;Implicit Grant&lt;/a&gt; for SPAs and recommended &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce" rel="noopener noreferrer"&gt;Authorization Code with PKCE&lt;/a&gt; as the replacement. That guidance is correct. In addition, OAuth 2.1 recommends PKCE for any client, not just SPAs. Somehow, many developers concluded that PKCE also addresses token storage security. It doesn't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/blog/demystifying-oauth-security-state-vs-nonce-vs-pkce/" rel="noopener noreferrer"&gt;PKCE (Proof Key for Code Exchange)&lt;/a&gt; protects the authorization code in transit. It prevents an attacker who intercepts the authorization code from exchanging it for tokens. Valuable, but it solves only one step of the OAuth flow.&lt;/p&gt;

&lt;p&gt;Once your app receives tokens, PKCE has done its job. It has nothing to say about where those tokens live in the browser or what happens if a &lt;a href="https://auth0.com/blog/cross-site-scripting-xss/" rel="noopener noreferrer"&gt;Cross-Site Scripting (XSS)&lt;/a&gt; attack runs in your app's context. Tokens in &lt;code&gt;localStorage&lt;/code&gt;, &lt;code&gt;sessionStorage&lt;/code&gt;, or even JavaScript memory are all reachable by malicious scripts.&lt;/p&gt;

&lt;p&gt;BFF solves a different problem: &lt;strong&gt;it keeps tokens out of the browser entirely&lt;/strong&gt;. The BFF exchanges the authorization code for tokens and stores them server-side. The browser gets an &lt;code&gt;HttpOnly&lt;/code&gt; session cookie. An XSS attack running in that browser can't steal what isn't there.&lt;/p&gt;

&lt;p&gt;PKCE and BFF are complementary, not alternatives. If your &lt;a href="https://owasp.org/www-community/Threat_Modeling" rel="noopener noreferrer"&gt;threat model&lt;/a&gt; includes XSS (and for most apps, it should), PKCE alone isn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  BFF Is Not Just a Proxy
&lt;/h2&gt;

&lt;p&gt;In a couple of cases, I've reviewed architectures labeled as "BFF" that were actually reverse proxies forwarding requests (and tokens) to a backend. That's not a BFF.&lt;/p&gt;

&lt;p&gt;The defining characteristic of a Backend for Frontend is that it acts as a &lt;strong&gt;confidential OAuth client&lt;/strong&gt;. It holds a client secret. It handles the full OAuth flow, including token exchange. Most critically, &lt;strong&gt;tokens never leave the server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A reverse proxy that forwards an Authorization header containing a bearer token is not a BFF. The token is still accessible to the browser. You've added a network hop without the security benefit.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps" rel="noopener noreferrer"&gt;IETF OAuth 2.0 for Browser-Based Apps BCP&lt;/a&gt; actually distinguishes between a &lt;em&gt;Token-Mediating Backend&lt;/em&gt; (a backend that obtains tokens and then forwards them to the frontend) and a proper BFF (where tokens are never passed to the frontend at all). These are different patterns with different security properties.&lt;/p&gt;

&lt;p&gt;If your backend is passing tokens to the browser in any form, you haven't implemented BFF. You've implemented token mediation, which is better than nothing, but it's not the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  No, Cookies Are Not Less Secure Than Tokens
&lt;/h2&gt;

&lt;p&gt;This one has roots in a legitimate historical concern. Cookies have a complicated reputation: they've been misused, and &lt;a href="https://auth0.com/blog/cross-site-request-forgery-csrf/" rel="noopener noreferrer"&gt;Cross-Site Request Forgery (CSRF)&lt;/a&gt; attacks were a real problem before &lt;code&gt;SameSite&lt;/code&gt; became standard. Some of the "don't use cookies" instinct also came from REST API orthodoxy, where stateless communication was treated as a design virtue.&lt;/p&gt;

&lt;p&gt;But here's what modern reality looks like: an &lt;code&gt;HttpOnly&lt;/code&gt; session cookie cannot be read by JavaScript. That means XSS attacks can't steal it directly. A JWT in &lt;code&gt;localStorage&lt;/code&gt; can be read by any script running on your page.&lt;/p&gt;

&lt;p&gt;The attack surface isn't symmetric. A CSRF attack using an &lt;code&gt;HttpOnly&lt;/code&gt; cookie requires the attacker to trick a user into making a specific authenticated request from another origin. An XSS attack that steals a &lt;code&gt;localStorage&lt;/code&gt; token gives the attacker full, direct access to call any API as that user, from anywhere, without any user interaction required.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;SameSite=Strict&lt;/code&gt; or &lt;code&gt;SameSite=Lax&lt;/code&gt;, CSRF attacks against &lt;code&gt;HttpOnly&lt;/code&gt; cookies are already difficult in most real-world scenarios. Add explicit CSRF tokens and they become practically infeasible.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HttpOnly&lt;/code&gt; cookies aren't immune to CSRF, but the attack surface is far smaller than XSS-based token theft. You're trading one attack vector (XSS-based token theft) for a different, more constrained one (CSRF). That trade is almost always worth making.&lt;/p&gt;

&lt;h2&gt;
  
  
  BFF Does Not Solve All Your Browser Auth Security Problems
&lt;/h2&gt;

&lt;p&gt;BFF shifts the security boundary. It doesn't eliminate it.&lt;/p&gt;

&lt;p&gt;When you adopt BFF, you trade the token theft problem (XSS can steal tokens from browser storage) for the session management problem (your BFF now manages sessions and those need to be secured properly). This is a good trade in most cases, but it comes with responsibilities that BFF doesn't automatically fulfill.&lt;/p&gt;

&lt;p&gt;Things BFF does not handle on its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSRF protection.&lt;/strong&gt; Your BFF uses cookies, which means state-changing requests need CSRF protection. &lt;code&gt;SameSite&lt;/code&gt; cookies help significantly, but this is still your responsibility to configure correctly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session invalidation.&lt;/strong&gt; When a user logs out, you need to revoke the session server-side, not just clear the cookie client-side. If you don't, stolen session cookies remain valid until natural expiry.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure cookie configuration.&lt;/strong&gt; &lt;code&gt;Secure&lt;/code&gt;, &lt;code&gt;HttpOnly&lt;/code&gt;, and the right &lt;code&gt;SameSite&lt;/code&gt; setting are all required. Missing any of them weakens the pattern's security properties.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization checks in your API.&lt;/strong&gt; BFF protects the token in transit. It doesn't automatically secure your API endpoints. You still need proper authorization logic on the backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don’t implement BFF and then relax your overall security posture, assuming the pattern covers everything. It doesn't. Treat it as one layer in a defense-in-depth approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  You Don’t Need to Rewrite Your Entire Application
&lt;/h2&gt;

&lt;p&gt;The assumption that teams must rewrite the entire application prevents them from adopting BFF even when they should.&lt;/p&gt;

&lt;p&gt;The full vision of BFF, as &lt;a href="https://samnewman.io/patterns/architectural/bff/" rel="noopener noreferrer"&gt;Sam Newman originally described it&lt;/a&gt;, is a server tailored to the specific needs of one frontend. That can mean a significant rearchitecting effort. But you don't have to implement the full pattern at once to get the security benefits.&lt;/p&gt;

&lt;p&gt;In practice, many teams introduce BFF incrementally. The most common path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a lightweight backend (Node.js, Next.js server components, ASP.NET Core, Python, or whatever fits your stack) that handles the OAuth flow.
&lt;/li&gt;
&lt;li&gt;The BFF exchanges authorization codes for tokens, stores them server-side, and issues session cookies to the browser.
&lt;/li&gt;
&lt;li&gt;Your existing frontend continues making API calls, now using session cookies instead of bearer tokens.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your existing backend APIs often don't change at all. You're inserting the BFF as the authentication layer, not replacing your entire architecture.&lt;/p&gt;

&lt;p&gt;The incremental path is real, and the auth-focused version of BFF is where most of the security value lives anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With the Threat Model
&lt;/h2&gt;

&lt;p&gt;Before deciding whether to implement BFF, be honest about your app's threat model.&lt;/p&gt;

&lt;p&gt;If your application handles sensitive data, if XSS is a credible risk (and it usually is, especially for apps loading third-party scripts or rendering user-generated content), or if you operate in a regulated industry, BFF is the right choice. The complexity cost is manageable and the security benefit is concrete.&lt;/p&gt;

&lt;p&gt;If you're building a simple public-facing app with no sensitive user data and strong existing XSS defenses, a well-implemented SPA with PKCE and in-memory token storage may be acceptable.&lt;/p&gt;

&lt;p&gt;The BFF pattern exists because the browser is a hostile environment for tokens. If that threat is real for your application (and you're the best judge of that) BFF addresses it in ways that PKCE and in-browser token handling simply can't.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>bff</category>
      <category>security</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>From Backend Engineer to Building AI Infrastructure at a Startup</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Wed, 22 Apr 2026 16:05:00 +0000</pubDate>
      <link>https://dev.to/auth0/from-backend-engineer-to-building-ai-infrastructure-at-a-startup-1cco</link>
      <guid>https://dev.to/auth0/from-backend-engineer-to-building-ai-infrastructure-at-a-startup-1cco</guid>
      <description>&lt;p&gt;What does it look like to go from a six-person startup team to running the infrastructure behind 1,000+ AI models?&lt;/p&gt;

&lt;p&gt;Matteo and I have known each other for over 10 years, but I realized I'd never actually asked him about his engineering journey in depth. I knew he was doing infrastructure work, but I didn't know the full story of how he got there. This conversation filled in a lot of gaps for me.&lt;/p&gt;

&lt;p&gt;In Episode 4 of Making Software, I talked to &lt;strong&gt;Matteo Ferrando&lt;/strong&gt;, Platform and Infra Engineer at &lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;fal.ai&lt;/a&gt;, about exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we covered
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The pivot that changed everything.&lt;/strong&gt; The company didn't start as an AI company. Matteo talks about what the original product was, how the pivot happened, and why letting go of something that's working is one of the hardest things you'll do at a startup as an engineer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How a backend engineer ends up doing infra.&lt;/strong&gt; There was no "switch." Matteo explains the reality of early-stage startups where in the beginning, there's no database, no Kubernetes cluster, no nothing. You just build it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A decision-making framework worth stealing.&lt;/strong&gt; One-way doors vs. two-way doors. Simple concept, but it changes how you think about MVPs, technical debt, and when to actually invest in doing things "the right way."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimizing a routing layer from 100ms to 5ms.&lt;/strong&gt; The use case that forced this is wild. I'll let Matteo tell that story.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you still need computer science fundamentals?&lt;/strong&gt; We talked about AI coding tools, what they're great at, and a very specific failure mode that Matteo keeps seeing on his team.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A couple of things Matteo said that stuck with me
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm not obsessed about technical debt like many engineers are. It's fine to have technical debt and we'll get to it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This resonated a lot with me. I think a lot of us carry guilt about tech debt like it's something we did wrong. Matteo's framing is different: it's not a problem until it's actually a problem. That shift in perspective is freeing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A mistake introduced by an AI that you prompted is still your mistake. You still have to understand what you're shipping."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This one hit. Especially right now, when so many of us are leaning on AI tools to write code faster. Faster is great, but Matteo makes a really compelling case for why "I didn't write it" is not an excuse. He shares a specific example from his team that I think every developer using AI tools needs to hear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Talking to Matteo reminded me that there's no clean, linear path in engineering. You don't go from "backend engineer" to "infra engineer" because you planned it. You get there because something needs to exist and you're the one who builds it. I think that's something a lot of us can relate to, especially if you've ever worked at a startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you ever had to build something way outside your comfort zone because no one else was going to?&lt;/strong&gt; Let me know in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  Listen to the full episode
&lt;/h2&gt;

&lt;p&gt;Available on &lt;a href="https://www.youtube.com/playlist?list=PLZ14qQz3cfJKRDmX3yasmbwoC4kipeQfu" rel="noopener noreferrer"&gt;&lt;strong&gt;YouTube&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://podcasts.apple.com/us/podcast/making-software/id1872107131" rel="noopener noreferrer"&gt;&lt;strong&gt;Apple Podcasts&lt;/strong&gt;&lt;/a&gt;, and &lt;a href="https://open.spotify.com/show/6J856S2fijMvP3rzFkRnBi" rel="noopener noreferrer"&gt;&lt;strong&gt;Spotify&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading! 👋&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>startup</category>
      <category>career</category>
    </item>
    <item>
      <title>How to Use Auth0 Agent Skills in Claude Code &amp; AI Coding Assistants</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Mon, 06 Apr 2026 18:27:59 +0000</pubDate>
      <link>https://dev.to/auth0/how-to-use-auth0-agent-skills-in-claude-code-ai-coding-assistants-56e5</link>
      <guid>https://dev.to/auth0/how-to-use-auth0-agent-skills-in-claude-code-ai-coding-assistants-56e5</guid>
      <description>&lt;p&gt;Tired of your AI coding assistant hallucinating APIs or writing insecure auth patterns? In this video, I'll show you how to use Auth0 Agent Skills to teach your AI assistant (like Claude Code or GitHub Copilot) how to implement Auth0 correctly. Say goodbye to XSS vulnerabilities and manual JWT decoding—ship production-ready, secure authentication from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why standard AI coding assistants struggle with secure authentication.&lt;/li&gt;
&lt;li&gt;How to install Auth0 Agent Skills via NPX or Claude Code plugins.&lt;/li&gt;
&lt;li&gt;The difference between Core Skills and SDK Skills (React, Next.js, etc.).&lt;/li&gt;
&lt;li&gt;A side-by-side comparison of "hallucinated" code vs. secure Auth0 patterns.&lt;/li&gt;
&lt;li&gt;How to implement production-ready auth in Next.js using Agent Skills.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;📝 &lt;a href="https://auth0.com/blog/auth0-agent-skills-ai-coding-assistants/" rel="noopener noreferrer"&gt;Read the full blog post&lt;/a&gt;&lt;br&gt;
🛠️ &lt;a href="https://auth0.com/docs/quickstart/agent-skills" rel="noopener noreferrer"&gt;Auth0 Documentation&lt;/a&gt;&lt;br&gt;
💻 &lt;a href="https://github.com/auth0/agent-skills" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Auth0 MCP Server Extension for Gemini CLI</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 27 Mar 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/auth0/auth0-mcp-server-extension-for-gemini-cli-405m</link>
      <guid>https://dev.to/auth0/auth0-mcp-server-extension-for-gemini-cli-405m</guid>
      <description>&lt;p&gt;The Auth0 MCP Server is now listed on the official Gemini CLI extensions page. This means the Auth0 MCP Server is now directly installable through Gemini CLI with one command, allowing you to authenticate to Auth0 directly from your Gemini CLI session and load tenant information automatically.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iiSuv8BnPC0"&gt;
  &lt;/iframe&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  What the Auth0 MCP Server Extension Provides
&lt;/h2&gt;

&lt;p&gt;The extension packages the Auth0 MCP Server for Gemini CLI and adds three integration layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discoverability&lt;/strong&gt;: Listed on &lt;a href="https://geminicli.com/extensions" rel="noopener noreferrer"&gt;geminicli.com/extensions&lt;/a&gt;, searchable by name, installable without manual configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication Commands&lt;/strong&gt;: Built-in slash commands for Auth0 tenant management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/auth0:init&lt;/code&gt; - Device authorization flow with tenant selection&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/auth0:logout&lt;/code&gt; - Session termination&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/auth0:session&lt;/code&gt; - Current authentication status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Context Injection&lt;/strong&gt;: After authentication, Gemini gains your tenant information so the AI can query applications, APIs, connections, actions, and logs without requiring manual tenant specification in every prompt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;

&lt;p&gt;Install the extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini extensions &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/auth0/auth0-mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the command successfully finishes you should see a message stating &lt;code&gt;Extension “Auth0” installed successfully&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzi13ha0j201bszply11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzi13ha0j201bszply11.png" alt="Terminal showing the installation of the Auth0 MCP server as a Gemini CLI extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initialize the Auth0 MCP Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/auth0:init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to allow the command to run when prompted. The server will run automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7q3o4dmnm02utqeu4ji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7q3o4dmnm02utqeu4ji.png" alt="Gemini CLI terminal showing /auth0:init command with permission prompt to allow command execution"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll authenticate via &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" rel="noopener noreferrer"&gt;device code flow&lt;/a&gt; to select your tenant:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnik2h6j061y0y9brksu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnik2h6j061y0y9brksu.png" alt="Auth0 device authorization screen displaying device code and instructions to complete authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And confirm the permissions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wx8dzfzrmbnntzeaj7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wx8dzfzrmbnntzeaj7b.png" alt="Auth0 authorization screen showing requested permissions for Auth0 MCP Server extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once authenticated, you should see a message within Gemini saying the Auth0 MCP Server is configured and to restart Gemini CLI to see the changes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10jdvkxmdmkufvebcm26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10jdvkxmdmkufvebcm26.png" alt="Gemini CLI terminal displaying successful Auth0 MCP Server initialization with tenant connection confirmed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refresh the MCP server list with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/mcp refresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemini now has Auth0 context. Ask: "show me my applications" and the AI will receive the structured information about your applications, which Gemini CLI will display as a structured tool call result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrab4gmufeqlx8rtwf8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrab4gmufeqlx8rtwf8m.png" alt="Gemini CLI tool call output showing structured JSON data of Auth0 applications from the MCP server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And since Gemini now understands your tenant structure, existing configurations, and naming conventions, it can also show you the same information in a more readable format:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7n6pl8wgu6ipiazokouq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7n6pl8wgu6ipiazokouq.png" alt="Gemini CLI displaying formatted, human-readable list of Auth0 applications with names, types, and client IDs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Before this extension, using the Auth0 MCP Server with Gemini CLI required manual server configuration, environment variable setup, and custom initialization scripts. The extension collapses that into a single install command and three slash commands.&lt;/p&gt;

&lt;p&gt;More importantly: context persistence. Once authenticated, every Gemini session knows your Auth0 environment. You're not re-explaining your tenant structure or copy-pasting app IDs. The AI assistant operates with the same tenant awareness you have.&lt;/p&gt;

&lt;p&gt;This is the same Auth0 MCP Server that powers VS Code integrations, now packaged for Gemini CLI's extension model. Same capabilities, different CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The Auth0 MCP Server supports tenant management, application configuration, API setup, and log analysis. For implementation details and the full MCP Server feature set, &lt;a href="https://github.com/auth0/auth0-mcp-server?tab=readme-ov-file#%EF%B8%8F-supported-tools" rel="noopener noreferrer"&gt;see the list on GitHub here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The extension is available now at &lt;a href="https://geminicli.com/extensions" rel="noopener noreferrer"&gt;geminicli.com/extensions&lt;/a&gt;. Install, authenticate, and start managing Auth0 tenants through natural language.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Future of Coding is Communication, Not Just Code</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Thu, 26 Mar 2026 14:31:00 +0000</pubDate>
      <link>https://dev.to/auth0/the-future-of-coding-is-communication-not-just-code-328p</link>
      <guid>https://dev.to/auth0/the-future-of-coding-is-communication-not-just-code-328p</guid>
      <description>&lt;p&gt;We recently had a great conversation on the Making Software podcast with Bobby Tierney. Bobby is a Principal Architect at Okta and Auth0 who focuses on agentic security, AI governance, and the Model Context Protocol (MCP).&lt;br&gt;
I have been feeling some "AI FOMO" lately because the industry is moving so quickly. Talking to Bobby helped me bridge the gap between the "vibes" of modern AI coding and the security standards we need as professional engineers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vibe Coding vs. Mission Critical Production:&lt;/strong&gt; Bobby shared his perspective on where "vibe coding" fits into the software development life cycle and when it becomes a risky proposition for an enterprise. He explained why prototypes are a great way to explore a solution space even if you eventually throw them away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managing "YOLO Mode" with Sandboxing:&lt;/strong&gt; We discussed the "YOLO mode" found in many AI tools and how engineers can use progressive security configurations to keep things under control. 
-** The Shift to Spec-Driven Development:** Bobby talked about moving away from probabilistic guesswork and toward "spec coding". He explained how to use tools like "skills" or "slash commands" to align an AI with your team's specific DNA and ways of working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Confused Deputy" Problem:&lt;/strong&gt; We touched on the security risks of AI agents and why you shouldn't just give them machine credentials to act on a user's behalf. Bobby highlighted the importance of finding the right intersection between what a user is allowed to do and what the AI is permitted to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Changing Role of the Engineer:&lt;/strong&gt; We explored how AI is shrinking the development life cycle and why communication might be the most valuable skill for a developer in the future. Bobby also shared how designers and PMs are starting to use these tools to contribute directly to codebases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Bobby reminded me that while tools are changing, the core of engineering is still about requirements and making good decisions. &lt;/p&gt;

&lt;p&gt;The age of AI is not about replacing engineers. It is about helping them. By being good communicators, teachers, and security-minded builders, we can use these amazing tools to make better, safer software.&lt;/p&gt;

&lt;p&gt;What is your take on YOLO mode coding? Let me know in the comments! 🌱&lt;br&gt;
If you want to hear more, check out the full episode: &lt;a href="https://listen.casted.us/public/49/Making-Software-2b1cff7b" rel="noopener noreferrer"&gt;https://listen.casted.us/public/49/Making-Software-2b1cff7b&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Common FAPI Misconceptions</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 20 Mar 2026 09:00:30 +0000</pubDate>
      <link>https://dev.to/auth0/common-fapi-misconceptions-121k</link>
      <guid>https://dev.to/auth0/common-fapi-misconceptions-121k</guid>
      <description>&lt;p&gt;For some time now, I've been interested in FAPI from both an Identity practitioner's and a developer's perspective. I've written a few posts on this topic on &lt;a href="https://auth0.com/blog" rel="noopener noreferrer"&gt;the Auth0 blog&lt;/a&gt; and created &lt;a href="https://auth0.com/resources/whitepapers/developers-guide-to-FAPI" rel="noopener noreferrer"&gt;a guide to FAPI&lt;/a&gt; with the support of colleagues who are much more experienced than I am. Surfing the web and talking to developers, however, I couldn't help but notice some misunderstandings about certain aspects of FAPI.&lt;br&gt;&lt;br&gt;
In this article, I'll summarize the most common and recurring ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 1: FAPI Is a New Protocol
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FAPI is a security profile based on OAuth 2.1&lt;/strong&gt;, it is &lt;strong&gt;not a new protocol&lt;/strong&gt;, intended as an alternative to established standards like OAuth 2.0, SAML, or OpenID Connect (OIDC).&lt;/p&gt;

&lt;p&gt;It acts as a prescriptive blueprint that defines exactly which OAuth 2.0 and OIDC extensions must be used and how they must be configured. While the core OAuth 2.0 specification (&lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;RFC 6749&lt;/a&gt;) is a flexible framework that provides a "toolbox" of flows and leaves security decisions to the implementer, FAPI removes this "dangerous flexibility" to ensure a secure-by-default posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 2: FAPI Is for Banks and Financial Organizations
&lt;/h2&gt;

&lt;p&gt;The "F" in FAPI originally stood for "Financial," reflecting its initial goal to protect banking applications. However, the scope has expanded significantly. Today, FAPI is a general-purpose high-security profile for any industry handling sensitive, high-risk data. Major applications include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E-Health:&lt;/strong&gt; Protecting Patient Health Information (PHI) in standards like &lt;a href="https://hl7.org/fhir/" rel="noopener noreferrer"&gt;HL7 FHIR&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E-Signing:&lt;/strong&gt; Securing the underlying API calls for legally binding digital signatures.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Government Services:&lt;/strong&gt; Managing Personally Identifiable Information (PII) and government services.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Misconception 3: FAPI 2.0 Is an Incremental Update of FAPI 1.0
&lt;/h2&gt;

&lt;p&gt;Some people mistakenly believe FAPI 2.0 is a minor version bump that maintains backward compatibility. This is incorrect. FAPI 2.0 is a complete redesign based on lessons learned from FAPI 1.0, specifically aimed at simplifying the developer experience.&lt;/p&gt;

&lt;p&gt;A key change is the removal of the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/hybrid-flow" rel="noopener noreferrer"&gt;OpenID Connect Hybrid Flow&lt;/a&gt; used in FAPI 1.0 "Advanced," which required complex front-channel validations of ID Tokens. FAPI 2.0 replaces this with a hardened &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow" rel="noopener noreferrer"&gt;Authorization Code Flow&lt;/a&gt; where all tokens are delivered via the back-channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 4: OAuth 2.1 Makes FAPI Redundant
&lt;/h2&gt;

&lt;p&gt;As OAuth 2.1 incorporates modern best practices like &lt;a href="https://datatracker.ietf.org/doc/html/rfc7636" rel="noopener noreferrer"&gt;PKCE&lt;/a&gt; and the removal of the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/implicit-flow-with-form-post" rel="noopener noreferrer"&gt;Implicit Flow&lt;/a&gt;, some wonder if FAPI 2.0 remains necessary. However, the difference between the two is significant.&lt;/p&gt;

&lt;p&gt;OAuth 2.1 is a "Best Current Practice" consolidation for the general community, while FAPI 2.0 is a "high-security profile" that mandates features OAuth 2.1 only recommends. Crucially, FAPI 2.0 is built on a &lt;a href="https://openid.net/specs/fapi-attacker-model-2_0-final.html" rel="noopener noreferrer"&gt;Formal Attacker Model&lt;/a&gt;, providing proof of security against specific threats that OAuth 2.1 does not formally address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 5: You Can Use Any Type of Client with FAPI
&lt;/h2&gt;

&lt;p&gt;Standard OAuth 2.0 allows for both public (e.g., mobile, browser-based) and confidential (server-side) clients. However, while FAPI 1.0 Baseline specification allows for public clients, &lt;strong&gt;FAPI 1.0 Advanced and FAPI 2.0 explicitly excludes support for them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAPI 2.0 focuses on confidential clients&lt;/strong&gt; because they are capable of protecting a private key. This is the cornerstone of FAPI’s security model; a client cannot perform asymmetric authentication or prove possession of a sender-constrained token if it cannot keep its private key secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 6: You Can’t Use Public Clients with FAPI
&lt;/h2&gt;

&lt;p&gt;As a consequence of the previous point, you may conclude that you can’t use public clients at all with FAPI. So, you can’t have mobile applications or SPAs in a FAPI-based system.&lt;/p&gt;

&lt;p&gt;Actually, this is not entirely true. You can still have public clients in a FAPI context provided that they don’t manage ID and access tokens. For example, you can have a SPA if you are using an architectural pattern like the &lt;a href="https://auth0.com/blog/the-backend-for-frontend-pattern-bff/" rel="noopener noreferrer"&gt;Backend for Frontend pattern&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 7: Pushed Authorization Requests Support Simply Shortens URLs
&lt;/h2&gt;

&lt;p&gt;Developers often view &lt;a href="https://auth0.com/blog/what-are-oauth-push-authorization-requests-par/" rel="noopener noreferrer"&gt;Pushed Authorization Requests&lt;/a&gt; (PAR) as merely an optional optimization for long URLs. In reality, FAPI 2.0 makes PAR mandatory because it moves the entire authorization request from the &lt;em&gt;risky&lt;/em&gt; environment of the browser to the secure back-channel.&lt;/p&gt;

&lt;p&gt;Standard GET requests expose sensitive data (scopes, identifiers) to browser history, server logs, and Referer headers. PAR solves this by having the client POST parameters directly to the server, receiving an opaque request_uri in return. This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Confidentiality:&lt;/strong&gt; Sensitive parameters are never in the URL.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrity:&lt;/strong&gt; The server authenticates the request before the user is ever involved.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability:&lt;/strong&gt; It bypasses URL length limits that often break complex authorization requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The journey through the FAPI landscape often involves navigating a complex mix of technical details and outdated assumptions. As this article has shown, FAPI is far more than an obscure security framework for banks; it is a critical, mature security profile that strips away the risky flexibility of standard OAuth 2.0 to offer a secure foundation for any system handling high-value data.&lt;/p&gt;

&lt;p&gt;From understanding that FAPI is a profile, not a new protocol, to recognizing the fundamental changes in FAPI 2.0 and its mandated use of features like Pushed Authorization Requests (PAR), a clear picture emerges. FAPI’s strict reliance on confidential clients and back-channel communications is a pragmatic response to known threat models, ensuring integrity and confidentiality where it matters most.&lt;/p&gt;

&lt;p&gt;Embracing FAPI is not about adding unnecessary complexity. It is about adopting a proven security posture necessary for protecting assets in e-Health, e-Signing, government services, and beyond. By moving past these common misconceptions, organizations can leverage FAPI to build truly robust and compliant high-security applications.&lt;/p&gt;

&lt;p&gt;Here is an infographic that summarizes the misconceptions discussed in this article. Keep it handy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdms4lk1ygtas81a32fkw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdms4lk1ygtas81a32fkw.jpg" alt="Infographic about the seven common misconceptions on FAPI" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>fapi</category>
      <category>oauth</category>
      <category>identity</category>
      <category>security</category>
    </item>
    <item>
      <title>Demystifying OAuth Security: State vs. Nonce vs. PKCE</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 13 Mar 2026 16:17:44 +0000</pubDate>
      <link>https://dev.to/auth0/demystifying-oauth-security-state-vs-nonce-vs-pkce-2eo2</link>
      <guid>https://dev.to/auth0/demystifying-oauth-security-state-vs-nonce-vs-pkce-2eo2</guid>
      <description>&lt;p&gt;Confused by the random strings in your OAuth URLs? You aren't alone. Many developers think &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, and &lt;code&gt;code_challenge&lt;/code&gt; (PKCE) are redundant—but skipping just one could leave your users' accounts wide open to attackers like "Eve." In this video, I'll break down why these three parameters are like three different locks on three different doors. We’ll look at real-world attack scenarios and show you exactly how each one keeps your app secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡  What You’ll Learn:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The State Parameter:&lt;/strong&gt; How to prevent Cross-Site Request Forgery ($CSRF$) attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Nonce Parameter:&lt;/strong&gt; Why ID tokens need protection against Replay attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PKCE (Proof Key for Code Exchange):&lt;/strong&gt; Protecting mobile and single-page apps from Authorization Code Injection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementation Strategy:&lt;/strong&gt; Why you should use all three instead of picking just one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://auth0.com/blog/demystifying-oauth-security-state-vs-nonce-vs-pkce" rel="noopener noreferrer"&gt;Read the full blog post by Andrea Chiarelli&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce" rel="noopener noreferrer"&gt;Auth0 Docs - Why PKCE?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://oauth.net/2/security-topics/" rel="noopener noreferrer"&gt;OAuth 2.0 Security Best Practices&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you enjoy this content and want to learn more about identity, security, and access management, subscribe to our channel! &lt;/p&gt;

&lt;p&gt;Have a topic you'd like to see covered? Let us know if the comments below 👀 &lt;/p&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>software</category>
    </item>
    <item>
      <title>Secure a C# MCP Server with Auth0</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 13 Mar 2026 09:59:59 +0000</pubDate>
      <link>https://dev.to/auth0/secure-a-c-mcp-server-with-auth0-4p0n</link>
      <guid>https://dev.to/auth0/secure-a-c-mcp-server-with-auth0-4p0n</guid>
      <description>&lt;p&gt;As the &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; gains traction, the transition from "localhost" experimentation to enterprise integration introduces a critical challenge: security. For developers building sophisticated integrations, treating every LLM request as an "admin" action is a significant risk.&lt;/p&gt;

&lt;p&gt;Luckily, the MCP specification supports authorization based on OAuth 2.1. So, basically, to protect your MCP server, you have to treat it as a resource server with some extra challenges in case you want to share it publicly to a wide audience.&lt;/p&gt;

&lt;p&gt;The .NET ecosystem can leverage the &lt;a href="https://csharp.sdk.modelcontextprotocol.io/" rel="noopener noreferrer"&gt;C# SDK for MCP&lt;/a&gt; to easily expose tools and resources to an AI-powered application. The SDK reached maturity recently &lt;a href="https://devblogs.microsoft.com/dotnet/release-v10-of-the-official-mcp-csharp-sdk/" rel="noopener noreferrer"&gt;supporting all the features defined by the specification&lt;/a&gt;, including security.&lt;/p&gt;

&lt;p&gt;This article explores a practical implementation of a secured MCP server. You will walk through a sample project and learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement three distinct tools: one available for public consumption and two gated behind specific permission sets.
&lt;/li&gt;
&lt;li&gt;Make the protected MCP server available to an MCP client, such as VSCode, leveraging Dynamic Client Registration (DCR) supported by Auth0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Protect an MCP Server?
&lt;/h2&gt;

&lt;p&gt;In a standard local setup, the MCP server and the client often share the same security boundary. However, as soon as an MCP server is deployed as a shared service or connected to a multi-user LLM platform, that boundary shifts. Protecting the server becomes relevant for at least two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Preventing prompt injection escalation&lt;/strong&gt;. If a tool allows an LLM to interact with internal databases or APIs, a malicious prompt could attempt to trick the model into executing commands it shouldn't. By implementing strict authorization checks at the server level, you provide a defense in depth strategy. Even if the LLM is convinced to call a restricted tool, the MCP server will reject the request because the underlying user context lacks the necessary permissions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource isolation and multi-tenancy&lt;/strong&gt;. Not every user interacting with an AI agent should have the same capabilities. For example, a junior developer might have access to a &lt;em&gt;ReadDocumentation&lt;/em&gt; tool, but only a lead engineer should be able to trigger a &lt;em&gt;DeployProduction&lt;/em&gt; tool. Without protection, the MCP server treats all requests as equal, which is unacceptable in any regulated or professional environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These reasons are what OAuth 2.1 support in the MCP specification aims to prevent. We will use it to protect a sample MCP server built with C# by integrating with Auth0.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To learn how Auth0 helps you secure MCP clients and servers, check out &lt;a href="https://auth0.com/ai/docs/mcp/intro/overview" rel="noopener noreferrer"&gt;Auth for MCP&lt;/a&gt;. For a detailed explanation of authorization support in MCP, read the &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;Authorization specification&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Build Your MCP Server in C
&lt;/h2&gt;

&lt;p&gt;Let’s begin our exploration by implementing a very simple MCP server that we will later extend and secure using Auth0.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;The MCP server project we are going to implement requires the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/download/dotnet/10.0" rel="noopener noreferrer"&gt;.NET SDK 10&lt;/a&gt; or later,
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://csharp.sdk.modelcontextprotocol.io/" rel="noopener noreferrer"&gt;C# SDK for MCP&lt;/a&gt; package,
&lt;/li&gt;
&lt;li&gt;An Auth0 account. If you don’t have it, &lt;a href="https://a0.to/blog_signup" rel="noopener noreferrer"&gt;sign up for free here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create the MCP Server
&lt;/h3&gt;

&lt;p&gt;To create a simple MCP server, you can use the MCP server template project provided by Microsoft. Currently, the template is in preview, so you won’t find it in the builtin template set. You can download and install the package by running the following command in a terminal window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Microsoft.McpServer.ProjectTemplates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have downloaded the project template, you can create your MCP server project by running a command like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new mcpserver &lt;span class="nt"&gt;-n&lt;/span&gt; AspNetCoreMcpServer &lt;span class="nt"&gt;-t&lt;/span&gt; remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates a new MCP server project named &lt;code&gt;AspNetCoreMcpServer&lt;/code&gt; in a folder with the same name. The option &lt;code&gt;-t remote&lt;/code&gt; specifies to create an MCP server based on HTTP transport, which leads to actually creating an ASP.NET Core application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MCP servers can use &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/transports" rel="noopener noreferrer"&gt;stdio or HTTP as transport protocols&lt;/a&gt;. OAuth protection for MCP servers is only designed for HTTP-based MCP servers. See &lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/authorization#when-should-you-use-authorization" rel="noopener noreferrer"&gt;When Should You Use Authorization?&lt;/a&gt; for more information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Explore the project
&lt;/h3&gt;

&lt;p&gt;Let’s take a quick look at the project to understand its implementation.&lt;br&gt;&lt;br&gt;
Go to the project’s folder and open the &lt;code&gt;Program.cs&lt;/code&gt; file. You should see the the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add the MCP services: the transport to use (http) and the tools to register.&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTools&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RandomNumberTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an ordinary .NET application that uses the MCP server service (&lt;code&gt;AddMcpServer&lt;/code&gt;). This service uses the HTTP transport (&lt;code&gt;WithHttpTransport&lt;/code&gt;) and exposes the tools implemented by the class &lt;code&gt;RandomNumberTools&lt;/code&gt; (&lt;code&gt;WithTools&amp;lt;RandomNumberTools&amp;gt;&lt;/code&gt;). The invocation of the &lt;code&gt;MapMcp()&lt;/code&gt; method initializes the MCP server.&lt;/p&gt;

&lt;p&gt;The C# SDK for MCP library takes charge of all the complexity of implementing the MCP protocol. You can stay focused on the tool implementation, which is pretty straightforward as well.&lt;br&gt;&lt;br&gt;
Open the &lt;code&gt;RandomNumberTools.cs&lt;/code&gt; file in the &lt;code&gt;Tools&lt;/code&gt; folder and take a look at the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Tools/RandomNumberTools.cs&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RandomNumberTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Generates a random number between the specified minimum and maximum values."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetRandomNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Minimum value (inclusive)"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Maximum value (exclusive)"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You see a very simple class implementing the method &lt;code&gt;GetRandomNumber()&lt;/code&gt;, which returns a random number within a range. The attribute &lt;code&gt;McpServerTool&lt;/code&gt; marks the &lt;code&gt;GetRandomNumber()&lt;/code&gt; method as an MCP tool. The &lt;code&gt;Description&lt;/code&gt; attributes describe what the tool does and what its parameters mean, very important to let the LLM understand when and how to use it.  In fact, clear descriptions of the tool's functionality help the LLM select the appropriate tool for a given task.&lt;/p&gt;

&lt;p&gt;That’s all! Your MCP server is ready to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test your MCP server
&lt;/h3&gt;

&lt;p&gt;Since the MCP server we implemented is nothing more than an ASP.NET Core application, we can test it in several ways: you can use curl or the &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/test/http-files" rel="noopener noreferrer"&gt;.http file&lt;/a&gt; automatically generated with the project. Or you can use the &lt;a href="https://modelcontextprotocol.io/docs/tools/inspector" rel="noopener noreferrer"&gt;MCP Inspector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll integrate our MCP server directly into an MCP client like VSCode. This will come in handy later when we implement some advanced features.&lt;/p&gt;

&lt;p&gt;You can add your MCP server to VSCode by creating a &lt;code&gt;.vscode&lt;/code&gt; folder in your project’s root folder, and adding an &lt;code&gt;mcp.json&lt;/code&gt; file with the following JSON content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//.vscode/mcp.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AspNetCoreMcpServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:PORT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;inputs&lt;/span&gt;&lt;span class="s2"&gt;": []
}

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to replace the PORT placeholder with the port your MCP server listens to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Look at &lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_add-an-mcp-server" rel="noopener noreferrer"&gt;alternative ways to add your MCP server to VSCode&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have added your MCP server to VSCode, set its chat window to agent mode and ask to get a random number. You should be prompted to authorize the use of the tool as shown in the following screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvyop35awnfo6ejzu73c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvyop35awnfo6ejzu73c.png" alt="Authorization request for the random numbers tool in Copilot chat in VSCode" width="283" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you authorize it, you will get a random number:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssslankyoogecbmzz5gu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssslankyoogecbmzz5gu.png" alt="Response of the random numbers tool in Copilot chat" width="283" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool! You have a new working MCP server in your VSCode instance!&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect Your MCP Server
&lt;/h2&gt;

&lt;p&gt;Now let's move on to the real focus of this article: protecting your MCP server from unauthorized access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the tools to protect
&lt;/h3&gt;

&lt;p&gt;Currently, anyone installing your MCP server can use its only tool, &lt;code&gt;get_random_number&lt;/code&gt;. This tool is publicly accessible because there is no restriction on it. You will leave it so, but you will also add two new tools that will require some form of authorization.&lt;/p&gt;

&lt;p&gt;Specifically, you will add a tool that gets the current state of a hypothetical system and another one that sets the state of this system. The system is fictitious, of course, but you can think of it as a kind of processor that can be found in different states.&lt;/p&gt;

&lt;p&gt;To implement these tools, add a new file named &lt;code&gt;SystemTools.cs&lt;/code&gt; to the &lt;code&gt;Tools&lt;/code&gt; folder with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Tools/SystemTools.cs&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Waiting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Stopped&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;AllStates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValues&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SystemState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_currentState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Returns the current state of the fictitious system. Possible states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetSystemState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_currentState&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="n"&gt;AllStates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AllStates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()!;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sets the current state of the fictitious system. Valid states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;SetSystemState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The new state to set. Must be one of: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_currentState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"System state set to '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_currentState&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You see that the tool &lt;code&gt;GetSystemState()&lt;/code&gt; picks a random state the first time it is called and stores it into a private static variable, &lt;code&gt;_currentState&lt;/code&gt;. Any subsequent call to the tool will return the value stored in that variable.&lt;/p&gt;

&lt;p&gt;The tool &lt;code&gt;SetSystemState()&lt;/code&gt; changes the value of the state stored in the &lt;code&gt;_currentState&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;You will protect these two new tools by requiring that the user has specific permissions to use each of them.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#roles" rel="noopener noreferrer"&gt;MCP specification&lt;/a&gt;, your MCP server is nothing but a resource server in the context of OAuth, and VSCode is just an OAuth client. So actually all you are going to implement is a well-known scenario: you will use Auth0 as the authorization server, which will authenticate the user and provide the access token to the MCP client (VSCode) in order to access the tools exposed by your MCP server. The access token will include the required permissions to access the protected tools.&lt;/p&gt;

&lt;p&gt;However, you may have a little concern now. You want to share your MCP server as a package that anyone can download and use in their VSCode instance. How can they register their VSCode instance with Auth0 as required for any OAuth client? Well, the MCP specification supports &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-registration-approaches" rel="noopener noreferrer"&gt;a few ways to do this&lt;/a&gt;. You will use &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;Dynamic Client Registration (DCR)&lt;/a&gt;, which allows an MCP client (VSCode) to self-register with the authorization server (Auth0). Be aware that DCR raises security concerns, as we will mention later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure your Auth0 tenant
&lt;/h3&gt;

&lt;p&gt;First of all, you need to configure Auth0 at the tenant level. In fact, you need to enable &lt;a href="https://auth0.com/docs/get-started/applications/dynamic-client-registration" rel="noopener noreferrer"&gt;Dynamic Client Registration&lt;/a&gt; to allow MCP clients to register. You also need to enable &lt;a href="https://auth0.com/ai/docs/mcp/guides/resource-param-compatibility-profile" rel="noopener noreferrer"&gt;Resource Parameter Compatibility Profile&lt;/a&gt; as required by the &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#resource-parameter-implementation" rel="noopener noreferrer"&gt;MCP specification&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind that these are tenant level settings, which means they affect all the applications registered in that tenant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To enable those settings, go to &lt;a href="https://manage.auth0.com/#/tenant" rel="noopener noreferrer"&gt;&lt;em&gt;Settings&lt;/em&gt;&lt;/a&gt; on the left menu and then select the &lt;em&gt;Advanced&lt;/em&gt; tab. Scroll down to the &lt;em&gt;Settings&lt;/em&gt; section and enable the &lt;em&gt;Dynamic Client Registration (DCR)&lt;/em&gt; and &lt;em&gt;Resource Parameter Compatibility Profile&lt;/em&gt; toggles, as shown in the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tlpn79sjl2k25wdkbvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tlpn79sjl2k25wdkbvs.png" alt="Dynamic Client Registration option on the Auth0 dashboard" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As mentioned before, you need to enable Dynamic Client Registration to allow VSCode to self-register with Auth0 the first time a user interacts with your MCP server. This allows any client to register with Auth0 with &lt;strong&gt;potential risks&lt;/strong&gt; such as resource exhaustion or unauthorized access attempts. Read &lt;a href="https://auth0.com/ai/docs/mcp/guides/registering-your-mcp-client-application#dynamic-client-registration-dcr" rel="noopener noreferrer"&gt;Register your MCP Client Application&lt;/a&gt; to learn more about the dangers of an open DCR endpoint and possible solutions.&lt;/p&gt;

&lt;p&gt;Beyond the static registration of an MCP client, an alternative approach is based on &lt;a href="https://auth0.com/blog/cimd-vs-dcr-mcp-registration/" rel="noopener noreferrer"&gt;Client ID Metadata Document (CIMD)&lt;/a&gt;, a recent standard whose implementation is in progress.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In addition to the previous tenant-level settings, you also need to &lt;a href="https://auth0.com/docs/authenticate/identity-providers/promote-connections-to-domain-level" rel="noopener noreferrer"&gt;promote the connections&lt;/a&gt; the MCP clients will use to domain level. To this purpose, go to the &lt;em&gt;Authentication&lt;/em&gt; menu item of your dashboard and select the connection to configure. For our current example, select the &lt;em&gt;Username-Password-Authentication&lt;/em&gt; connection under &lt;em&gt;Database&lt;/em&gt;, and scroll down until you see the setting &lt;em&gt;Promote Connection to Domain Level&lt;/em&gt;. Enable it as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsdbdaugjqa1utxwqihb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsdbdaugjqa1utxwqihb.png" alt="Promote Connection to Domain Level setting in the Auth0 dashboard" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! You have prepared your Auth0 tenant for self-registrations of MCP clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Register your MCP Server
&lt;/h3&gt;

&lt;p&gt;Now you need to register your MCP server.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, from the OAuth point of view, an MCP server is nothing but a resource server or API server. This means that you can register it with Auth0 &lt;a href="https://auth0.com/docs/get-started/auth0-overview/set-up-apis" rel="noopener noreferrer"&gt;as a standard API&lt;/a&gt;. The only requirement is to make sure to set your MCP server base URL as the audience. This is &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728#section-3.3" rel="noopener noreferrer"&gt;a requirement of the Protected Resource Metadata specification&lt;/a&gt;. So, in your case, if your MCP server listens to &lt;code&gt;http://localhost:5678&lt;/code&gt;, use this URL as the audience.&lt;/p&gt;

&lt;p&gt;Once you have created your API on the Auth0 dashboard, scroll down in the &lt;em&gt;Settings&lt;/em&gt; tab to reach the &lt;em&gt;Access Token Settings&lt;/em&gt; section. Here, select the &lt;em&gt;RFC 9068&lt;/em&gt; format as shown in the following picture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzv41xbfc20z2lqn0lrb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzv41xbfc20z2lqn0lrb.png" alt="Enabling the RFC 9068 access token profile in the Auth0 dashboard" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read the &lt;a href="https://auth0.com/docs/secure/tokens/access-tokens/access-token-profiles" rel="noopener noreferrer"&gt;documentation to learn more about access token profiles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then scroll down more to the &lt;em&gt;RBAC Settings&lt;/em&gt; section and enable both &lt;em&gt;RBAC&lt;/em&gt; and &lt;em&gt;Add Permissions in the Access Token&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20yx4s6dcwhxv1n60hzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20yx4s6dcwhxv1n60hzu.png" alt="Enabling RBAC and permissions in access tokens in the Auth0 dashboard" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These settings will include the user’s permissions in the access token so your MCP server will be able to make authorization decisions to let users access the tools.&lt;/p&gt;

&lt;p&gt;Save the settings and go to the &lt;em&gt;Permissions&lt;/em&gt; tab. Here you should add the &lt;code&gt;tool:getsystemstate&lt;/code&gt; and &lt;code&gt;tool:setsystemstate&lt;/code&gt; permissions as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqtssnxz7pcm7tn62mhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqtssnxz7pcm7tn62mhb.png" alt="List of permissions associated with the API" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the instructions in &lt;a href="https://auth0.com/docs/get-started/apis/add-api-permissions" rel="noopener noreferrer"&gt;this document to learn how to add permissions to an API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, go to the &lt;em&gt;Application Access&lt;/em&gt; tab and select the &lt;em&gt;Allow&lt;/em&gt; value for the application access policy for user access, as you can see in the picture below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vqw170c3ogrj9d5jjxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vqw170c3ogrj9d5jjxl.png" alt="Enabling application access policy on the Auth0 dashboard" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setting allows any self-registered MCP client to access your MCP server on behalf of the user. To learn more about this setting, read the &lt;a href="https://auth0.com/docs/get-started/apis/api-access-policies-for-applications" rel="noopener noreferrer"&gt;API Access Policy documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now your MCP server is configured on the Auth0 side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure your MCP Server
&lt;/h3&gt;

&lt;p&gt;Let's complete the MCP server protection by modifying the current code.&lt;/p&gt;

&lt;p&gt;First, add an &lt;code&gt;appsettings.json&lt;/code&gt; file with the current content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//appsettings.json&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Auth0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_DOMAIN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Audience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_MCP_SERVER_BASE_URL"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"McpServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"BaseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_MCP_SERVER_BASE_URL"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;YOUR_DOMAIN&lt;/code&gt; with your Auth0 tenant domain, and &lt;code&gt;YOUR_MCP_SERVER_BASE_URL&lt;/code&gt; with the actual URL of your MCP server. Remember that &lt;code&gt;YOUR_MCP_SERVER_BASE_URL&lt;/code&gt; &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728#section-3.3" rel="noopener noreferrer"&gt;must also correspond to the audience you registered in Auth0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you will add support for the access token validation and protection on the two MCP tools you added to the initial project.&lt;/p&gt;

&lt;p&gt;Install the middleware to manage JWT tokens with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet package add Microsoft.AspNetCore.Authentication.JwtBearer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the current content of the &lt;code&gt;Program.cs&lt;/code&gt; file with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.AspNetCore.Authentication&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"https://&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Auth0:Domain"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Auth0:Audience"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"McpServer:BaseUrl"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultChallengeScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;McpAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultAuthenticateScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JwtBearerEvents&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OnTokenValidated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token validated successfully."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;OnAuthenticationFailed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Authentication failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceMetadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AuthorizationServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ScopesSupported&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorizationFilters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTools&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RandomNumberTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break down this code to understand what it does.&lt;/p&gt;

&lt;p&gt;Basically, it adds support for authenticating client requests as in any ordinary ASP.NET Core Web API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultChallengeScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;McpAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultAuthenticateScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JwtBearerEvents&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OnTokenValidated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token validated successfully."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;OnAuthenticationFailed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Authentication failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code sets up the access token validation logic through &lt;code&gt;AddJwtBearer()&lt;/code&gt;. The &lt;code&gt;OnTokenValidated&lt;/code&gt; and &lt;code&gt;OnAuthenticationFailed&lt;/code&gt; event managers do nothing special apart from showing their message to the console, but you can use them for any customization you may need.&lt;/p&gt;

&lt;p&gt;Just after &lt;code&gt;AddJwtBearer()&lt;/code&gt; invocation, you find &lt;code&gt;AddMcp()&lt;/code&gt;, as highlighted below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceMetadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AuthorizationServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ScopesSupported&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AddMcp()&lt;/code&gt; method defines the &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata" rel="noopener noreferrer"&gt;Protected Resource Metadata (PRM)&lt;/a&gt; document for this MCP server. This document provides useful information for the client about the capabilities of the MCP server and which authorization server to contact to access protected tools.&lt;/p&gt;

&lt;p&gt;This document will be dynamically exposed at the URL &lt;code&gt;YOUR_MCP_SERVER_BASE_URL/.well-known/oauth-protected-resource&lt;/code&gt;, where &lt;code&gt;YOUR_MCP_SERVER_BASE_URL&lt;/code&gt; is the base URL of your MCP server. The document will contain the following JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_MCP_SERVER_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://YOUR_DOMAIN"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bearer_methods_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scopes_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, enforce authorization with the code highlighted below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorizationFilters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTools&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RandomNumberTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//👈 changed code&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;//👈 changed code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You added the authorization middleware service (&lt;code&gt;AddAuthorization()&lt;/code&gt;) and support for authorization filters (&lt;code&gt;AddAuthorizationFilters()&lt;/code&gt;). Also, you applied authorization to the MCP endpoints (&lt;code&gt;RequireAuthorization()&lt;/code&gt;) and forced the server to use the base URL defined in the &lt;code&gt;appsettings.json&lt;/code&gt; file. The last change is not strictly required, but it ensures that you have one source of truth for the MCP base URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add authorization policy
&lt;/h3&gt;

&lt;p&gt;You added a generic support for authorization with &lt;code&gt;AddAuthorization()&lt;/code&gt;. This makes sure that only authenticated users access the protected tools. But our initial intent is to allow users to access each specific tool based on their permissions.&lt;/p&gt;

&lt;p&gt;You can do this by defining a couple of authorization policies based on the existence of specific permissions in the access token. To this purpose, replace the &lt;code&gt;AddAuthorization()&lt;/code&gt; invocation with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code defines two authorization policies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GetSystemStatePolicy&lt;/code&gt; based on the &lt;code&gt;tool:getsystemstate&lt;/code&gt; value for the claim &lt;code&gt;permissions&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SetSystemStatePolicy&lt;/code&gt; based on the &lt;code&gt;tool:setsystemstate&lt;/code&gt; value for the claim &lt;code&gt;permissions&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s complete the protection of the tools by applying the authorization filter with the specific policy, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Tools/SystemTools.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Returns the current state of the fictitious system. Possible states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"GetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetSystemState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sets the current state of the fictitious system. Valid states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;SetSystemState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The new state to set. Must be one of: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your MCP server is secure now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Your Secure MCP Server
&lt;/h2&gt;

&lt;p&gt;Now it’s time to test your brand new secure MCP server!&lt;/p&gt;

&lt;p&gt;Run your server with the &lt;code&gt;dotnet run&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Then, in your VSCode instance, &lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_manage-mcp-servers" rel="noopener noreferrer"&gt;restart your MCP server&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszwrz0m7xcozmjor0qzl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszwrz0m7xcozmjor0qzl.png" alt="Start an MCP server in VSCode" width="605" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few seconds, you will see a popup message like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6j9hsx8l701i3fmz18mz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6j9hsx8l701i3fmz18mz.png" alt="MCP server requests authentication in VSCode" width="316" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This message is the effect of the &lt;code&gt;RequireAuthorization()&lt;/code&gt; execution you added to the server. It sent a 401 HTTP status message to VSCode with a reference to the Protected Resource Metadata document, where VSCode found all the needed info to start the user authentication and authorization process.&lt;/p&gt;

&lt;p&gt;Once you have allowed the authentication flow, you will get the &lt;a href="https://auth0.com/blog/anatomy-of-an-oauth2-authorization-request/" rel="noopener noreferrer"&gt;authorization request&lt;/a&gt; that VSCode is about to send to Auth0, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnewm5m4s1vx0os0p97o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnewm5m4s1vx0os0p97o.png" alt="Authorization request warning for MCP server in VSCode" width="567" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After user authentication, you will be requested to authorize VSCode to access your profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mfoyx0nlgcz9fmoy85i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mfoyx0nlgcz9fmoy85i.png" alt="Authorization consent prompt for an MCP server on the Auth0 side" width="800" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you accept the request, you will be redirected back to your VSCode instance.&lt;/p&gt;

&lt;p&gt;You didn’t notice it, but under the hood, your VSCode instance automatically registered with Auth0 using DCR. You can verify this by going to your Auth0 dashboard and look in the list of the &lt;a href="https://manage.auth0.com/#/applications" rel="noopener noreferrer"&gt;registered applications&lt;/a&gt;. You will see VSCode among them, and it is marked as a third-party application, as you can see below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdapytz8fqtpnypekl1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdapytz8fqtpnypekl1i.png" alt="VSCode is automatically registered as a client application on the Auth0 dashboard" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now your VSCode instance has an access token to access the tools implemented by your MCP server.&lt;/p&gt;

&lt;p&gt;Make sure your chat window is in Agent mode and &lt;a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_enable-tools-for-chat" rel="noopener noreferrer"&gt;list the tools&lt;/a&gt; provided by your MCP server. You will see only the &lt;code&gt;get_random_number&lt;/code&gt; tool:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp04v1aj76vk6khf2z539.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp04v1aj76vk6khf2z539.png" alt="List only Random Numbers tool for the MCP server on VSCode" width="618" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why? Doesn’t VSCode have an access token?&lt;/p&gt;

&lt;p&gt;VSCode has an access token, but without the required permissions to access the protected tools. So you can only access the public tool as before the protection implementation.&lt;/p&gt;

&lt;p&gt;To test your MCP server properly, you should assign one or more of the required permissions to your user profile on Auth0. Follow the instructions in &lt;a href="https://auth0.com/docs/manage-users/access-control/configure-core-rbac/rbac-users/assign-permissions-to-users" rel="noopener noreferrer"&gt;Assign Permissions to Users&lt;/a&gt; to assign the &lt;code&gt;tool:getsystemstate&lt;/code&gt; permission to your user profile.&lt;/p&gt;

&lt;p&gt;Use the command palette to &lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_manage-mcp-servers" rel="noopener noreferrer"&gt;sign out and restart&lt;/a&gt; your MCP server in VSCode. Then authenticate and check again the list of tools available to you. This time you should also see the &lt;code&gt;get_system_state&lt;/code&gt; tool:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1ma49cpxyj0adzzesc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1ma49cpxyj0adzzesc.png" alt="List two tools for the MCP server on VSCode" width="618" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ask something involving the state of the fictitious system in the chat window, as shown in the following screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8b2jpmoo0xadvfcara4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8b2jpmoo0xadvfcara4l.png" alt="Running the Get System State tool of the MCP server in the Copilot chat of VSCode" width="269" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should be able to use the tool as expected.&lt;/p&gt;

&lt;p&gt;Now, feel free to play with the permissions to get access to the other tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s Recap
&lt;/h2&gt;

&lt;p&gt;After a long journey, this article provided a comprehensive guide on securing a C# MCP server using Auth0, transforming it from a local experiment into an enterprise-ready resource server.&lt;/p&gt;

&lt;p&gt;The journey covered the following main steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building the Base MCP Server:&lt;/strong&gt; You quickly set up a basic, unsecured MCP server using the C# SDK for MCP and the MCP server template, exposing a simple &lt;code&gt;GetRandomNumber()&lt;/code&gt; tool.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defining Protected Tools:&lt;/strong&gt; You introduced two new tools, &lt;code&gt;GetSystemState()&lt;/code&gt; and &lt;code&gt;SetSystemState()&lt;/code&gt;, which require specific authorization, setting the stage for security implementation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth0 Tenant Configuration:&lt;/strong&gt; You configured the Auth0 tenant by enabling &lt;strong&gt;Dynamic Client Registration (DCR)&lt;/strong&gt; to allow MCP clients (like VSCode) to self-register, and enabling the &lt;strong&gt;Resource Parameter Compatibility Profile&lt;/strong&gt; as required by the MCP specification. We also promoted necessary connections to the domain level.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registering the MCP Server as an API:&lt;/strong&gt; The MCP server was registered with Auth0 as a standard API (Resource Server), ensuring its base URL was set as the audience. You configured it to use &lt;strong&gt;RFC 9068 access tokens&lt;/strong&gt; and enabled &lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt; to include permissions in the access token.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Securing the Server Code:&lt;/strong&gt; You updated the C# application to:

&lt;ul&gt;
&lt;li&gt;Include necessary configuration via &lt;code&gt;appsettings.json&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Integrate the &lt;code&gt;JwtBearer&lt;/code&gt; authentication middleware for access token validation against Auth0.
&lt;/li&gt;
&lt;li&gt;Define the &lt;strong&gt;Protected Resource Metadata (PRM)&lt;/strong&gt; document using &lt;code&gt;AddMcp()&lt;/code&gt;, providing clients with authorization server and scope information.
&lt;/li&gt;
&lt;li&gt;Implement authorization policies based on specific permissions.
&lt;/li&gt;
&lt;li&gt;Apply these policies to the respective tools using the &lt;code&gt;[Authorize]&lt;/code&gt; attribute.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Testing the Secure Server:&lt;/strong&gt; Finally, you tested the secured server by integrating it with VSCode. The process demonstrated the DCR flow, user authentication, and the crucial step of assigning user permissions within Auth0 to grant access to the newly protected tools.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;By following these steps, you successfully implemented an OAuth 2.1-compliant security layer, ensuring that access to sensitive tools is gated by proper user permissions validated through Auth0.&lt;/p&gt;

&lt;p&gt;You can download the final version of this project from this &lt;a href="https://github.com/auth0-samples/auth0-ai-samples" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, looking into the &lt;a href="https://github.com/auth0-samples/auth0-ai-samples/tree/main/auth-for-mcp/aspnetcore-mcp-server" rel="noopener noreferrer"&gt;auth-for-mcp/aspnetcore-mcp-server&lt;/a&gt; folder.&lt;/p&gt;

&lt;p&gt;To learn more about using Auth0 to secure your AI agents check out &lt;a href="http://a0.to/ai-content" rel="noopener noreferrer"&gt;Auth0 for AI Agents&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
