<?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: Andrea Chiarelli</title>
    <description>The latest articles on DEV Community by Andrea Chiarelli (@andychiare).</description>
    <link>https://dev.to/andychiare</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%2Fuser%2Fprofile_image%2F2656%2Fme2.jpg</url>
      <title>DEV Community: Andrea Chiarelli</title>
      <link>https://dev.to/andychiare</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andychiare"/>
    <language>en</language>
    <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>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>Generative AI and Non-Determinism</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Tue, 31 Mar 2026 07:12:49 +0000</pubDate>
      <link>https://dev.to/andychiare/generative-ai-and-non-determinism-526l</link>
      <guid>https://dev.to/andychiare/generative-ai-and-non-determinism-526l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“&lt;em&gt;The limits of my language are the limits of my world.&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;– Ludwig Wittgenstein&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We often hear that generative &lt;strong&gt;AI is non-deterministic&lt;/strong&gt; because it responds differently each time to the same prompt. This quality is both fascinating, because it is, in a sense, creative, and problematic, because it eludes our control in contexts where precision and predictability are essential.&lt;/p&gt;

&lt;p&gt;So, &lt;strong&gt;is non-determinism really what lies behind the processing of an LLM?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Non-Determinism?
&lt;/h2&gt;

&lt;p&gt;To understand what non-determinism is, we need to turn to the &lt;a href="https://en.wikipedia.org/wiki/Theory_of_computation" rel="noopener noreferrer"&gt;theory of computation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you’re in a maze. In a &lt;strong&gt;deterministic system&lt;/strong&gt;, you are a lone explorer. You come to a fork in the road and choose the right path. If the path is blocked, you must go back (good old &lt;a href="https://en.wikipedia.org/wiki/Backtracking" rel="noopener noreferrer"&gt;backtracking&lt;/a&gt;) and try the left path.&lt;/p&gt;

&lt;p&gt;In a &lt;strong&gt;non-deterministic system&lt;/strong&gt;, however, you possess the gift of ubiquity. When you reach the fork, you split into two people. One goes right and the other goes left.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://en.wikipedia.org/wiki/Michael_Sipser" rel="noopener noreferrer"&gt;Michael Sipser&lt;/a&gt;, the author of the book &lt;a href="https://en.wikipedia.org/wiki/Introduction_to_the_Theory_of_Computation" rel="noopener noreferrer"&gt;&lt;em&gt;Introduction to the Theory of Computation&lt;/em&gt;&lt;/a&gt;, this process is perfect parallelism: just one clone needs to find the exit for the entire machine to succeed. The computation of a non-deterministic machine is not expressed as a transition from one state to another, but rather as a transition from one state to a set of states. This theoretical &lt;em&gt;super-capacity&lt;/em&gt; is used to solve complex problems, such as those in the famous &lt;a href="https://en.wikipedia.org/wiki/NP_(complexity)" rel="noopener noreferrer"&gt;NP class&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In computer science, non-determinism is a model of logical perfection. &lt;strong&gt;If a solution exists, a non-deterministic machine will find it&lt;/strong&gt; because it explores every possible path without ever making a mistake. However, be careful: &lt;strong&gt;there is no such thing as a purely non-deterministic physical computer&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Many Faces of Non-Determinism
&lt;/h2&gt;

&lt;p&gt;Before returning to generative AI, we need to clarify the various concepts of non-determinism that are often conflated in everyday language. In fact, the term’s meaning changes significantly depending on the context.&lt;/p&gt;

&lt;p&gt;As we have seen, in computer science, non-determinism is a logical abstraction: a virtual perfect parallelism that allows us to solve a problem within a reasonable amount of time.&lt;/p&gt;

&lt;p&gt;In physics, non-determinism, or more accurately, &lt;a href="https://en.wikipedia.org/wiki/Quantum_indeterminacy" rel="noopener noreferrer"&gt;indeterminism&lt;/a&gt;, is the principle that it is impossible to simultaneously know two conjugate properties of a particle, such as position and momentum.&lt;/p&gt;

&lt;p&gt;Biology also has the &lt;a href="https://en.wikipedia.org/wiki/Indeterminism#Evolution_and_biology" rel="noopener noreferrer"&gt;concept of indeterminism&lt;/a&gt;, though it is linked to the role of chance in the evolution of living beings. This chance seems to be present in other aspects of nature, such as the weather, which are so difficult to predict that they appear non-deterministic. In reality, these are not non-deterministic systems, but rather highly complex deterministic systems, as &lt;a href="https://en.wikipedia.org/wiki/Chaos_theory" rel="noopener noreferrer"&gt;chaos theory&lt;/a&gt; explains.&lt;/p&gt;

&lt;p&gt;Generative AI is often defined as non-deterministic because of the &lt;em&gt;chaos effect&lt;/em&gt;: small variations in the input produce different outputs. However, as we will see, its nature is quite different.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI and Probability
&lt;/h2&gt;

&lt;p&gt;When you ask an LLM to answer a question or write code, the system doesn't use a non-deterministic approach to find the solution. The LLM simply… guesses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLMs are probabilistic machines&lt;/strong&gt;, not non-deterministic machines. Their entire universe boils down to assigning a probability percentage to the next token. For example, if you write, “The cat is on the...”, the model isn’t &lt;em&gt;thinking&lt;/em&gt; about physical space. It calculates that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;carpet&lt;/em&gt; has an 80% probability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;lawn&lt;/em&gt; has a 15% probability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike Sipser’s non deterministic machine, an LLM doesn’t traverse all logical branches. It chooses one based on a statistical distribution. This is a substantial difference!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A non-deterministic machine would &lt;em&gt;virtually&lt;/em&gt; always give the correct solution&lt;/strong&gt;. It would explore all possible branches of the computation to identify the correct one. If a solution is incorrect, that branch of the computation simply ends. There cannot be an output that is not a valid solution to the problem.&lt;/p&gt;

&lt;p&gt;The LLM follows &lt;strong&gt;likelihood&lt;/strong&gt;, not &lt;strong&gt;truth&lt;/strong&gt;. It can generate a grammatically perfect sentence that is factually false because those words &lt;em&gt;sound&lt;/em&gt; good together statistically. For instance, the LLM might tell you that the capital of Brazil is Rio de Janeiro because Rio is associated with Brazil much more often than Brasília in its training data. Statistically, Rio is the &lt;em&gt;more likely&lt;/em&gt; answer, even if it is logically incorrect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Is Imprecise AI Useful?
&lt;/h2&gt;

&lt;p&gt;If AI is fallible and imprecise, why is it revolutionizing the world? Why is it permeating our daily lives?&lt;/p&gt;

&lt;p&gt;Traditional, deterministic software is like a train traveling on tracks. It only does what it was programmed to do. It doesn’t make decisions beyond what the programmer intended, and it doesn’t evolve on its own. Traditional software expects input in a specific format. It cannot handle ambiguity or imprecision. For example, if you want to reschedule an appointment from today to tomorrow, you can do so by following one of a few predefined methods.&lt;/p&gt;

&lt;p&gt;In contrast, if you ask a chatbot “Move today’s appointment to tomorrow,” or “Move today’s meeting to tomorrow,” or even “&lt;em&gt;Tooday’s apointment goes to tomorow&lt;/em&gt;,” it uses its probabilistic nature to correctly interpret your request.&lt;/p&gt;

&lt;p&gt;This ability to handle unstructured data, such as analyzing the sentiment of an email, summarizing a document, or translating a text, cannot be reduced to simple, rigid rules. In these cases, probabilistic flexibility is the only way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigating Uncertainty
&lt;/h2&gt;

&lt;p&gt;For now, non-determinism remains a logical ideal of supreme efficiency. However, what we actually have at our disposal is an extraordinary probabilistic tool that &lt;strong&gt;is not intended to replace traditional deterministic software, but rather to enhance it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As developers and engineers, our task is not to eliminate AI's unpredictability, but rather to learn how to channel it. We must design deterministic systems around probabilistic engines that know when to be &lt;em&gt;intuitive&lt;/em&gt; and &lt;em&gt;imaginative&lt;/em&gt; and when to be precise.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</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>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>
    <item>
      <title>Strengthening OAuth 2.0 with FAPI 2.0</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Tue, 24 Feb 2026 08:27:26 +0000</pubDate>
      <link>https://dev.to/auth0/strengthening-oauth-20-with-fapi-20-oea</link>
      <guid>https://dev.to/auth0/strengthening-oauth-20-with-fapi-20-oea</guid>
      <description>&lt;p&gt;OAuth 2.0 has long been the cornerstone of modern authorization. It is a consolidated technology that powers everything from social logins to complex enterprise integrations. Developers appreciate its flexibility, which allows them to adapt the framework to various scenarios like traditional web applications, single page applications, desktop and mobile environments.&lt;/p&gt;

&lt;p&gt;However, this flexibility is a double-edged sword. Because OAuth 2.0 is a framework rather than a rigid protocol, it provides a collection of specifications that developers can combine in different ways. In high-risk and regulated sectors like banking, healthcare, and insurance, this freedom creates a significant surface area for configuration errors and interoperability issues. This is where the FAPI profile steps in, providing a strict set of rules to ensure the highest levels of security and trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flexibility Paradox of OAuth 2.0
&lt;/h2&gt;

&lt;p&gt;The fundamental challenge with "vanilla" OAuth 2.0 is the vast number of optional features. While the &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;core specification&lt;/a&gt; defines the basic grant types, it leaves many security decisions to the implementer. For a developer building a low-risk internal tool, these choices are manageable. But for an architect designing a system for &lt;a href="https://en.wikipedia.org/wiki/Open_banking" rel="noopener noreferrer"&gt;Open Banking&lt;/a&gt; or sensitive patient records, the stakes are much higher.&lt;/p&gt;

&lt;p&gt;In high-assurance environments, the phrase "&lt;em&gt;with great power comes great responsibility&lt;/em&gt;" applies literally. Security vulnerabilities often arise not from flaws in the individual specifications, but from how they are combined. For example, a system might use the &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; but fail to implement &lt;a href="https://datatracker.ietf.org/doc/html/rfc7636" rel="noopener noreferrer"&gt;Proof Key for Code Exchange (PKCE)&lt;/a&gt; or use insecure redirect URIs. FAPI 2.0 solves this by removing the "optional" from security best practices and mandating a "secure by default" posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is FAPI 2.0?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openid.net/specs/fapi-2_0-security-02.html" rel="noopener noreferrer"&gt;FAPI 2.0&lt;/a&gt; is a security profile developed by the OpenID Foundation. Unlike its predecessor (FAPI 1.0), which was often criticized for its complexity, FAPI 2.0 focuses on simplicity and robust interoperability. It is not a new protocol. Instead, it is &lt;strong&gt;a prescriptive profile&lt;/strong&gt; that defines exactly which OAuth 2.0 and OpenID Connect extensions must be used and how they must be configured.&lt;/p&gt;

&lt;p&gt;The profile is built upon a formal attacker model that assumes a high-threat environment where attackers might control the network or have the ability to intercept front-channel messages. By adhering to FAPI 2.0, organizations ensure their authorization servers and clients meet a standardized level of resistance against modern attack vectors like session injection, token theft, and authorization request tampering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardening OAuth 2.0
&lt;/h2&gt;

&lt;p&gt;The key security enhancements introduced by FAPI 2.0 to strengthen the OAuth 2.0 framework focus on three main areas: hardening the authorization request, eliminating bearer token risks, and improving client authentication. Here are the most relevant protection mechanisms FAPI 2.0 enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pushed Authorization Requests (PAR):&lt;/strong&gt; FAPI 2.0 mandates &lt;a href="https://a0.to/pushed-authorization-6da4c7" rel="noopener noreferrer"&gt;PAR&lt;/a&gt; to mitigate the risks of sending sensitive authorization parameters (like scopes and PKCE challenges) over the insecure front-channel (browser). With PAR, the client sends these parameters via a secure back-channel POST request before redirection. The server then issues a short-lived &lt;code&gt;request_uri&lt;/code&gt; used in the subsequent browser redirect, ensuring sensitive data never appears in browser history or logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sender-Constrained Tokens:&lt;/strong&gt; FAPI 2.0 moves away from vulnerable bearer tokens by enforcing "sender-constrained" tokens, which are cryptographically bound to the client that requested them. This prevents token misuse even if stolen. The primary mechanisms for this are Mutual TLS (mTLS), which binds the token to the client's X.509 certificate, and &lt;a href="https://a0.to/dpop-d188dc" rel="noopener noreferrer"&gt;Demonstration of Proof-of-Possession (DPoP)&lt;/a&gt;, a more flexible, application-level solution where the client proves possession of a private key using a signed JWT with every request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asymmetric Client Authentication:&lt;/strong&gt; FAPI 2.0 replaces shared client secrets with asymmetric authentication, typically using Private Key JWT. The client signs a JWT with its private key, which the authorization server verifies with the client's registered public key. This method removes the risk of client secrets being leaked during transmission.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Role of FAPI Certification
&lt;/h2&gt;

&lt;p&gt;A critical component of the FAPI ecosystem is the &lt;a href="https://openid.net/certification/" rel="noopener noreferrer"&gt;OpenID Foundation's certification program&lt;/a&gt;. Because security depends on correct implementation, the foundation provides a suite of automated tests that vendors and organizations can use to verify their compliance. This certification provides a common ground for interoperability, ensuring that a FAPI-compliant client from one vendor can work seamlessly and securely with a FAPI-compliant authorization server from another.&lt;/p&gt;

&lt;p&gt;Choosing certified components reduces the burden of security auditing and provides assurance that the system adheres to the industry's highest standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and Further Reading
&lt;/h2&gt;

&lt;p&gt;For more technical details on the specifications mentioned, consult the following official resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://openid.net/specs/fapi-security-profile-2_0-final.html" rel="noopener noreferrer"&gt;OpenID FAPI 2.0 Security Profile - Final Specification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9126" rel="noopener noreferrer"&gt;OAuth 2.0 Pushed Authorization Requests (RFC 9126)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9449" rel="noopener noreferrer"&gt;OAuth 2.0 Demonstration of Proof-of-Possession (RFC 9449)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openid.net/specs/fapi-attacker-model-2_0-final.html" rel="noopener noreferrer"&gt;FAPI 2.0 Attacker Model&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more about FAPI 2.0 from a developer’s perspective, download the free ebook &lt;a href="https://a0.to/fapi-book-26d859" rel="noopener noreferrer"&gt;A Developer’s Guide to FAPI&lt;/a&gt;, which provides a detailed overview of the various security measures to make your application FAPI-ready.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>identity</category>
      <category>fapi</category>
    </item>
    <item>
      <title>What a Developer Advocate Is Not</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 03 Oct 2025 16:20:32 +0000</pubDate>
      <link>https://dev.to/andychiare/what-a-developer-advocate-is-not-1fd4</link>
      <guid>https://dev.to/andychiare/what-a-developer-advocate-is-not-1fd4</guid>
      <description>&lt;p&gt;Someone: "What’s your job?"&lt;/p&gt;

&lt;p&gt;Me: "I'm a developer advocate."&lt;/p&gt;

&lt;p&gt;Someone: "Oh, cool! Like... what do you do exactly?"&lt;/p&gt;

&lt;p&gt;Me: "Well, my job is to make it easier for developers to adopt and use a product."&lt;/p&gt;

&lt;p&gt;Someone: "Oh, so you work in sales?"&lt;/p&gt;

&lt;p&gt;Me: "No, no. I don't sell the product. I help developers use it effectively, for example, by providing code samples, tutorials, articles, and so on."&lt;/p&gt;

&lt;p&gt;Someone: "So are you a blogger? Or a technical writer?"&lt;/p&gt;

&lt;p&gt;Me: "Well… maybe partly, but that's not all. I support the community and try to understand the pain points developers experience with the product so I can provide that feedback internally to improve it."&lt;/p&gt;

&lt;p&gt;Someone: "Oh, right. Uhm… well, that's interesting..."&lt;/p&gt;

&lt;p&gt;I’ve had many conversations like this. And often, the other person walks away with a very confused idea about the kind of work I do. I know, it's not easy to explain what I do for a living in just a few words. That's why I wrote &lt;a href="https://leanpub.com/i-am-a-developer-advocate" rel="noopener noreferrer"&gt;a little book&lt;/a&gt; that tries to explain it. In this article, however, I want to focus on what a developer advocate is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Developer Advocate Is Not…
&lt;/h2&gt;

&lt;p&gt;It's natural to try to relate a new concept to something you already know. We always do it whenever we have to learn something new. Keeping that familiar concept in mind helps us understand the similarities and differences.&lt;/p&gt;

&lt;p&gt;While understanding a new concept by looking at &lt;strong&gt;what it's not&lt;/strong&gt; is not the most efficient process, it's the most natural way to frame it, at least initially. So, let's define what a developer advocate is not by comparing this role to a few common assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  …a Salesperson
&lt;/h2&gt;

&lt;p&gt;The goal of a developer advocate is not to sell a product. Although they aim to &lt;em&gt;influence&lt;/em&gt;, their purpose is not to sell. Rather, they seek to improve the product so that it meets the real needs of developers. This influence moves in two directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outward&lt;/strong&gt; (from the company to the developer community), to teach developers how to best use the product.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inward&lt;/strong&gt; (from the developer community to the company), to convey their feedback to the company and encourage improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers don't trust marketing promises; they trust other developers who have experience with a product and understand its technical details. A developer advocate builds this trust through technical competence, not sales techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  …a Copywriter
&lt;/h2&gt;

&lt;p&gt;Although writing is one of their core skills, the role of a developer advocate differs from that of a copywriter. The fundamental difference lies in the purpose of the communication. Content created by a developer advocate is meant to stimulate interaction and feedback. Their work is not about promoting a brand; rather, it is about starting a technical conversation with the developer community. Their work is part of an ongoing dialogue aimed at improving the developer's experience with the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  …a Blogger
&lt;/h2&gt;

&lt;p&gt;Another common oversimplification is confusing a developer advocate with a blogger. While they may manage a blog and write articles, this is just one activity among many and one communication channel among many. Unlike bloggers, whose primary focus is content creation, developer advocates have the crucial responsibility of inbound advocacy. Their job doesn't end when a post is published. Rather, it continues by actively gathering comments, criticism, and suggestions from the community and bringing them back internally to influence product decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  …a Technical Writer
&lt;/h2&gt;

&lt;p&gt;There is a clear distinction between a developer advocate and a technical writer. Technical writers create a product's documentation, which, by nature, has an impersonal style and follows specific rules to guide users. Technical documentation implies one-way communication from the company to the user. In contrast, a developer advocate's content is personal and carries the author's imprint. Its goal is to establish a human connection. Their goal is not only to explain "how to do something," but also to stimulate dialogue, answer questions, and build trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  …a (Mere) Presenter
&lt;/h2&gt;

&lt;p&gt;Although presentation skills are important, a developer advocate is more than just a presenter of technical content. They are, first and foremost, experienced developers. Their credibility comes from their ability to write code, understand development complexities, and “feel the pain” of other developers, not just from their public speaking abilities. Presentations at conferences or in videos are merely a means of communication and are not the essence of the role. Without direct, in-depth development experience, they would be unable to earn the community's trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Now that we've ruled out some of the professions it can be confused with, you probably want to know what a developer advocate is. What do they do? What value do they bring to a company that builds products for developers?&lt;/p&gt;

&lt;p&gt;It would take too long to explain here. Find out by downloading my booklet “&lt;a href="https://leanpub.com/i-am-a-developer-advocate" rel="noopener noreferrer"&gt;I Am a Developer Advocate&lt;/a&gt;”. &lt;strong&gt;It's free!&lt;/strong&gt; And let me know what you think!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://leanpub.com/i-am-a-developer-advocate" rel="noopener noreferrer"&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%2F7c4jynkwemi09dbjf9uw.jpg" alt="I Am a Developer Advocate" width="640" height="801"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>career</category>
      <category>role</category>
      <category>developers</category>
    </item>
    <item>
      <title>Being a Developer in the Vibe Coding Era</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Tue, 26 Aug 2025 15:07:44 +0000</pubDate>
      <link>https://dev.to/andychiare/being-a-developer-in-the-vibe-coding-era-3me8</link>
      <guid>https://dev.to/andychiare/being-a-developer-in-the-vibe-coding-era-3me8</guid>
      <description>&lt;p&gt;I was looking at results of the &lt;a href="https://survey.stackoverflow.co/2025" rel="noopener noreferrer"&gt;Stackoverflow Developer Survey 2025&lt;/a&gt;, and one aspect caught my attention: the percentages relating to developers' confidence in &lt;a href="https://survey.stackoverflow.co/2025/ai#2-accuracy-of-ai-tools" rel="noopener noreferrer"&gt;the accuracy of AI tools&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%2Fqt5ussgo1vqos8lvb6y5.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%2Fqt5ussgo1vqos8lvb6y5.png" alt="Stats about the trust developers have in the accuracy of AI tools according to Stackoverflow Developer Survey 2025" width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned in the comment on the results related to this question, &lt;strong&gt;more developers do not trust AI tools than trust them&lt;/strong&gt;. In my opinion, this is good news, although I am concerned about that &lt;strong&gt;3% who have a high level of trust in AI tools&lt;/strong&gt;. This is not to demonize AI tools, but rather to address the evolving role of developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developers and Libraries
&lt;/h2&gt;

&lt;p&gt;Some time ago, I wrote an article about whether &lt;a href="https://auth0.com/blog/zen-and-the-art-of-identity-management/" rel="noopener noreferrer"&gt;using a library or framework exempts developers from understanding the specifics of the underlying tools and technologies&lt;/a&gt;. I addressed this issue by referring to the cult book &lt;a href="https://dev.toZen_and_the_Art_of_Motorcycle_Maintenance"&gt;Zen and the Art of Motorcycle Maintenance&lt;/a&gt; by Robert M. Pirsig. Paraphrasing the book, I identified three types of developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;classic developer&lt;/strong&gt;, who cannot resist the curiosity to see how a tool works. If possible, they will even venture into modifying it. This is what we commonly call a &lt;strong&gt;hacker&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;romantic developer&lt;/strong&gt;, who just uses the library or framework as it is. He is more interested in achieving his desired result and appreciates the library more for how easily it allows him to do so than for how it works internally. The details of the tool are none of his business.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;pragmatic developer&lt;/strong&gt; uses the library to solve a problem but does not overlook the advantage of understanding how it works internally. They may not know all the internal details of the library, but they have a high-level understanding of its architecture and can delve deeper if necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking at the data on developers' trust in AI tools, I was reminded of that article. I thought that, nowadays, &lt;strong&gt;that classification and the need for pragmatic rather than romantic developers is even more relevant&lt;/strong&gt;. If we want to translate this classification in terms of trust, the classic developer does not trust the tool; the pragmatic developer trusts it to a certain extent but tries to keep everything under control; and the romantic developer has blind faith in the tool.&lt;/p&gt;

&lt;p&gt;If blindly trusting a traditional library has its drawbacks, then &lt;strong&gt;how can we trust code generated by an AI tool?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Towards a New Way of Being Developers?
&lt;/h2&gt;

&lt;p&gt;With the rise of &lt;a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener noreferrer"&gt;vibe coding&lt;/a&gt;, a new &lt;strong&gt;Romanticism of software development&lt;/strong&gt; seems to have emerged, generating widespread enthusiasm. However, if code written by a developer is often unreliable, how can we trust code generated by an AI tool that has no real understanding of what it is doing? I am referring, of course, to tools based on LLMs, the current mainstream of AI.&lt;/p&gt;

&lt;p&gt;The answer is that &lt;strong&gt;we should not trust it. Ever!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Should we give up on AI code generation tools? Absolutely not! We should use them because they can improve the quality of our code more than increase the production speed.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;we should avoid becoming romantic developers&lt;/strong&gt; who get excited because we can accomplish in a couple of hours what would have taken days or even weeks a few months ago. We must be pragmatic and &lt;strong&gt;verify that the generated code does what we expected&lt;/strong&gt;. If we want to take it a step further and become classic developers who understand how the tool generated the code, even better. At the very least, it will help us formulate better questions.&lt;/p&gt;

&lt;p&gt;The developer's job is not—and never has been—to produce code. Their job is to provide a solution that works as expected. As a &lt;em&gt;vibe coder&lt;/em&gt;, &lt;strong&gt;you are responsible for verifying the code generated by the AI tool&lt;/strong&gt;. You must understand the code and modify it if necessary. Some of the time saved by using the AI tool will need to be spent reviewing the results.&lt;/p&gt;

&lt;p&gt;In short, you should never trust what an AI tool proposes. You must always analyze and validate the results. This means that you cannot stop learning programming languages, their best practices, design patterns, and the technologies on which your application is based. You will also need to learn the new things that the generated code will probably include.&lt;/p&gt;

&lt;p&gt;Keep in mind that if something goes wrong—rarely does everything go smoothly—you are responsible for the code generated by the AI tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer Is Dead. Long Live the Developer.
&lt;/h2&gt;

&lt;p&gt;To those who think that AI will replace developers, I would say that they are right. In a few years—or even months—there won't be any more developers. &lt;strong&gt;There will be no more developers as we understood them until a few years ago&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once the &lt;em&gt;Romanticism of software development&lt;/em&gt; has passed, we will find that the role of developers has been redefined and probably expanded. We will likely have a new title, such as "code manager" or "coded solution provider."&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;there will still be a need for technical skills in code review and validation&lt;/strong&gt;: high-level skills that minimize bugs and security issues in code generated by AI tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We cannot afford to be romantic developers who blindly trust the code generated by AI tools&lt;/strong&gt;. Professional developers should never accept code produced by an AI tool without question. They must question it, employing the &lt;strong&gt;critical thinking&lt;/strong&gt; that many believe is threatened by AI. After all, the saying "&lt;strong&gt;Trust, but verify&lt;/strong&gt;" still holds true.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>developer</category>
      <category>programming</category>
    </item>
    <item>
      <title>.NET MAUI Authentication on Windows: The OpenID Connect Challenge</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Tue, 06 May 2025 10:36:39 +0000</pubDate>
      <link>https://dev.to/auth0/net-maui-authentication-on-windows-the-openid-connect-challenge-40h2</link>
      <guid>https://dev.to/auth0/net-maui-authentication-on-windows-the-openid-connect-challenge-40h2</guid>
      <description>&lt;p&gt;I'm not an expert desktop and mobile developer. My experience in this field is from a while ago, particularly in mobile development, where I wouldn't claim professional competence. Most of the time, I've created desktop console applications supporting backend processing. Still, I know how hard it is to build native applications with a GUI, especially when cross-platform support is required.&lt;/p&gt;

&lt;p&gt;When I heard about &lt;a href="https://dotnet.microsoft.com/it-it/apps/maui" rel="noopener noreferrer"&gt;.NET MAUI&lt;/a&gt;, I immediately thought it would be an ambitious and very interesting project. Several development teams dream of having a single codebase (or so) for applications that can run on different platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET MAUI and OpenID Connect
&lt;/h2&gt;

&lt;p&gt;I had the chance to play with MAUI to explore the integration with &lt;a href="https://auth0.com/" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; authentication, which is based on &lt;a href="https://openid.net/developers/how-connect-works/" rel="noopener noreferrer"&gt;OpenID Connect&lt;/a&gt;, a standard authentication protocol. However, since my first attempt around mid-2022, I immediately ran into issues on the Windows platform.&lt;/p&gt;

&lt;p&gt;As you may know, OpenID Connect is a browser-based authentication protocol. This means that authentication is performed by opening the Identity Provider's login page in a system browser window. Auth0's experience is similar to what happens when you access a website using your Google or Facebook account.&lt;br&gt;
While you might consider using an embedded WebView in your native application, &lt;a href="https://datatracker.ietf.org/doc/html/rfc8252#section-8.12" rel="noopener noreferrer"&gt;best practices&lt;/a&gt; recommend launching authentication in a separate system browser window. Among other things, this allows you to benefit from &lt;a href="https://auth0.com/blog/what-is-and-how-does-single-sign-on-work/" rel="noopener noreferrer"&gt;Single Sign-On&lt;/a&gt;. But let's not digress.&lt;/p&gt;

&lt;p&gt;.NET MAUI leverages the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.authentication.webauthenticator" rel="noopener noreferrer"&gt;WebAuthenticator&lt;/a&gt; class to initiate the browser-based OpenID Connect authentication flow. While this approach provides a seamless experience for user login on platforms like Android, iOS, and macOS, developers encounter significant challenges with Windows when implementing secure authentication in their .NET MAUI applications. This was true three years ago, and &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/communication/authentication?view=net-maui-9.0&amp;amp;tabs=windows" rel="noopener noreferrer"&gt;it is still true&lt;/a&gt; at the time of writing:&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%2Fm1uv83wefk7qybvv6xl2.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%2Fm1uv83wefk7qybvv6xl2.png" alt="WebAuthenticator does not work on Windows" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Frustrating Experience
&lt;/h2&gt;

&lt;p&gt;You know, I'm not the only one surprised by this issue. If you take a look at the &lt;a href="https://github.com/microsoft/WindowsAppSDK/issues/441" rel="noopener noreferrer"&gt;long threads&lt;/a&gt; around &lt;a href="https://github.com/dotnet/maui/issues/2702" rel="noopener noreferrer"&gt;this issue&lt;/a&gt;, you can see many developers are complaining about the missing support for a very common feature such as authentication in Microsoft's own operating system!&lt;/p&gt;

&lt;p&gt;But developers are not sitting idly by. Some have rolled up their sleeves and found workarounds. My colleagues on the SDK team have also &lt;a href="https://github.com/auth0/auth0-oidc-client-net/pull/287" rel="noopener noreferrer"&gt;implemented a solution inspired by the one adopted by WinUIEx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to this workaround implemented by the &lt;a href="https://github.com/auth0/auth0-oidc-client-net/tree/master/src/Auth0.OidcClient.MAUI" rel="noopener noreferrer"&gt;Auth0 MAUI SDK&lt;/a&gt;, I was able to create some sample apps, write &lt;a href="https://auth0.com/blog/add-authentication-to-dotnet-maui-apps-with-auth0" rel="noopener noreferrer"&gt;some tutorials&lt;/a&gt;, and add a MAUI template to the &lt;a href="https://github.com/auth0/auth0-dotnet-templates" rel="noopener noreferrer"&gt;Auth0 Templates for .NET&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;Everything worked fine until .NET 9. With the latest release, once again, OpenID Connect integration stopped working in applications migrated to .NET 9, and &lt;a href="https://github.com/microsoft/WindowsAppSDK/issues/441#issuecomment-2585843963" rel="noopener noreferrer"&gt;the threads restarted&lt;/a&gt; (if ever they have actually stopped).&lt;/p&gt;

&lt;p&gt;We also had &lt;a href="https://community.auth0.com/t/add-authentication-to-net-maui-apps-with-auth0/86929/137" rel="noopener noreferrer"&gt;some&lt;/a&gt; &lt;a href="https://community.auth0.com/t/add-auth0-authentication-to-blazor-hybrid-apps-in-net-maui/101159/28" rel="noopener noreferrer"&gt;requests&lt;/a&gt; for &lt;a href="https://community.auth0.com/t/add-authentication-to-net-maui-apps-with-auth0/86929/130" rel="noopener noreferrer"&gt;help&lt;/a&gt; in the Auth0 Community forum.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ongoing Journey of .NET MAUI Authentication on Windows
&lt;/h2&gt;

&lt;p&gt;Bottom line, MAUI developers had to revise their workarounds to fix the OIDC integration once again. My colleagues on the SDK team were also great in finding a way to fix the issue once again and &lt;a href="https://auth0.com/blog/announcing-a-new-release-of-the-auth0-oidc-client-for-net/" rel="noopener noreferrer"&gt;update the MAUI SDK to support OIDC in .NET 9&lt;/a&gt;. On my side, I took the opportunity to update the &lt;a href="https://github.com/auth0/auth0-dotnet-templates" rel="noopener noreferrer"&gt;Auth0 Templates for .NET&lt;/a&gt; package just in these days and provide you with a working &lt;a href="https://github.com/auth0/auth0-dotnet-templates/blob/main/docs/auth0maui.md" rel="noopener noreferrer"&gt;.NET MAUI template for Windows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From some ongoing &lt;a href="https://github.com/dotnet/maui/discussions/27981" rel="noopener noreferrer"&gt;discussions&lt;/a&gt;, it seems like there is a glimmer of light for the future. It goes without saying that every MAUI developer is looking forward to the resolution of this complicated relationship between MAUI, OpenID Connect, and Windows.&lt;/p&gt;

&lt;p&gt;And you? What has been your experience integrating OpenID Connect authentication in MAUI applications on Windows? Share your story below.👇&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>openid</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Letter to Blazor</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Mon, 10 Mar 2025 17:34:33 +0000</pubDate>
      <link>https://dev.to/andychiare/letter-to-blazor-9h1</link>
      <guid>https://dev.to/andychiare/letter-to-blazor-9h1</guid>
      <description>&lt;p&gt;Dear Blazor, I met you when you were still in your infancy. I started playing with you when you could barely stand up...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Promise
&lt;/h2&gt;

&lt;p&gt;Even though you were still frail, your ambition was exciting: &lt;strong&gt;to allow developers to create web, desktop, and mobile applications using only C# and Razor&lt;/strong&gt;. Not bad at all. It was a bit like dreaming of becoming an astronaut when you grow up. 🧑‍🚀&lt;/p&gt;

&lt;p&gt;You started with the web, promising to dethrone JavaScript and do much better than that old-timer. You promised that &lt;strong&gt;developers only need to know C# and Razor&lt;/strong&gt; to create efficient web applications, whether they run on the server or the browser. No more complications to make a SPA communicate with the server. You, developer, create your components in Razor and all the logic in C#. The framework will take care of transforming everything into an application that runs on a web server or in a browser. Don't worry. &lt;strong&gt;Everything will happen behind the scenes in a linear and transparent manner&lt;/strong&gt;. 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Old Memories
&lt;/h2&gt;

&lt;p&gt;Who knows why, but those promises brought to my mind something already seen: memories that I thought I had completely erased resurfaced. &lt;strong&gt;The promise of &lt;a href="https://en.wikipedia.org/wiki/ASP.NET_Web_Forms" rel="noopener noreferrer"&gt;Web Forms&lt;/a&gt; resurfaced in my memory&lt;/strong&gt;: a unified paradigm for web development, whether the application logic runs on the server or the client. The framework would have taken care of doing the right thing. This promise created quite a few disappointments at that time. Many developers who were approaching Web programming for the first time got confused. But perhaps now things have changed, times are different, we have different technologies. &lt;em&gt;Blazor is not Web Forms&lt;/em&gt;. 🤔&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Wonderful World
&lt;/h2&gt;

&lt;p&gt;You, Blazor, have made it possible to create fantastic applications that run on the server and on the browser. You enabled &lt;strong&gt;components that can be shared between the server and the client&lt;/strong&gt;. And your followers are not few. You have enchanted many. Not just those who hated JavaScript - that was easy - you also attracted those who were familiar with JavaScript. In short, your dream was within reach.&lt;/p&gt;

&lt;p&gt;You enabled the reuse of the same Razor components in MAUI to create desktop and mobile applications, as you promised. Many .NET developers have started to idolize you. 😍&lt;/p&gt;

&lt;p&gt;With .NET 8, you've gone further. Not only can developers use the same programming model to build server and client applications, but now &lt;strong&gt;they can decide at the level of individual components whether it should run on the server or the client&lt;/strong&gt;. They can even decide that a component starts as a server component and becomes a client component. Crazy stuff! 🤯&lt;/p&gt;

&lt;h2&gt;
  
  
  A Crack in the Wall
&lt;/h2&gt;

&lt;p&gt;Even though I have followed your growth, I can't say I know you well. I only played with you in a limited scope. I have a narrow view of you, I admit it. But the little I got to know about you left me a bit puzzled.&lt;/p&gt;

&lt;p&gt;Ok. Many of the promises you made, you kept. But from what I could see, there are aspects that resemble me - no offense - more like a big deception. 😒&lt;/p&gt;

&lt;p&gt;You promised that &lt;strong&gt;we wouldn't have to worry about JavaScript anymore&lt;/strong&gt;, but every now and then there are things that you don't allow us to do. And so we inevitably have to communicate with the old man through &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/" rel="noopener noreferrer"&gt;JSInterop&lt;/a&gt;, and it's not a walk in the park.&lt;/p&gt;

&lt;p&gt;You promised a unified programming model, where we didn't have to come up with tricks to circumvent programming problems that the not-so-simple coexistence of JavaScript and HTML has accustomed us to. I would have been &lt;strong&gt;a unified and consistent model where we create a component and don't need to worry about how and where it will be rendered and executed&lt;/strong&gt;. The framework will handle everything.&lt;/p&gt;

&lt;p&gt;So, we write an interactive auto component that needs to make an HTTP call to the server. Follow all the simple rules, but &lt;a href="https://stackoverflow.com/questions/77503849/in-the-new-blazor-web-app-template-using-net-8-how-do-i-make-an-http-request-f" rel="noopener noreferrer"&gt;it doesn't work&lt;/a&gt;. Why? You also have to register &lt;code&gt;HttpClient&lt;/code&gt; on the server. But why? Due to &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/call-web-api?view=aspnetcore-8.0#service-abstractions-for-web-api-calls" rel="noopener noreferrer"&gt;how the framework works under the hood&lt;/a&gt;. 😨&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;what about the combination of render mode and interactivity management?&lt;/strong&gt; How should I go about making my application today? Interactive WebAssembly or Interactive Server? But maybe it's better Global Interactive Auto, so it takes care of everything. The best way to get a headache is the day something doesn't work and you can't go back. As someone said, &lt;a href="https://www.reddit.com/r/dotnet/comments/1folj0j/comment/lorluuv/" rel="noopener noreferrer"&gt;Blazor is absolutely amazing, until it’s not&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, you want to add support for authentication. A fairly common operation. Which application is without authentication nowadays? You enter a nightmare. &lt;strong&gt;How do I transfer the authentication state between the server and the client parts of a Blazor application?&lt;/strong&gt; Well, just &lt;a href="https://auth0.com/blog/auth0-authentication-blazor-web-apps/#Syncing-the-Authentication-State" rel="noopener noreferrer"&gt;implement a few classes here and there&lt;/a&gt;. Why? &lt;strong&gt;Didn't you promise us that there wouldn't be any more need for tricks and gimmicks to do simple things?&lt;/strong&gt; Yes, but this depends on how the framework works. 😞&lt;/p&gt;

&lt;p&gt;Ok, now &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-9.0&amp;amp;tabs=visual-studio#manage-authentication-state-in-blazor-web-apps" rel="noopener noreferrer"&gt;with .NET 9 you've simplified it&lt;/a&gt;, but only after your supporters went crazy trying to get the server and client to communicate about the authentication state. And in any case, it’s still a developer’s burden to tell you what to do to make them transfer the serialized authentication state. But &lt;strong&gt;wasn't the framework supposed to take care of these details?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Big Deception?
&lt;/h2&gt;

&lt;p&gt;What about the programming model you promised us? You had promised us that it was no longer necessary to know how JavaScript works, its interaction with HTML, etc. You had promised us a simple and unified model — &lt;strong&gt;a model that abstracts the underlying complications&lt;/strong&gt;: HTTP, HTML, JavaScript, AJAX, etc.&lt;/p&gt;

&lt;p&gt;I feel like &lt;strong&gt;you replaced a complex model based on standard stuff with YOUR model&lt;/strong&gt;. Now, instead of knowing how HTTP, HTML, and JavaScript work, I have to understand how YOU work internally to create my applications. And then I wonder: &lt;strong&gt;where is the gain?&lt;/strong&gt; 🤔&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; &lt;strong&gt;This letter is not meant to be a complaint against the work done by the Blazor team&lt;/strong&gt;. It is merely an invitation to not trust abstractions when they are pushed too far. Not all complexity can be hidden behind an abstraction. Even if the model proposed by an abstraction seems to work, you always need to have an idea of how things work under the hood when they don't behave as expected. &lt;strong&gt;Be enthusiastic, yet pragmatic&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>Securing Blazor in All Its Flavors</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Thu, 27 Feb 2025 09:36:02 +0000</pubDate>
      <link>https://dev.to/auth0/securing-blazor-in-all-its-flavors-2819</link>
      <guid>https://dev.to/auth0/securing-blazor-in-all-its-flavors-2819</guid>
      <description>&lt;p&gt;Since its first official release in .NET 3.0, &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt; has made a solemn promise to .NET developers: allow them to build web applications using just C# and the .NET platform, with no JavaScript and very little HTML. But Blazor’s goal was even more ambitious: &lt;a href="https://devblogs.microsoft.com/dotnet/blazor-server-in-net-core-3-0-scenarios-and-performance/#what%E2%80%99s-next-for-blazor" rel="noopener noreferrer"&gt;becoming the reference framework for all types of applications, not just web&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since then, the Blazor framework has gained considerable popularity among .NET developers, but not without some problems, inevitably due to its evolution. The framework's changes since its first release have significantly impacted developers' work, who have had to adapt their applications to accommodate its evolution.&lt;/p&gt;

&lt;p&gt;Furthermore, while the idea of ​​having a single framework to develop any type of application may seem great, this can hide pitfalls and complications when implementing standards-based authentication and authorization, such as those based on &lt;a href="https://openid.net/developers/how-connect-works/" rel="noopener noreferrer"&gt;OpenID Connect&lt;/a&gt; (OIDC) and &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt;, for example. As you may know, you should use &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/which-oauth-2-0-flow-should-i-use" rel="noopener noreferrer"&gt;different flows to authenticate users&lt;/a&gt; based on the application type: server-rendered web application, single-page application, or native application.&lt;/p&gt;

&lt;p&gt;In this article, we will focus on implementing authentication based on the different types of applications you can build with Blazor. We will provide guidance on best practices and direct you to practical implementations using Auth0.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blazor “Flavors”
&lt;/h2&gt;

&lt;p&gt;We mentioned Blazor’s evolution earlier. Let’s briefly recap what it is about.&lt;/p&gt;

&lt;p&gt;In the beginning, Blazor was released with two &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/hosting-models" rel="noopener noreferrer"&gt;hosting models&lt;/a&gt;: Blazor Server and Blazor WebAssembly, also known as Blazor WASM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blazor Server&lt;/strong&gt; applications are intended to run on the server, and the UI part sent to the browser communicates with the server part over a &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction" rel="noopener noreferrer"&gt;SignalR&lt;/a&gt; connection using &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/websockets" rel="noopener noreferrer"&gt;WebSocket&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blazor WASM&lt;/strong&gt; applications are compiled into &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;WebAssembly&lt;/a&gt; and run entirely on the browser. While you can host a Blazor WASM application on any web server (&lt;em&gt;standalone Blazor WASM&lt;/em&gt;), a specific hosting model called &lt;em&gt;ASP.NET Core hosted model&lt;/em&gt; allows you to simply host your application &lt;em&gt;within&lt;/em&gt; an ASP.NET Core application, giving you more control over some server-side functionality.&lt;/p&gt;

&lt;p&gt;With .NET 6.0, a new hosting model was introduced: &lt;strong&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/hybrid" rel="noopener noreferrer"&gt;Blazor Hybrid&lt;/a&gt;&lt;/strong&gt;. This hosting model allows you to use the Blazor framework to build desktop and mobile applications using a hybrid approach, i.e., Razor components run directly in the native application.&lt;/p&gt;

&lt;p&gt;With .NET 8.0, a significant change was introduced: &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes" rel="noopener noreferrer"&gt;render modes&lt;/a&gt;. This revolutionized the way the hosting model was understood until now. With the Server and WASM hosting models, either the entire application ran on the server, or it all ran on the client. With render modes, the components run on the server or client, not the entire application. Or rather, the developer can decide the level of granularity with which to run the various parts of their Blazor application on the server or client. This is a great opportunity to build high-performance web applications, but it also complicates things quite a bit in different contexts, such as authentication and authorization.&lt;/p&gt;

&lt;p&gt;At the same time, the introduction of render modes practically deprecates the Blazor Server hosting model. From now on, Blazor applications that support render modes are called &lt;strong&gt;Blazor Web Apps&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With the proliferation of all these &lt;em&gt;flavors&lt;/em&gt; of Blazor, what is the best approach to implementing OIDC and OAuth 2.0 for authentication and authorization?&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor Server
&lt;/h2&gt;

&lt;p&gt;While Blazor Server may be considered deprecated, legacy applications based on this hosting model exist. From the OAuth 2.0 and OpenID Connect perspective, a Blazor Server application is a server-rendered application and should therefore be considered a &lt;a href="https://auth0.com/docs/get-started/applications/confidential-and-public-applications#confidential-applications" rel="noopener noreferrer"&gt;confidential client&lt;/a&gt;. It will use the &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; to obtain ID and access tokens.&lt;/p&gt;

&lt;p&gt;In code terms, this means that your Blazor application will use the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnecthandler" rel="noopener noreferrer"&gt;OpenID Connect middleware&lt;/a&gt;, as in the following example:&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="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;DefaultScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CookieAuthenticationDefaults&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;DefaultChallengeScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenIdConnectDefaults&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;AddCookie&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenIdConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"myOidc"&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="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;"Oidc: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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientId&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;"Oidc:ClientId"&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;ClientSecret&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;"Oidc:ClientSecret"&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;ResponseType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"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;If you use Auth0 for authentication, you can leverage the &lt;a href="https://github.com/auth0/auth0-aspnetcore-authentication" rel="noopener noreferrer"&gt;Auth0 ASP.NET Core Authentication SDK&lt;/a&gt; to simplify things.&lt;/p&gt;

&lt;p&gt;Read &lt;a href="https://a0.to/blazor-webapp-with-authentication" rel="noopener noreferrer"&gt;this article to learn how to add Auth0 authentication to your Blazor Server application&lt;/a&gt;. As an additional resource, &lt;a href="https://a0.to/blazor-logout-problem" rel="noopener noreferrer"&gt;this article explains how to overcome an issue with logout that affects Blazor Server&lt;/a&gt; due to SignalR's behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor WebAssembly
&lt;/h2&gt;

&lt;p&gt;Since Blazor WASM applications run entirely in the browser, they can be considered single-page applications, i.e., &lt;a href="https://auth0.com/docs/get-started/applications/confidential-and-public-applications#public-applications" rel="noopener noreferrer"&gt;public clients&lt;/a&gt;. In this case, you must use the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce" rel="noopener noreferrer"&gt;Authorization Code flow with PKCE&lt;/a&gt; to get ID and access tokens.&lt;/p&gt;

&lt;p&gt;In terms of implementation, you need a specific WebAssembly package for your application, the &lt;a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Components.WebAssembly.Authentication" rel="noopener noreferrer"&gt;Microsoft.AspNetCore.Components.WebAssembly.Authentication&lt;/a&gt; package. Your code for configuring OIDC will be as follows:&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="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;AddOidcAuthentication&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;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="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Oidc"&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;ProviderOptions&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;ProviderOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"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;You also need to add a reference to the &lt;code&gt;AuthenticationService.js&lt;/code&gt; script in the &lt;code&gt;index.html&lt;/code&gt; file of your application, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.webassembly.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;a href="https://a0.to/blazor-webassembly-apps" rel="noopener noreferrer"&gt;this article for more details on how to add Auth0 authentication to your Blazor WASM application&lt;/a&gt;. The article uses the ASP.NET Core hosted model, but the authentication configuration is the same even for standalone Blazor WebAssembly applications. To learn about &lt;a href="https://a0.to/web-apps-for-blazor-wasm" rel="noopener noreferrer"&gt;building Blazor WASM applications to host in Azure Static Web Apps, read this article&lt;/a&gt;. Finally, take a look at &lt;a href="https://a0.to/rbac-in-blazor-apps" rel="noopener noreferrer"&gt;this article to learn how to use Auth0’s Role-Based Access Control (RBAC)&lt;/a&gt; in both Server and WASM hosting models.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Chaos of Blazor Render Modes
&lt;/h2&gt;

&lt;p&gt;As you can see, identifying the correct authentication approach with the Server and WASM hosting models is quite straightforward: the first follows the confidential client approach, and the second follows the public client approach.&lt;/p&gt;

&lt;p&gt;The introduction of render modes complicates things a bit. Indeed, in this case, part of an application runs on the server, and part runs on the client. To complicate the situation even more, with the &lt;em&gt;interactive auto render mode&lt;/em&gt;, a component is initially rendered on the server and then on the client. What category does a Blazor application fall into in this case? Is it a confidential client or a public client?&lt;/p&gt;

&lt;p&gt;Actually, there is no definitive general answer to this question. Much depends on how the components and their related render modes are combined in a specific application. A conservative approach to implementing OIDC authentication in a Blazor Web App is to consider it a confidential client. This entails that you can reuse the same code to configure a Blazor Server application.&lt;/p&gt;

&lt;p&gt;However, this opens a new problem: how do you determine whether or not a user can see a specific component based on its authentication state? If a user authenticates after the browser downloads a WASM component, how does the browser know the new user authentication state?&lt;/p&gt;

&lt;p&gt;To solve this problem, you need to keep the server-side and client-side &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/security/authentication-state" rel="noopener noreferrer"&gt;authentication states&lt;/a&gt; in sync. However, while you have to manually implement authentication state sync in .NET 8.0, .NET 9.0 simplifies this approach by providing native support for authentication state synchronization.&lt;/p&gt;

&lt;p&gt;Read &lt;a href="https://a0.to/auth0-authentication-blazor" rel="noopener noreferrer"&gt;this article to learn the details of how to add support to Blazor Web Apps authentication&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, problems are not just related to authentication. You should also take care of how to call remote APIs from your WASM components. Since you consider your Blazor Web App a confidential client, your WASM components do not receive any access token to call APIs. How can you deal with this scenario?&lt;/p&gt;

&lt;p&gt;Don't be tempted to make the access token available to WASM components. As &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps" rel="noopener noreferrer"&gt;security best practices&lt;/a&gt; suggest, the best approach is to implement the &lt;a href="https://a0.to/the-backend-for-frontend-pattern-bff" rel="noopener noreferrer"&gt;Backend for Frontend (BFF)&lt;/a&gt; pattern. This &lt;a href="https://auth0.com/blog/call-protected-api-from-blazor-web-app/" rel="noopener noreferrer"&gt;article shows how to call APIs by implementing BFF in Blazor&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor Hybrid Apps
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, Blazor Hybrid allows you to reuse Razor components and the skills learned with the Blazor framework to create UIs for desktop and mobile applications. In this context, your Blazor application is hosted by a native application, typically developed with &lt;a href="https://dotnet.microsoft.com/it-it/apps/maui" rel="noopener noreferrer"&gt;MAUI&lt;/a&gt;. In fact, the combination of Blazor and MAUI is also often called &lt;strong&gt;Blazor MAUI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this scenario, authentication is handled entirely by the native component of the Blazor MAUI application. That is, the OIDC configuration and the authentication process occur in the native code of MAUI. The Blazor part simply creates an authentication state based on the user authentication result. Here is an example of an authentication state definition for a Blazor MAUI application using the &lt;a href="https://github.com/auth0/auth0-oidc-client-net/tree/master/src/Auth0.OidcClient.MAUI" rel="noopener noreferrer"&gt;Auth0 MAUI SDK&lt;/a&gt;:&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Auth0AuthenticationStateProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AuthenticationStateProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ClaimsPrincipal&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsIdentity&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;readonly&lt;/span&gt; &lt;span class="n"&gt;Auth0Client&lt;/span&gt; &lt;span class="n"&gt;auth0Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Auth0AuthenticationStateProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Auth0Client&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;auth0Client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuthenticationState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAuthenticationStateAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthenticationState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;LogInAsync&lt;/span&gt;&lt;span class="p"&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;loginTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;LogInAsyncCore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;NotifyAuthenticationStateChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loginTask&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;loginTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuthenticationState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;LogInAsyncCore&lt;/span&gt;&lt;span class="p"&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;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;LoginWithAuth0Async&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthenticationState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ClaimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;LoginWithAuth0Async&lt;/span&gt;&lt;span class="p"&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;authenticatedUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsIdentity&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;loginResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;auth0Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoginAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;loginResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;authenticatedUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loginResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&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;authenticatedUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LogOut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;auth0Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogoutAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsIdentity&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nf"&gt;NotifyAuthenticationStateChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthenticationState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&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;Read &lt;a href="https://a0.to/authentication-to-blazor-hybrid-apps" rel="noopener noreferrer"&gt;this article for more details about adding Auth0 authentication to a Blazor MAUI application&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the Auth0 Templates
&lt;/h2&gt;

&lt;p&gt;As you may have understood by now, there are many moving parts in the process of integrating OIDC and OAuth 2.0 with the many flavors of Blazor. Understanding why and how each approach works is important. It allows you to identify the source of any problems and understand why something goes wrong.&lt;/p&gt;

&lt;p&gt;Once you understand which approach to take for each Blazor flavor, you just need to correctly implement the integration for each new application.&lt;/p&gt;

&lt;p&gt;Auth0 provides you with project templates to make this job easier. The &lt;a href="https://www.nuget.org/packages/Auth0.Templates" rel="noopener noreferrer"&gt;Auth0 Templates for .NET package&lt;/a&gt; includes a few project templates for Blazor that you can use with the .NET CLI, Visual Studio, or JetBrains Rider.&lt;/p&gt;

&lt;p&gt;For example, the &lt;a href="https://github.com/auth0/auth0-dotnet-templates/blob/main/docs/auth0blazor.md" rel="noopener noreferrer"&gt;Auth0 Blazor Web Application template&lt;/a&gt; allows you to scaffold a Blazor Web App application with Auth0 authentication embedded with just 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 auth0blazor &lt;span class="nt"&gt;-o&lt;/span&gt; MyBlazorWebApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have the &lt;a href="https://github.com/auth0/auth0-cli" rel="noopener noreferrer"&gt;Auth0 CLI&lt;/a&gt; installed on your machine and &lt;a href="https://github.com/auth0/auth0-cli?tab=readme-ov-file#authenticating-to-your-tenant" rel="noopener noreferrer"&gt;logged in to your Auth0 tenant&lt;/a&gt;, you can also get your application automatically registered with Auth0 and ready to run in less than one minute.&lt;/p&gt;

&lt;p&gt;Alternatively, you can use your preferred IDE, such as Visual Studio, to generate your application and register it manually:&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%2Fzszqf504ff2gq8klu8cl.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%2Fzszqf504ff2gq8klu8cl.png" alt="Auth0 Blazor Web App template" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The package also provides a legacy &lt;a href="https://github.com/auth0/auth0-dotnet-templates/blob/main/docs/auth0blazorserver.md" rel="noopener noreferrer"&gt;Blazor Server template&lt;/a&gt; and a &lt;a href="https://github.com/auth0/auth0-dotnet-templates/blob/main/docs/auth0blazorwasm.md" rel="noopener noreferrer"&gt;Blazor WebAssembly template&lt;/a&gt; in the ASP.NET Core hosted model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;At this point, you have a comprehensive overview of the different flavors of Blazor available so far and the issues you face when adding support for OIDC and OAuth 2.0-based authentication and authorization.&lt;/p&gt;

&lt;p&gt;If you use Auth0 as your identity provider, follow the Blazor resources mentioned here. They will allow you to understand how to integrate authentication and authorization correctly. Furthermore, the Auth0 Templates for .NET package will allow you to have applications ready to use in minutes.&lt;/p&gt;

&lt;p&gt;Leave your experience using authentication and authorization with Blazor in the comments.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>authentication</category>
      <category>oauth</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
