<?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>Forem: Auth0</title>
    <description>The latest articles on Forem by Auth0 (@auth0).</description>
    <link>https://forem.com/auth0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F634%2Fc6bfc78f-136d-456b-96dc-bcc4be1c88f9.jpg</url>
      <title>Forem: Auth0</title>
      <link>https://forem.com/auth0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/auth0"/>
    <language>en</language>
    <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://forem.com/auth0/things-developers-get-wrong-about-the-backend-for-frontend-pattern-10n1</link>
      <guid>https://forem.com/auth0/things-developers-get-wrong-about-the-backend-for-frontend-pattern-10n1</guid>
      <description>&lt;p&gt;Since I published my &lt;a href="https://auth0.com/blog/the-backend-for-frontend-pattern-bff" rel="noopener noreferrer"&gt;overview of the Backend for Frontend (BFF) pattern&lt;/a&gt;, the questions I've received fall into surprisingly consistent patterns. The same misunderstandings come up again and again, from developers who genuinely want to build secure apps.&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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




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

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

</description>
      <category>oauth</category>
      <category>security</category>
      <category>software</category>
    </item>
    <item>
      <title>Secure a C# MCP Server with Auth0</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 13 Mar 2026 09:59:59 +0000</pubDate>
      <link>https://forem.com/auth0/secure-a-c-mcp-server-with-auth0-4p0n</link>
      <guid>https://forem.com/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>The four-year-old typo that nobody can fix 🧟 and more about building developer tools</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Wed, 25 Feb 2026 14:30:00 +0000</pubDate>
      <link>https://forem.com/auth0/building-tools-that-developers-actually-want-to-use-6j6</link>
      <guid>https://forem.com/auth0/building-tools-that-developers-actually-want-to-use-6j6</guid>
      <description>&lt;p&gt;As the host of &lt;strong&gt;Making Software&lt;/strong&gt; I get to have some pretty real conversations about what it actually takes to build good software. And in my recent chat with &lt;a href="https://rhamzeh.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Raghd Hamzeh&lt;/strong&gt;&lt;/a&gt; (Senior Software Engineer at Okta and a core maintainer of &lt;a href="https://openfga.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;OpenFGA&lt;/strong&gt;&lt;/a&gt;) hit home for me.&lt;/p&gt;

&lt;p&gt;We talked about a side of engineering that doesn’t get enough airtime: what happens when your users are other engineers? If you’ve ever built an API, an SDK, or even just a shared library, you know that developers are the most rewarding, but also the most "vocal" audience you can have.&lt;/p&gt;

&lt;p&gt;Here are the three things from our conversation that have been living rent-free in my head:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The "Breaking Change" Itch is Real 😬
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Raghd&lt;/strong&gt; admitted something that I think a lot of us can relate to: when there’s a four-year-old typo in one of the products you're building, that drives you crazy. It could be just a small naming inconsistency, but when it’s &lt;em&gt;there&lt;/em&gt;, it's staring back at you every time.&lt;/p&gt;

&lt;p&gt;We got into that internal battle every maintainer knows: do you break everyone’s build just to satisfy the need for a clean API? Probably not. But it’s a powerful reminder that the decisions we make in Month 1, like how we pass a parameter, become the legacy we have to live with in Year 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Stop building "Alien" SDKs 👽
&lt;/h3&gt;

&lt;p&gt;This one got personal! While coding on the Ruby SDK for OpenFGA, I ran into this head-on. Rubyists are &lt;em&gt;opinionated&lt;/em&gt; (I should know, I’m one of them).&lt;/p&gt;

&lt;p&gt;When you’re building multi-language tools, you’re constantly balancing two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; Making sure the Go SDK and the Python SDK behave the same way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idiom:&lt;/strong&gt; Making sure a Ruby dev doesn't feel like they're writing Java code with extra steps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Raghd’s&lt;/strong&gt; take? You won't get it perfect 100% of the time. Sometimes, you have to prioritize the long-term health of the platform over a "perfectly idiomatic" implementation just so the project stays maintainable for the team behind it. That tension never fully goes away, you just get better at navigating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The "AI" file that’s actually for humans 🤖
&lt;/h3&gt;

&lt;p&gt;With the rise of AI, everyone is adding &lt;code&gt;agents.md&lt;/code&gt; files to their repos. &lt;strong&gt;Raghd&lt;/strong&gt; had a spicy take: &lt;strong&gt;Write that file for your human contributors first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want people to help you build your open-source dream, lay out your expectations clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How should commits be structured?&lt;/li&gt;
&lt;li&gt;What does the core architecture look like?&lt;/li&gt;
&lt;li&gt;Should contributors open an issue before submitting a PR?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Documenting these "non-code" decisions up front saves an enormous amount of friction. It turns "This PR isn't what we wanted" into "Here is the blueprint we’re all following."&lt;/p&gt;

&lt;h3&gt;
  
  
  🎙️ Want the full story?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Check out the full episode of Making Software here: &lt;a href="https://listen.casted.us/public/49/Making-Software-2b1cff7b/35fd6fc3" rel="noopener noreferrer"&gt;Link to episode&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’d love to hear from you in the comments, what’s the one tiny thing in your codebase that you’re dying to "breaking change" just to feel better? 😅&lt;/p&gt;

</description>
      <category>api</category>
      <category>discuss</category>
      <category>softwareengineering</category>
      <category>tooling</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://forem.com/auth0/strengthening-oauth-20-with-fapi-20-oea</link>
      <guid>https://forem.com/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>Why Your Demo Code Should Be Treated as Production Code (And Other DevRel Secrets) 🎸</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Thu, 29 Jan 2026 10:38:56 +0000</pubDate>
      <link>https://forem.com/auth0/why-your-demo-code-should-be-treated-as-production-code-and-other-devrel-secrets-3332</link>
      <guid>https://forem.com/auth0/why-your-demo-code-should-be-treated-as-production-code-and-other-devrel-secrets-3332</guid>
      <description>&lt;p&gt;Have you ever found a code snippet in a tutorial, spent an hour trying to make it work, only to realize it’s using a deprecated library from 2019?&lt;/p&gt;

&lt;p&gt;We’ve all been there.&lt;/p&gt;

&lt;p&gt;I recently sat down with &lt;a href="https://sambego.tech/" rel="noopener noreferrer"&gt;Sam Bellen&lt;/a&gt;, Principal Developer Advocate at Auth0, for the first episode of the Making Software podcast. We talked about the "diplomatic" nature of Developer Relations, the controversy of "selling" to developers, and why we need to stop treating demo code like a second-class citizen.&lt;/p&gt;

&lt;p&gt;Here are the key takeaways for every developer, whether you’re looking to break into DevRel or just want to build better software.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. DevRel: Part Engineer, Part Diplomat 🤝
&lt;/h2&gt;

&lt;p&gt;Sam and I agreed on a unique definition: DevRel is a mix of developer and diplomat.&lt;/p&gt;

&lt;p&gt;As a developer, you’re used to talking to Product Teams to understand requirements. In DevRel, that flow often reverses. You are the "voice of the developer" inside the company.&lt;/p&gt;

&lt;p&gt;"It’s building relationships with the product teams... at a certain point, they will start trusting that what you’re telling them is actually valuable feedback instead of just randomly pinging people on Slack." — Sam Bellen&lt;/p&gt;

&lt;p&gt;The Lesson: If you want to influence a product's roadmap, don't just throw bugs over the fence. Build a relationship with the people making the product. Trust is the currency of influence.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Authenticity &amp;gt; Sales 🚫💰
&lt;/h2&gt;

&lt;p&gt;Developers are notoriously skeptical of being "sold" to. We can smell a marketing pitch from a mile away. Sam’s approach? Lead with the problem, not the product.&lt;/p&gt;

&lt;p&gt;Instead of saying, "Use our auth tool because it's great," talk about how hard it is to implement DPoP (Demonstrating Proof-of-Possession) or Passkeys manually.&lt;/p&gt;

&lt;p&gt;When you illustrate the complexity of a problem, the solution (your product) becomes a natural part of the conversation rather than a forced advertisement.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Treat Demo Code as Production Code 🛠️
&lt;/h2&gt;

&lt;p&gt;This was Sam’s biggest take: Any code you write (even a tiny sample for a blog post) should be treated as production code.&lt;/p&gt;

&lt;p&gt;If a vulnerability is found in a framework, DevRel teams should be the first to update their samples. Why? Because that sample is often a developer's first "touchpoint" with your tech.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security: Developers often copy-paste samples directly into their projects.&lt;/li&gt;
&lt;li&gt;Trust: If your "Quick Start" doesn't work out of the box, you've lost the user.&lt;/li&gt;
&lt;li&gt;Maintenance: Technical debt exists in docs, too. Set aside time (Sam recommends monthly) to audit your repos for deprecated APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Get Your Hands Dirty with the Spec 📖
&lt;/h2&gt;

&lt;p&gt;Sam has a "superpower": he actually likes reading technical specifications. While most of us fall asleep reading an RFC, Sam uses them to build visual tools that explain what’s happening "under the hood."&lt;/p&gt;

&lt;p&gt;"In order for me to explain something... I build it myself. Not a clone of the product, but a demo that implements the topic. I make loads of mistakes, get lots of errors, and eventually, I figure out what's happening."&lt;/p&gt;

&lt;p&gt;The "Aha!" Moment: You don't truly understand a tool until you've tried to build the logic it simplifies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary: Two Steps to Better Developer Experience
&lt;/h2&gt;

&lt;p&gt;If you’re an engineer looking to improve the DX (Developer Experience) of your project today, start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listen to your DevRel team: They aren't just "social developers." They have the technical baggage and the community feedback to tell you what’s actually broken.&lt;/li&gt;
&lt;li&gt;Stop "dropping" code on GitHub: Don't just create a repo and ignore it. If it’s public, it’s "production" for someone else.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎧 Listen to the Full Episode
&lt;/h2&gt;

&lt;p&gt;Want to hear the full conversation about guitars, Passkeys, and the early days of DevRel?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://listen.casted.us/public/49/Making-Software-2b1cff7b/75c539aa/share/7a4d465a" rel="noopener noreferrer"&gt;Listen to the full episode here&lt;/a&gt; or in your platform of choice.&lt;/p&gt;

&lt;p&gt;What’s your take? Should demo code be held to the same linting and security standards as the core product? Let’s talk in the comments! 👇&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>softwareengineering</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Auth0 for AI Agents is now generally available!</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Mon, 24 Nov 2025 16:32:18 +0000</pubDate>
      <link>https://forem.com/auth0/auth0-for-ai-agents-is-now-generally-available-29el</link>
      <guid>https://forem.com/auth0/auth0-for-ai-agents-is-now-generally-available-29el</guid>
      <description>&lt;p&gt;Hey DEV Community! 👋&lt;/p&gt;

&lt;p&gt;If you're building AI agents right now (and honestly, who isn't?), you've probably hit the auth problem. You know the one - where the quickest path to getting your agent working is to just hardcode some API keys and move on. It works great... until you need to actually ship to production.&lt;/p&gt;

&lt;p&gt;Today, we're excited to share that &lt;strong&gt;Auth0 for AI Agents is now generally available&lt;/strong&gt;, and it's designed to solve exactly this problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/signup?onboard_app=auth_for_aa&amp;amp;ocid=701KZ000000cXXxYAM_aPA4z0000008OZeGAM?utm_source=devto&amp;amp;utm_campaign=amer_namer_usa_all_ciam_dev_dg_plg_auth0_display_devto_display_aud_Q4_GAlaunch_nov2025_utm2&amp;amp;utm_medium=cpc&amp;amp;utm_id=aNKWR000002Z1ph4AC" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Auth0 for AI Agents&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/YypMEnCetqI"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Hardcoded Credentials
&lt;/h2&gt;

&lt;p&gt;Let's be real: when you're prototyping an AI agent with LangChain or LlamaIndex, hardcoded credentials are the path of least resistance. Your agent needs to access Slack, GitHub, Google Calendar, or your own APIs, and frameworks make it easy to just plug in those keys.&lt;/p&gt;

&lt;p&gt;But production is a different story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens when your agent needs to act on behalf of different users with different permissions?&lt;/li&gt;
&lt;li&gt;How do you handle token refreshing across 30+ different apps?&lt;/li&gt;
&lt;li&gt;How do you let users approve critical actions (like making purchases) without giving your agent carte blanche?&lt;/li&gt;
&lt;li&gt;How do you ensure your RAG-powered agent only accesses documents the user actually has permission to see?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't edge cases - they're fundamental requirements for any AI agent that's going to interact with real user data and take real actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built
&lt;/h2&gt;

&lt;p&gt;Auth0 for AI Agents gives you four key capabilities:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;User Authentication&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Secure and scalable &lt;a href="https://auth0.com/ai/docs/intro/user-authentication" rel="noopener noreferrer"&gt;User Authentication&lt;/a&gt; allows you to identify who's talking to your agent and give it secure access to your first-party APIs. Your agent can access user-specific data like order history, preferences, or chat logs - all scoped to the right permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Token Vault&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://auth0.com/features/token-vault" rel="noopener noreferrer"&gt;Token Vault&lt;/a&gt; handles OAuth flows with 30+ pre-integrated apps (GitHub, Slack, Google Workspace, and more) plus any custom OAuth provider you want to connect. It manages access tokens, refresh tokens, and the whole lifecycle automatically. Your agent requests a connection, the user authorizes once, and you never have to think about token management again.&lt;/p&gt;

&lt;p&gt;The SDK detects when a tool call needs authentication, pauses execution, prompts the user to authenticate, stores the token securely, and resumes automatically. On subsequent calls, it just works.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Asynchronous Authorization&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Your agent can work in the background and only interrupt the user when it needs approval for critical actions. Using &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-flow" rel="noopener noreferrer"&gt;Client-Initiated Backchannel Authentication (CIBA)&lt;/a&gt;, you can send approval requests via email or Auth0 Guardian (SMS coming soon). &lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;FGA for RAG&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When your agent uses Retrieval Augmented Generation to search through documents, it needs to respect access controls. &lt;a href="https://auth0.com/ai/docs/get-started/authorization-for-rag" rel="noopener noreferrer"&gt;Fine-Grained Authorization for RAG&lt;/a&gt; ensures that users only get answers from documents they actually have permission to access.&lt;/p&gt;

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

&lt;p&gt;AI agents are moving from demos to production. The difference between a hackathon project and a real product often comes down to handling auth correctly. We built Auth0 for AI Agents because we kept hearing from developers that this was the hard part - not the LLM integration, not the prompt engineering, but the secure, user-scoped access to real systems.&lt;/p&gt;

&lt;p&gt;This isn't about adding features. It's about removing blockers so you can ship production-ready AI agents without building your own auth infrastructure from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Support
&lt;/h2&gt;

&lt;p&gt;We've built SDKs for the frameworks you're already using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LangChain&lt;/strong&gt; (Python &amp;amp; JavaScript)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LlamaIndex&lt;/strong&gt; (Python &amp;amp; JavaScript)
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare AI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firebase Genkit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel AI SDK&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each SDK handles the OAuth dance automatically, so you can focus on building your agent's capabilities, not wrestling with authentication flows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Our free tier includes two connected apps in Token Vault, async authorization, and all the core features you need to start building. As you scale, we have self-service plans that grow with you.&lt;/p&gt;

&lt;p&gt;Early-stage startups can apply for one year of Auth0 free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/signup?onboard_app=auth_for_aa&amp;amp;ocid=701KZ000000cXXxYAM_aPA4z0000008OZeGAM?utm_source=devto&amp;amp;utm_campaign=amer_namer_usa_all_ciam_dev_dg_plg_auth0_display_devto_display_aud_Q4_GAlaunch_nov2025_utm2&amp;amp;utm_medium=cpc&amp;amp;utm_id=aNKWR000002Z1ph4AC" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Start Building Today&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>auth0</category>
      <category>agents</category>
      <category>security</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
