<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Auth0 for Developers</title>
    <description>The latest articles on DEV Community by Auth0 for Developers (auth0).</description>
    <link>https://dev.to/auth0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F634%2Fc6bfc78f-136d-456b-96dc-bcc4be1c88f9.jpg</url>
      <title>DEV Community: Auth0 for Developers</title>
      <link>https://dev.to/auth0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/auth0"/>
    <language>en</language>
    <item>
      <title>Introducing The Auth0 Plugin for ChatGPT</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Tue, 13 Oct 2026 15:25:34 +0000</pubDate>
      <link>https://dev.to/auth0/introducing-the-auth0-plugin-for-chatgpt-506b</link>
      <guid>https://dev.to/auth0/introducing-the-auth0-plugin-for-chatgpt-506b</guid>
      <description>&lt;p&gt;&lt;em&gt;The new Auth0 plugin for ChatGPT is here, and it’s a massive productivity win for developers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this video, you'll learn how to connect ChatGPT directly to your Auth0 tenant and automatically refactor a standard username/password login into a secure OTP (One-Time Password) flow. Because the plugin leverages your active Auth0 CLI session, you can prompt your way through complex auth changes without ever leaving your editor or digging through documentation.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;How to install and configure the Auth0 ChatGPT plugin.&lt;/li&gt;
&lt;li&gt;Prompting ChatGPT to refactor your codebase for an OTP login flow.&lt;/li&gt;
&lt;li&gt;How the plugin uses the Auth0 CLI to manage active tenant sessions securely.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=2Zon_SNnGkQ" rel="noopener noreferrer"&gt;We Let AI Resurrect a 2-Year-Old Flask Python App (Cursor + Auth0)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=NBHf4OUCi_0" rel="noopener noreferrer"&gt;📺 Manage Your Auth0 Tenants Faster with the Gemini CLI Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📂 &lt;a href="https://www.youtube.com/playlist?list=PLZ14qQz3cfJJDxKv6FEWPTDVWKQlImbIN" rel="noopener noreferrer"&gt;Check out the Using Auth0 playlist&lt;/a&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>ai</category>
      <category>programming</category>
      <category>mcp</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Handling Auth in Next.js 16 with Server Actions and Middleware</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Mon, 12 Oct 2026 10:44:27 +0000</pubDate>
      <link>https://dev.to/auth0/handling-auth-in-nextjs-16-with-server-actions-and-middleware-2gb5</link>
      <guid>https://dev.to/auth0/handling-auth-in-nextjs-16-with-server-actions-and-middleware-2gb5</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: Authentication in Next.js has changed significantly — patterns like client-side session hooks and middleware-only authorization can become insecure in a server-first application. Auth0's Next.js SDK v4 aligns with the App Router by replacing legacy route handlers, simplifying session access across server contexts, and supporting route protection.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Authentication in Next.js has changed significantly over the last few major releases. With Next.js 16, the framework fully embraces a server-first architecture: Server Components render on the server by default, Server Actions execute mutations server-side, and the App Router pushes developers toward colocated backend logic rather than traditional API-centric patterns.&lt;/p&gt;

&lt;p&gt;That shift changes how authentication should be implemented. Patterns that worked well in older Next.js applications, such as client-side session hooks, &lt;code&gt;/api/auth/*&lt;/code&gt; route handlers, and middleware-only authorization checks, can become awkward or even insecure in a server-first application. Session assumptions that were safe in client-heavy architectures no longer hold with Server Actions. Many developers upgrading existing applications discover that older Auth0 integration patterns simply no longer map cleanly to the App Router model.&lt;/p&gt;

&lt;p&gt;Instead of working around the framework, the latest &lt;a href="https://github.com/auth0/nextjs-auth0" rel="noopener noreferrer"&gt;Auth0 Next.js SDK&lt;/a&gt; is designed specifically for this architecture and integrates directly with the server-first model that Next.js 16 promotes. In this guide, you will learn how to build a production-grade authentication layer in a Next.js 16 App Router application using Auth0’s Next.js SDK v4. This article will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The architectural shift from SDK v3 to v4&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;proxy.ts&lt;/code&gt; replaces legacy auth route handlers&lt;/li&gt;
&lt;li&gt;Session access patterns in Server Components, Server Actions, and API routes&lt;/li&gt;
&lt;li&gt;Practical route protection strategies&lt;/li&gt;
&lt;li&gt;Security considerations that matter in &lt;a href="https://auth0.com/blog/auth0-native-integration-with-vercel/" rel="noopener noreferrer"&gt;real-world deployments&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Changed in Auth0 Next.js SDK v4 (and Why It Matters)
&lt;/h2&gt;

&lt;p&gt;Before App Router and Server Components became the default, authentication in Next.js typically revolved around API routes. In the Pages Router era, Auth0 integrations commonly looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/auth/[auth0].ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handleAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth0/nextjs-auth0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;handleAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Protected API routes used wrappers like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;withApiAuthRequired&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Session access relied on request/response objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This model worked well because applications were heavily request-oriented. Rendering often happened client-side or through &lt;code&gt;getServerSideProps&lt;/code&gt;, and API routes formed the natural backend boundary. Next.js 16 changes the execution model entirely. Server Components run directly on the server without explicit request handlers. Server Actions execute independently from page renders, and route segments can stream progressively. The framework assumes that most logic lives server-side from the beginning.&lt;/p&gt;

&lt;p&gt;The diagram below shows how &lt;a href="https://github.com/auth0/nextjs-auth0" rel="noopener noreferrer"&gt;Auth0 SDK v4&lt;/a&gt; aligns directly with that architecture, tracing a request from initial HTTP call through to protected mutation.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmvhw4gt05k4l8orytbxo.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmvhw4gt05k4l8orytbxo.png" alt="An Incoming Request triggers proxy.ts, directing to app routes like Server Component. It intercepts auth routes via Auth0 SDK v4, creating an Encrypted Session Cookie. Server Component uses auth0.getSession(). Valid sessions show Protected UI Render, which enables submissions to Server Action, re-validating the cookie via getSession(). No session causes a redirect. Re-validation authorizes Protected Mutation or rejects the request." width="800" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of manually defining &lt;code&gt;/api/auth/[auth0]&lt;/code&gt;, the SDK now auto-mounts authentication routes like &lt;code&gt;/auth/login&lt;/code&gt;, &lt;code&gt;/auth/logout&lt;/code&gt;, &lt;code&gt;/auth/profile&lt;/code&gt;, etc. when you include a properly configured &lt;code&gt;proxy.ts&lt;/code&gt; file (more on this file in the next section).&lt;/p&gt;

&lt;p&gt;The SDK also simplifies configuration substantially. For example, you can create a shared Auth0 client with only one line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/auth0.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth0Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth0/nextjs-auth0/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Auth0Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK automatically reads environment variables like &lt;code&gt;AUTH0_DOMAIN&lt;/code&gt;, &lt;code&gt;AUTH0_CLIENT_ID&lt;/code&gt;, &lt;code&gt;AUTH0_CLIENT_SECRET&lt;/code&gt;, &lt;code&gt;AUTH0_SECRET&lt;/code&gt;, &lt;code&gt;APP_BASE_URL&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This minimal, &lt;a href="https://auth0.com/blog/you-are-using-auth0-the-hard-way/" rel="noopener noreferrer"&gt;boilerplate-free setup&lt;/a&gt; is intentional. The SDK naturally integrates into the Next.js server-first runtime rather than requiring extensive bootstrapping code.&lt;/p&gt;

&lt;p&gt;The biggest practical change is session access. Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you now use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with no request or response arguments.&lt;/p&gt;

&lt;p&gt;That works because the SDK reads encrypted session cookies directly from the current server execution context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proxy.ts Pattern
&lt;/h2&gt;

&lt;p&gt;The most important architectural concept in Auth0 SDK v4 is &lt;code&gt;proxy.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Next.js 16, &lt;a href="https://nextjs.org/docs/app/getting-started/proxy" rel="noopener noreferrer"&gt;&lt;code&gt;proxy.ts&lt;/code&gt;&lt;/a&gt; acts as a request interceptor that executes before the framework processes a route. It replaces the former &lt;code&gt;middleware.ts&lt;/code&gt; convention, with the new name emphasizing that this code runs at the network boundary and can rewrite, redirect, or modify requests and responses before they reach your application routes. For Auth0, this interception point allows the SDK to handle authentication routes and session-related processing before the request continues into the application.&lt;/p&gt;

&lt;p&gt;Here is the standard implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/proxy.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="cm"&gt;/*
     * Match all request paths except:
     * - _next/static
     * - _next/image
     * - favicon.ico
     * - sitemap.xml
     * - robots.txt
     */&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)&lt;/span&gt;&lt;span class="dl"&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;This file is the mechanism that enables the SDK to intercept and handle authentication routes automatically. Without it, requests to &lt;code&gt;/auth/login&lt;/code&gt; or &lt;code&gt;/auth/callback&lt;/code&gt; will typically return 404 errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: If your application uses a &lt;code&gt;src/&lt;/code&gt; directory, the &lt;code&gt;proxy.ts&lt;/code&gt; file must live inside this &lt;code&gt;src&lt;/code&gt; directory. Otherwise, it belongs at the project root. Misplacing the file often causes silent failures where auth routes return 404s even though the SDK appears configured correctly.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the matcher pattern
&lt;/h3&gt;

&lt;p&gt;The matcher pattern is more important than it initially appears.&lt;/p&gt;

&lt;p&gt;This line tells Next.js which requests should pass through the Auth0 proxy layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exclusions affect both performance and security. For example, paths like &lt;code&gt;_next/image&lt;/code&gt; serve optimized image requests, which should not be intercepted as this can introduce unnecessary auth processing overhead and break asset delivery. On the other hand, paths like &lt;code&gt;favicon.ico&lt;/code&gt; and &lt;code&gt;robots.txt&lt;/code&gt; are public assets that should not be protected behind authentication. You must also set up the matcher to exclude any other public routes in your app, but be careful not to accidentally exclude sensitive paths, as this may cause them to bypass auth interception entirely. This is why you should treat matcher configuration as part of your security model, not merely routing boilerplate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-mounted routes
&lt;/h3&gt;

&lt;p&gt;Once &lt;code&gt;proxy.ts&lt;/code&gt; is configured, the SDK automatically mounts several auth endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/auth/login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/auth/logout&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/auth/callback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/auth/profile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/auth/access-token&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/auth0/nextjs-auth0#routes" rel="noopener noreferrer"&gt;and others&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These routes are now part of the SDK contract, and you no longer need to manually implement them yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Session Access in the Server with Components, Actions, and API Routes
&lt;/h2&gt;

&lt;p&gt;One of the most important concepts in Next.js 16 authentication is understanding that each server execution context behaves differently. Server Components, Server Actions, and API routes all run on the server, but session access patterns are not identical across them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Components
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://react.dev/reference/rsc/server-components" rel="noopener noreferrer"&gt;Server Components&lt;/a&gt; are the cleanest place to access session state. Because they execute directly on the server, they can read encrypted session cookies without any client-side round trip.&lt;/p&gt;

&lt;p&gt;Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/dashboard/page.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;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;&lt;code&gt;getSession()&lt;/code&gt; reads directly from encrypted cookies. If no session is found, the user is redirected. No API request is required, and no token exchange happens during rendering. The redirect occurs server-side before protected content renders. This is both simpler and more secure than relying on client-side auth checks after hydration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Actions
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/docs/app/getting-started/mutating-data" rel="noopener noreferrer"&gt;Server Actions&lt;/a&gt; are where many developers accidentally introduce security gaps. Even though Server Actions execute on the server, they are invoked independently from the client. That means they cannot assume the session state from a previous page render is still valid, and every Server Action must re-validate authentication explicitly.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/actions/update-profile.ts&lt;/span&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Protected mutation logic&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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;Some developers see this repeated session validation as redundant, but it is not. Server Actions are independent execution contexts. A user may have logged out, had their session invalidated, or switched accounts between the initial render and the action invocation. That is why you should treat every Server Action as a fresh authenticated request in order to avoid critical authorization bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  API routes
&lt;/h3&gt;

&lt;p&gt;For API routes, the SDK still provides a convenient protection wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/admin/route.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withApiAuthRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&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="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;This is often the most straightforward approach for protected APIs because unauthenticated requests automatically receive a &lt;code&gt;401 Unauthorized&lt;/code&gt; response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing User Data and Tokens
&lt;/h2&gt;

&lt;p&gt;Once you have a session, you can access user information directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to call a third-party API on behalf of the user, you can also retrieve the access token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Route Protection Strategies
&lt;/h2&gt;

&lt;p&gt;There is no single “correct” protection strategy in Next.js 16. The right approach depends on how much of your application is private and how centralized you want authorization logic to be.&lt;/p&gt;

&lt;h3&gt;
  
  
  Page-level protection
&lt;/h3&gt;

&lt;p&gt;The most explicit strategy is protecting individual pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&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;This approach gives maximum control and clarity since you can tweak the authorization logic for each page.&lt;/p&gt;

&lt;p&gt;Protecting individual pages works especially well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only some routes are protected&lt;/li&gt;
&lt;li&gt;Authorization rules vary per page&lt;/li&gt;
&lt;li&gt;You need custom redirect logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The downside is repetition at scale. Having this guard for every page can get too verbose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layout-level protection
&lt;/h3&gt;

&lt;p&gt;For larger applications, layout-level protection is usually more maintainable.&lt;/p&gt;

&lt;p&gt;For example, this specifies that everything nested under &lt;code&gt;/dashboard&lt;/code&gt; now requires authentication automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/dashboard/layout.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/auth0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&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="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&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 often the best tradeoff for applications with clearly separated public and private route segments, such as a CMS with a public homepage and a dashboard for authenticated users.&lt;/p&gt;

&lt;h3&gt;
  
  
  proxy.ts-level protection
&lt;/h3&gt;

&lt;p&gt;For applications where nearly every route requires authentication, protection can move even higher into the request pipeline. You can extend the proxy matcher to blanket-protect large portions of the app. This reduces duplication and centralizes enforcement. However, it is usually less flexible than page or layout-level checks for applications that mix public marketing pages with authenticated dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using useUser()
&lt;/h2&gt;

&lt;p&gt;So far, you have seen how we can authenticate the user on the server. But what about user profile access on the client-side? The SDK also provides a client-side hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useUser()&lt;/code&gt; is useful for authentication and authorization in the UI, such as conditional UI rendering, user avatars, client-side personalization, navigation states, etc., but it should not be trusted for authorization decisions. Client-side state can be stale, manipulated, or temporarily inconsistent. For anything involving data access, mutations, permissions, or protected rendering, you should always validate using &lt;code&gt;getSession()&lt;/code&gt; on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;The following are some of the most common security tips that you should keep in mind when using Auth0 with Next.js Server Actions and Middleware.&lt;/p&gt;

&lt;h3&gt;
  
  
  AUTH0_SECRET rotation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;AUTH0_SECRET&lt;/code&gt; environment variable encrypts session cookies. If it changes, existing cookies can no longer be decrypted, resulting in errors like &lt;code&gt;JWEDecryptionFailed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In practice, users may need to clear cookies or re-authenticate after secret rotation. This becomes particularly important in CI/CD environments where secrets may accidentally differ across deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Allowed callback, logout, and origin URLs
&lt;/h3&gt;

&lt;p&gt;These &lt;a href="https://auth0.com/docs/get-started/applications/application-settings#application-uris" rel="noopener noreferrer"&gt;Auth0 dashboard settings&lt;/a&gt; are security controls, not just configuration details.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allowed Callback URLs&lt;/li&gt;
&lt;li&gt;Allowed Logout URLs&lt;/li&gt;
&lt;li&gt;Allowed Web Origins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These settings protect against malicious redirect flows and unauthorized origins. If they do not match your deployed application URLs exactly, authentication flows will fail intentionally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not trust client-side session state
&lt;/h3&gt;

&lt;p&gt;Authorization decisions should always happen server-side. This principle deserves repeating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useUser()&lt;/code&gt; is for UI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getSession()&lt;/code&gt; is for authorization in the server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relying on the client state for protected actions is one of the easiest ways to introduce privilege escalation bugs into Next.js applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protect environment variables
&lt;/h3&gt;

&lt;p&gt;Secrets like &lt;code&gt;AUTH0_CLIENT_SECRET&lt;/code&gt; must never be exposed to the client bundle. Fortunately, Next.js only exposes variables prefixed with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; to client-side code. None of the Auth0 secrets use that prefix, so they are safe from being exposed by Next.js.&lt;/p&gt;

&lt;p&gt;However, they can still be accidentally made public. To prevent that, always store them in a &lt;code&gt;.env&lt;/code&gt; file, and never check it into version control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rethinking Auth in the Server Actions Era
&lt;/h2&gt;

&lt;p&gt;Authentication in Next.js 16 requires a different mindset than earlier generations of the framework. The App Router, Server Components, and Server Actions fundamentally change where code executes and how you trust boundaries should be enforced.&lt;/p&gt;

&lt;p&gt;In this guide, you learned how Auth0’s Next.js SDK v4 aligns with that architecture by replacing legacy route handlers with the &lt;code&gt;proxy.ts&lt;/code&gt; interception model, simplifying session access through server-native APIs, and supporting protection strategies that scale from individual pages to entire application segments. You also saw why session validation must happen independently across Server Components, Server Actions, and API routes, and why server-side authorization remains essential even in highly interactive React applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; is the identity platform behind all of this: developer-first, deeply flexible, and designed to scale from a weekend project to enterprise-grade infrastructure without forcing teams to trade control for convenience.&lt;/p&gt;

&lt;p&gt;The Next.js SDK v4 is a strong example of that philosophy in practice. Instead of layering abstractions on top of the framework, it embraces the Next.js server-first architecture directly through patterns like proxy.ts, server-native session access, and streamlined configuration. The result is an authentication model that feels natural inside modern Next.js applications while still supporting the advanced authorization, multi-tenant identity, and extensibility requirements that growing applications eventually need.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>backend</category>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Type-Safe Authentication in Rust with Auth0 and Axum</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Mon, 12 Oct 2026 10:36:40 +0000</pubDate>
      <link>https://dev.to/auth0/type-safe-authentication-in-rust-with-auth0-and-axum-3eio</link>
      <guid>https://dev.to/auth0/type-safe-authentication-in-rust-with-auth0-and-axum-3eio</guid>
      <description>&lt;p&gt;CLI tools are deeply embedded in modern developer workflows. But while browser-based authentication is naturally integrated into web applications, and desktop and mobile applications work well with it, CLI applications present a unique challenge: there is no available browser or redirect URL to complete a standard OAuth login flow.&lt;/p&gt;

&lt;p&gt;This is exactly the problem the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" rel="noopener noreferrer"&gt;OAuth 2.0 Device Authorization Flow&lt;/a&gt; was designed to solve. Rather than redirecting users within the application, the CLI requests a temporary device code, displays a short verification URL and user code, and waits while the user authenticates in their browser. Once complete, the CLI receives tokens it can use for authenticated operations. You have likely encountered this flow before without realizing it. Smart TVs, IoT devices, and headless servers all rely on it when launching or redirecting to a browser is inconvenient or impossible.&lt;/p&gt;

&lt;p&gt;In this tutorial, you will build a complete end-to-end system in Rust and &lt;a href="https://github.com/tokio-rs/axum" rel="noopener noreferrer"&gt;Axum&lt;/a&gt; using Auth0. First, you will create a Rust CLI that authenticates users using the Auth0’s Device Authorization Flow and retrieves both an ID token and an access token. Then, you will build an Axum API to validate that access token before allowing access to a protected route. Together, these two pieces demonstrate the full authentication and authorization lifecycle: one application obtains trust, and another verifies it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Auth0 account. You can &lt;a href="https://auth0.com/signup" rel="noopener noreferrer"&gt;sign up for free&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The Rust toolchain installed on your system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configure Auth0
&lt;/h2&gt;

&lt;p&gt;Start by creating a new Native Application in the Auth0 dashboard.&lt;/p&gt;

&lt;p&gt;Open the Auth0 Dashboard, navigate to &lt;strong&gt;&lt;a href="https://manage.auth0.com/dashboard/#/applications" rel="noopener noreferrer"&gt;Applications &amp;gt; Applications&lt;/a&gt;&lt;/strong&gt;, and click &lt;strong&gt;Create Application&lt;/strong&gt;. Select &lt;strong&gt;Native&lt;/strong&gt; and give the application a name like &lt;code&gt;Rust CLI Demo&lt;/code&gt;, as you can see 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7vfq1tueo0ofdk2iz4t.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7vfq1tueo0ofdk2iz4t.png" alt="Create Application" width="800" height="778"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Native applications are designed specifically for desktop applications and CLI tools that cannot securely store a client secret.&lt;/p&gt;

&lt;p&gt;Once the application is created, open the &lt;strong&gt;Settings&lt;/strong&gt; tab, scroll to &lt;strong&gt;Advanced Settings &amp;gt; Grant Types&lt;/strong&gt;, and enable &lt;strong&gt;Device Code&lt;/strong&gt; as illustrated in the screenshot 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5vfvzz5bwchcmm3f1y1f.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5vfvzz5bwchcmm3f1y1f.png" alt="Advanced Settings" width="799" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This step is critical because Auth0 disables the Device Authorization grant by default. If you skip it, the token polling request will fail with an &lt;code&gt;unauthorized_client&lt;/code&gt; error that is difficult to diagnose if you do not already know the grant type is disabled.&lt;/p&gt;

&lt;p&gt;Now, save your changes.&lt;/p&gt;

&lt;p&gt;You will also need to copy the &lt;strong&gt;Domain&lt;/strong&gt; and &lt;strong&gt;Client ID&lt;/strong&gt; values from the Settings page. Store these somewhere safe, as you will need them later.&lt;/p&gt;

&lt;p&gt;Next, you will create an API in Auth0. This represents the secure API that you will access. Navigate to &lt;strong&gt;&lt;a href="https://manage.auth0.com/dashboard/#/apis" rel="noopener noreferrer"&gt;Applications &amp;gt; APIs&lt;/a&gt;&lt;/strong&gt;, click &lt;strong&gt;Create API&lt;/strong&gt;, give it a name, and set an identifier such as &lt;code&gt;https://rust-api-demo.example.com&lt;/code&gt;, 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2v2i344l8ywrs7a72v14.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2v2i344l8ywrs7a72v14.png" alt="General Settings" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This API identifier becomes the &lt;strong&gt;audience&lt;/strong&gt; for your access tokens.&lt;/p&gt;

&lt;p&gt;Now, you need to give your application access to the API. Go to &lt;strong&gt;Application Access&lt;/strong&gt; and click &lt;strong&gt;Edit&lt;/strong&gt; next to the name of your application. In the &lt;strong&gt;User-Delegated Access&lt;/strong&gt; tab, click &lt;strong&gt;Grant Access&lt;/strong&gt;. Your screen should look like the screenshot 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8f6wwz5fcll3oe43zp6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8f6wwz5fcll3oe43zp6.png" alt="Rust API Application Access" width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a CLI in Rust
&lt;/h2&gt;

&lt;p&gt;With Auth0 configured, you can now start writing the Rust code. The first thing you need to create is a Rust CLI that uses the Device Authorization Flow to authenticate with Auth0.&lt;/p&gt;

&lt;p&gt;Create a new Rust workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;rust-auth0-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;rust-auth0-demo

cargo new auth-lib &lt;span class="nt"&gt;--lib&lt;/span&gt;
cargo new auth0-cli
cargo new auth0-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands create a &lt;code&gt;rust-auth0-demo&lt;/code&gt; directory and three Rust projects within it.&lt;/p&gt;

&lt;p&gt;Configure the workspace root &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[workspace]&lt;/span&gt;
&lt;span class="py"&gt;resolver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;

&lt;span class="py"&gt;members&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"auth-lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"auth0-cli"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"auth0-api"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build the Shared Authentication Library
&lt;/h2&gt;

&lt;p&gt;The token verification logic lives in the shared &lt;code&gt;auth-lib&lt;/code&gt; library crate used by both the CLI and API.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;auth-lib/Cargo.toml&lt;/code&gt;, add the following dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;anyhow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.102"&lt;/span&gt;
&lt;span class="py"&gt;jsonwebtoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"10.4.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"rust_crypto"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;reqwest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.13.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.228"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.150"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These dependencies each serve particular functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;anyhow&lt;/code&gt;: Simplifies error handling and propagation across the application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jsonwebtoken&lt;/code&gt;: Validates and decodes JWTs using Rust-based cryptographic verification.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reqwest&lt;/code&gt;: Sends HTTP requests to Auth0 APIs with form and JSON support.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;serde&lt;/code&gt;: Serializes and deserializes Rust structs to and from JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;serde_json&lt;/code&gt;: Provides JSON parsing, generation, and the &lt;code&gt;json!&lt;/code&gt; macro for structured responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the following structure in the &lt;code&gt;auth-lib&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth-lib/
├── src/
│   ├── lib.rs
│   ├── auth.rs
│   └── models.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define typed models
&lt;/h3&gt;

&lt;p&gt;Inside &lt;code&gt;auth-lib/src/models.rs&lt;/code&gt;, define the data models, which eliminate entire categories of bugs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DeviceCodeResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;device_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;user_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;verification_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;verification_uri_complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TokenResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TokenError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;error_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(untagged)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Audience&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;Multiple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;UserClaims&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;iss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;aud&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="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Jwk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;kid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Jwks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Jwk&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Without these typed structs, authentication code often becomes fragile JSON parsing logic with unchecked assumptions about response fields. Here, every expected field is explicit and validated during deserialization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement JWT Validation
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;auth-lib/src/auth.rs&lt;/code&gt;, add the logic that verifies the JWT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;decode_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Algorithm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DecodingKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Validation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;models&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Jwks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserClaims&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_DOMAIN"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;validate_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UserClaims&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Decode JWT header&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decode_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;kid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;
        &lt;span class="py"&gt;.kid&lt;/span&gt;
        &lt;span class="nf"&gt;.ok_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;anyhow!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Missing kid"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch JWKS&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;jwks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Jwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"https://{}/.well-known/jwks.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;DOMAIN&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Find matching key&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;jwk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwks&lt;/span&gt;
        &lt;span class="py"&gt;.keys&lt;/span&gt;
        &lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.find&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="py"&gt;.kid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.ok_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;anyhow!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No matching JWK"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Build RSA decoding key&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decoding_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DecodingKey&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_rsa_components&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;jwk&lt;/span&gt;&lt;span class="py"&gt;.n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;jwk&lt;/span&gt;&lt;span class="py"&gt;.e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Configure validation&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;Validation&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Algorithm&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;RS256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="nf"&gt;.set_audience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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;validation&lt;/span&gt;&lt;span class="nf"&gt;.set_issuer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&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;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Decode and validate&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;token_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UserClaims&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;decoding_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="py"&gt;.claims&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;The &lt;code&gt;validate_token&lt;/code&gt; function verifies that a JWT was genuinely issued by Auth0 and has not been tampered with. It fetches Auth0’s public signing keys (JWKS), selects the correct key using the token’s &lt;code&gt;kid&lt;/code&gt;, validates the token signature and claims such as issuer and audience, and finally deserializes the payload into a strongly typed &lt;code&gt;UserClaims&lt;/code&gt; struct.&lt;/p&gt;

&lt;p&gt;Here, you can again see Rust’s strong typing in action. The deserialization into the &lt;code&gt;UserClaims&lt;/code&gt; struct guarantees the presence of all the required fields. If any field is absent, you get a hard error instead of silently failing parsing.&lt;/p&gt;

&lt;p&gt;Finally, export the modules in &lt;code&gt;auth-lib/src/lib.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build the CLI and Device Authorization Flow
&lt;/h2&gt;

&lt;p&gt;To start building the CLI and device authorization flow, first, move into the CLI project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;auth0-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following dependencies in &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;anyhow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.102"&lt;/span&gt;
&lt;span class="py"&gt;jsonwebtoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"10.4.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"rust_crypto"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;reqwest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.13.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.228"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.150"&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.52.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;auth-lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../auth-lib"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Request the device code
&lt;/h3&gt;

&lt;p&gt;Before you can add a function to request the device code, which is the first step in the authorization flow, you need to add the following imports and variables inside &lt;code&gt;auth0-cli/src/main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;auth_lib&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="nn"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;validate_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;models&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
        &lt;span class="n"&gt;DeviceCodeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TokenError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TokenResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_DOMAIN"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_CLIENT_ID"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_AUDIENCE"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note: This code hardcodes the domain, client ID, and audience values for simplicity. These values are not sensitive and can be safely stored in code. For more flexibility or to store sensitive secrets, you can use a crate like &lt;a href="https://crates.io/crates/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt; to store values as environment variables.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure you replace the placeholder values with the data from Auth0.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;request_device_code()&lt;/code&gt; function in &lt;code&gt;main.rs&lt;/code&gt; to request the device code and start the Device Authorization Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;request_device_code&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DeviceCodeResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"https://{}/oauth/device/code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;DOMAIN&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"openid profile email"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"audience"&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="p"&gt;])&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;device_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DeviceCodeResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_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;This endpoint makes a request to &lt;code&gt;/oauth/device/code&lt;/code&gt; in your Auth0 domain with the client ID, scope, and audience data. Auth0 returns a short-lived device code along with instructions for the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Poll for the token
&lt;/h3&gt;

&lt;p&gt;Once the code is retrieved, the next step is to show the user the code and the login URL. Since a CLI has no redirect URL or callback mechanism, the only way to know when the authorization is finished is to poll for the status in regular intervals.&lt;/p&gt;

&lt;p&gt;Add the following code in &lt;code&gt;main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;poll_for_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;device_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TokenResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
            &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"https://{}/oauth/token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;DOMAIN&lt;/span&gt;
            &lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"grant_type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"urn:ietf:params:oauth:grant-type:device_code"&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"device_code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device_code&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.is_success&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;response&lt;/span&gt;
                    &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TokenResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
            &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TokenError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="py"&gt;.error&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="s"&gt;"authorization_pending"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Waiting for authentication..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="s"&gt;"slow_down"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Slowing polling interval..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="s"&gt;"expired_token"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;bail!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Device code expired"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="s"&gt;"access_denied"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;bail!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User denied access"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;bail!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Unhandled error: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;other&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="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="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;The &lt;code&gt;poll_for_token&lt;/code&gt; function repeatedly checks Auth0’s token endpoint to see whether the user has completed authentication in the browser. This function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sends the device code obtained earlier&lt;/li&gt;
&lt;li&gt;Handles all possible OAuth device authorization flow responses such as &lt;code&gt;authorization_pending&lt;/code&gt;, &lt;code&gt;slow_down&lt;/code&gt;, and &lt;code&gt;access_denied&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Waits between polling attempts using Tokio’s async sleep&lt;/li&gt;
&lt;li&gt;Returns the access and ID tokens once authentication succeeds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once again, the strong typing ensures that the JSON has all the required fields.&lt;/p&gt;

&lt;p&gt;OAuth device authorization flow responses are inherently asynchronous, and Rust forces you to consciously handle every possible outcome using the &lt;code&gt;match&lt;/code&gt; statement instead of silently ignoring error states.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wire the two functions
&lt;/h3&gt;

&lt;p&gt;Finally, wire these two functions together in the main function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;request_device_code&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Visit:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.verification_uri_complete&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Code: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.user_code&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;poll_for_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.device_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Access Token: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="py"&gt;.access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&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;The &lt;code&gt;main&lt;/code&gt; function calls the &lt;code&gt;request_device_code&lt;/code&gt; function, prints the verification URL and the device code, and calls the &lt;code&gt;poll_for_token&lt;/code&gt; function. The access token returned from this function is printed.&lt;/p&gt;

&lt;p&gt;You can run the CLI by executing &lt;code&gt;cargo run&lt;/code&gt;. You should see an output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visit:
https://your-tenant.auth0.com/activate?user_code=ABCD-EFGH
Code: ABCD-EFGH
Waiting for authentication...
Waiting for authentication...
Waiting for authentication...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you visit the URL in your browser, you will be prompted to log in with Auth0. You can use an existing Auth0 account to log in or create a new account. Once you log in, Auth0 generates a token, which your code will automatically fetch. You will see the access token printed in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access Token: eyJhbGciOiJSUzI1NiIsIn....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this example prints the access token for demo purposes only. In a real-world application, you should never log the access token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetch the Authenticated User
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;token&lt;/code&gt; returned from the &lt;code&gt;poll_for_token&lt;/code&gt; function also includes an ID token, which contains information about the user's identity. The CLI can validate the &lt;strong&gt;ID token&lt;/strong&gt; and extract information about the user, like name or email.&lt;/p&gt;

&lt;p&gt;Add the following function in the same file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;say_hi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;auth_lib&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;models&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UserClaims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;
        &lt;span class="py"&gt;.name&lt;/span&gt;
        &lt;span class="nf"&gt;.as_deref&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome, {}!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function extracts the &lt;code&gt;name&lt;/code&gt; field from a &lt;code&gt;UserClaims&lt;/code&gt; object and prints a greeting.&lt;/p&gt;

&lt;p&gt;You can also fetch the user info from the &lt;code&gt;/userinfo&lt;/code&gt; &lt;a href="https://auth0.com/docs/api/authentication/user-profile/get-user-info" rel="noopener noreferrer"&gt;endpoint&lt;/a&gt; by passing the access token.&lt;/p&gt;

&lt;p&gt;Change the &lt;code&gt;main&lt;/code&gt; function to call the &lt;code&gt;validate_token&lt;/code&gt; function and pass the returned claims to &lt;code&gt;say_hi&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;request_device_code&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Visit:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.verification_uri_complete&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Code: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.user_code&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;poll_for_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.device_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="py"&gt;.interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="py"&gt;.id_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;say_hi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Access token:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="py"&gt;.access_token&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&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;Now, when you run it, you will see the user's name being printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visit:
https://your-tenant.auth0.com/activate?user_code=ABCD-EFGH
Code: ABCD-EFGH
Waiting for authentication...
Waiting for authentication...

Welcome, Alice!

Access token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, you have a fully authenticated CLI application using Auth0’s Device Authorization Flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect an Axum API Route with Auth0
&lt;/h2&gt;

&lt;p&gt;Now that the CLI can retrieve an access token, the next step is building a protected API accessible through it.&lt;/p&gt;

&lt;p&gt;Move into the API project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../auth0-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add dependencies to &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;auth-lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../auth-lib"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="py"&gt;axum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8"&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Axum is a modern asynchronous web framework for Rust built on top of Tokio and Hyper. With its simple and modular design, Axum makes it easy to route requests to handlers with a macro-free API and generate responses with minimal boilerplate. This tutorial uses Axum to create a protected API route that validates Auth0 access tokens before allowing access to authenticated users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a JWT extractor
&lt;/h3&gt;

&lt;p&gt;The first step in protecting your API route is creating a JWT extractor middleware that reads the &lt;code&gt;Authorization&lt;/code&gt; header, extracts the Bearer token, validates the JWT, and rejects invalid tokens with &lt;code&gt;401 Unauthorized&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;auth0-api/src/main.rs&lt;/code&gt;, add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="nn"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FromRequestParts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;http&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nn"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;auth_lib&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="nn"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;validate_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;models&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UserClaims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_AUDIENCE"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nf"&gt;AuthUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;UserClaims&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FromRequestParts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;AuthUser&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt;
    &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Send&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Rejection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;from_request_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Parts&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Rejection&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;
            &lt;span class="py"&gt;.headers&lt;/span&gt;
            &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.to_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
                &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="s"&gt;"Missing authorization header"&lt;/span&gt;
                &lt;span class="p"&gt;})),&lt;/span&gt;
            &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt;
            &lt;span class="nf"&gt;.strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bearer "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
                &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="s"&gt;"Invalid bearer token"&lt;/span&gt;
                &lt;span class="p"&gt;})),&lt;/span&gt;
            &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;token&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="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Invalid token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;AuthUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&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;This middleware centralizes authentication logic so that protected routes only execute if validation succeeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the protected route
&lt;/h3&gt;

&lt;p&gt;The API validates the &lt;strong&gt;access token&lt;/strong&gt;. If the token is valid, a message is shown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;AuthUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;AuthUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="s"&gt;"You are seeing a protected route"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="s"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="py"&gt;.sub&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;Create the &lt;code&gt;main&lt;/code&gt; function to set up the router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/protected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TcpListener&lt;/span&gt;
        &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0.0.0:3000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server listening on port 3000"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&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="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap&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;h3&gt;
  
  
  Call the API from the CLI
&lt;/h3&gt;

&lt;p&gt;The final step of the whole flow is for the CLI to call the API and pass the access token in the &lt;code&gt;Authorization&lt;/code&gt; header. Add the following function in &lt;code&gt;auth0-cli/src/main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;call_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:3000/protected"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.bearer_auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.is_success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API response:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;println!&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="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to_string_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"API returned {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&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;This code calls the protected route you created in the previous step, passes the access token, and prints the response in the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Full Flow
&lt;/h2&gt;

&lt;p&gt;To test the flow, first, run the API server from the &lt;code&gt;auth0-api&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticate using the CLI as before. The CLI will call the API, and you should see the success response:&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;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are seeing a protected route"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google-oauth2|123456789"&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;You can find the full app on &lt;a href="https://github.com/heraldofsolace/rust-auth0-demo" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust, Auth0, and Axum Bring Type-Safe Authentication to Your CLI
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you built a complete authentication flow in Rust using Auth0 and Axum. The CLI used Auth0’s Device Authorization Flow to securely authenticate users in a browserless environment, while the Axum API validated the resulting access token before allowing access to protected endpoints. Rust’s type system plays an important role throughout the implementation, with typed API responses preventing malformed OAuth responses from slipping through silently.&lt;/p&gt;

&lt;p&gt;From here, you could add refresh token support to keep the CLI authenticated across sessions, extend the Axum server with role-based authorization using Auth0 custom claims, or &lt;a href="https://openfga.dev/" rel="noopener noreferrer"&gt;explore fine-grained authorization&lt;/a&gt; for relationship-based access control.&lt;/p&gt;

&lt;p&gt;The same Auth0 platform that powers this example also scales to MFA, enterprise SSO, and multi-tenant B2B identity as your needs grow. Check out the &lt;a href="https://auth0.com/docs" rel="noopener noreferrer"&gt;Auth0 documentation&lt;/a&gt; and the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow#device-authorization-flow" rel="noopener noreferrer"&gt;Device Authorization Flow guide&lt;/a&gt; to keep building.&lt;/p&gt;

</description>
      <category>auth0</category>
      <category>rust</category>
      <category>authentication</category>
    </item>
    <item>
      <title>AI Agents Are Not Users: Building an Identity Model That Reflects That</title>
      <dc:creator>Cameron Pavey</dc:creator>
      <pubDate>Sat, 10 Oct 2026 22:15:00 +0000</pubDate>
      <link>https://dev.to/auth0/ai-agents-are-not-users-building-an-identity-model-that-reflects-that-1mfp</link>
      <guid>https://dev.to/auth0/ai-agents-are-not-users-building-an-identity-model-that-reflects-that-1mfp</guid>
      <description>&lt;p&gt;In April 2026, an &lt;a href="https://www.theguardian.com/technology/2026/apr/29/claude-ai-deletes-firm-database" rel="noopener noreferrer"&gt;AI coding agent deleted the entire production database of PocketOS&lt;/a&gt;, a B2B platform used by car rental companies, along with every volume-level backup, in nine seconds. The agent was Cursor, running on a leading frontier model, configured with explicit safety rules its founder later described as "exactly what these vendors tell developers to do." It was working on a routine staging task when it encountered a credential mismatch, found a token with sufficient permissions, and issued a single GraphQL mutation that wiped everything. &lt;/p&gt;

&lt;p&gt;The system prompt told it not to do anything destructive without approval, but system prompts are advisory, not enforced. As the post-mortem makes clear, the deeper architectural failure was giving a non-deterministic agent the same access a human operator would have, and trusting natural-language instructions to constrain what it did with that access.&lt;/p&gt;

&lt;p&gt;The agent did not behave like a human user. It ran continuously and ignored explicit instructions. It also did not behave like a backend service, as it had no fixed code path and no auditor-readable workflow. The identity layer underneath it had to choose one of those two shapes, because those are the shapes our identity infrastructure offers. It chose wrong, but there was no way for it to choose right, because the right shape did not really exist.&lt;/p&gt;

&lt;p&gt;This article makes the case that AI agents need to be treated as first-class identities in their own right, distinct from human users and from traditional machine identities. That case covers why the traditional models break down, what a first-class agent identity consists of, how lifecycle management has to change, why authorization has to be evaluated at runtime against the request rather than baked into roles at provisioning time, and what observability requirements emerge. The goal is not to build a new identity primitive from scratch, but to extend your existing infrastructure to handle a class of actor it was not designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Legacy Models Break Down
&lt;/h2&gt;

&lt;p&gt;OAuth delegation gives the agent the same authority as the user. When a user grants an agent access to their CRM with the scopes they themselves hold, the agent can do anything the user can, across every future interaction, regardless of what it is being asked to do in any specific moment. The intent is selective delegation, but OAuth cannot express it at that granularity. Saying "let the agent see contact details for accounts I'm currently working on, but not anything in the legal holding queue" requires elaborate scope engineering that few products expose.&lt;/p&gt;

&lt;p&gt;Service accounts have the opposite problem. The agent gets its own credential, but that credential is typically static, broadly scoped, and disconnected from any user context. When something goes wrong, the audit log shows that the service account did it, which is almost no information at all. There is no way to trace back through the chain of delegation, no way to see which user request triggered the action, and no way to revoke a single agent run without revoking the entire service account.&lt;/p&gt;

&lt;p&gt;Both models share four failure modes that worsen as agent usage scales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over-permissioning:&lt;/strong&gt; The credential carries broader authority than the agent needs for the task in front of it, because scopes are typically defined at the resource level (&lt;code&gt;billing:write&lt;/code&gt;) rather than the capability level (&lt;code&gt;billing.refund.issue_under_50_usd&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blast radius:&lt;/strong&gt; A compromise of the agent process, its memory, or its logs exposes a credential that grants standing access to production systems.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of behavioral context:&lt;/strong&gt; The identity layer cannot distinguish between "the agent is summarizing tickets" and "the agent is issuing refunds" because both operations present the same broadly-scoped credential that authorizes both actions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential sprawl:&lt;/strong&gt; Every new tool the agent integrates with means another credential to provision, store, rotate, and audit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All four failure modes existed before AI agents and have been seen in incidents involving traditional automation. However, agents make them worse for two main reasons. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The gap between the credential's authority and the work the agent is doing at any specific moment is much wider than it is for traditional automation.
&lt;/li&gt;
&lt;li&gt;The agent's behavior is &lt;a href="https://auth0.com/blog/ai-agents-have-two-souls-you-control-only-one" rel="noopener noreferrer"&gt;non-deterministic&lt;/a&gt; in a way that conflicts with the assumptions baked into both models. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A service account is safe partly because the code calling it is fixed. An agent's "code" is a tool-calling loop steered by an LLM interpreting natural language input that may itself be untrusted. When you can no longer rely on the determinism of an automation's code, the credential's scoping becomes one of the few remaining constraints on what the agent can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a First-Class Agent Identity Looks Like
&lt;/h2&gt;

&lt;p&gt;A first-class agent identity is more than a service account with a different label. It has its own lifecycle, permissions, policy bindings, and audit trail. It is an identity primitive that is structurally distinct from both users and traditional machine identities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openfga.dev/" rel="noopener noreferrer"&gt;OpenFGA&lt;/a&gt;, the CNCF authorization engine that underpins Auth0's Fine-Grained Authorization product, formalizes this in its &lt;a href="https://openfga.dev/docs/modeling/agents" rel="noopener noreferrer"&gt;authorization patterns for agents&lt;/a&gt;. Agents are modeled as &lt;a href="https://auth0.com/blog/why-ai-agents-need-their-own-permission-model" rel="noopener noreferrer"&gt;first-class principals&lt;/a&gt; that participate in the same permission hierarchy as users, with their own type in the authorization model and their own relationships to resources, but with permissions scoped to capabilities and bound to tasks rather than inherited as-is from a user role.&lt;/p&gt;

&lt;p&gt;Specifically, a first-class agent identity has four properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A directory identifier&lt;/strong&gt; distinct from any user or service account, with metadata describing the agent type, the agent definition version, and ideally, who is accountable for its behavior.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped permissions&lt;/strong&gt; defined in business terms, separate from any user's permissions even when the agent is acting on a user's behalf.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delegation context&lt;/strong&gt; recording both the agent as actor and the user as subject when the agent acts on someone's behalf. Downstream systems see "agent X, acting on behalf of user Y, with delegated authority Z" rather than just "service account did a thing."
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dedicated audit trail&lt;/strong&gt; attributing every action to the specific agent identity with the delegation chain intact, separate from the logs the underlying tools generate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A backend service has a static workflow and code paths that an auditor can read, so a broadly scoped credential is constrained by what the code does. An agent has none of these constraints. Its workflow is determined at runtime by an LLM, and its code path is a tool-calling loop that takes in untrusted input. The credential has to do the work that the code is not doing, and it has to do that work at the request level, not at provisioning time. OAuth 2.0 &lt;a href="https://datatracker.ietf.org/doc/html/rfc8693" rel="noopener noreferrer"&gt;Token Exchange (RFC 8693)&lt;/a&gt; was designed to support this kind of dynamic, request-scoped delegation, and it is the foundation that Auth0's Token Vault builds on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lifecycle Management for Ephemeral Actors
&lt;/h2&gt;

&lt;p&gt;User accounts live for years, and service accounts live for the lifetime of the service. Agent identities live somewhere in between, often lower down the spectrum. A long-running agent that handles support tickets might persist for the lifetime of the deployment. A task-scoped agent that runs once to answer a single user query might exist only for the duration of that task.&lt;/p&gt;

&lt;p&gt;Provisioning is rarely interactive. Consent happens when a user authorizes an application that uses agents, and the agent identities are created on demand by that application. Provisioning has to be programmatic, idempotent, and tied to the agent definition rather than a human approval workflow. &lt;/p&gt;

&lt;p&gt;Execution-context credentials should not be the same credentials that establish the agent's identity. The identity establishes who the agent is and what it could in principle do, while the execution-context credential is a short-lived token, scoped to the operation in front of the agent, that authorizes a particular call. This is the pattern Auth0's Token Vault implements at the OAuth layer: the agent does not hold the long-lived refresh token for a connected service, and instead requests a &lt;a href="https://auth0.com/blog/secure-ai-stop-managing-api-keys-by-hand" rel="noopener noreferrer"&gt;just-in-time access token through OAuth 2.0 Token Exchange&lt;/a&gt;. If the agent is compromised, the only credential exposed is whatever short-lived token it currently holds.&lt;/p&gt;

&lt;p&gt;The diagram below shows what this flow looks like in practice. There are two phases. Once, when the application is first authorized, the user grants consent and the identity provider stores the long-lived refresh token in Token Vault, where it stays. Then, for each operation the agent needs to perform, it requests a short-lived access token scoped to that specific operation. The identity provider evaluates the request against policy, returns a token that lasts minutes rather than days, and the agent uses it for a single tool call before discarding it. If the agent is compromised, the attacker inherits only whatever short-lived token happens to be in memory, and revoking the connection at the identity provider invalidates all future token exchanges immediately.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffeaxp31k09o5vappx4iw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffeaxp31k09o5vappx4iw.png" alt="Sequence diagram showing the provisioning of agent credentials" width="800" height="911"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suspension and decommissioning also have to be easy, fast, and accurate. Recall the PocketOS incident: when the agent went off the rails, there was no equivalent of "revoke the agent's database write capability for the next ten minutes while we figure out what just happened." The credential it was using gave it standing authority, and standing authority can only be revoked at the level of the whole credential. Centralizing token issuance through a single identity provider makes finer-grained revocation possible, since revoking a connection invalidates future token exchanges immediately while leaving the rest of the system running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy-Driven Authorization
&lt;/h2&gt;

&lt;p&gt;Static role assignment is the traditional model for user and service identity: an actor has a role, the role grants permissions, and permissions are checked against actions. This works fine when behavior is predictable, but is inadequate for agents. An agent might have access to a wide set of capabilities in principle, but in any given task, it should only exercise a narrow subset, determined by the task itself.&lt;/p&gt;

&lt;p&gt;A better model is to bind authorization to behavior rather than to identity, evaluated at runtime against the specific request. There are two relevant patterns here. Attribute-based access control (ABAC) takes contextual attributes into account: the user the agent is acting on behalf of, the resource, the time, the action, and any task parameters. &lt;a href="https://auth0.com/blog/relationship-based-access-control-rebac/" rel="noopener noreferrer"&gt;Relationship-based access control (ReBAC)&lt;/a&gt; goes further, expressing authorization in terms of relationships between entities. This makes it natural to write rules like "an agent can read contact details for accounts where it has an active assigned ticket," because the relationship is a first-class concept in the model rather than something encoded through scope engineering.&lt;/p&gt;

&lt;p&gt;ABAC alone is enough for most teams. If your authorization rules can be expressed as "this attribute equals that attribute," you may not need the full relational graph. ReBAC pays for itself when you're managing thousands of resource-relationship pairs across a multi-tenant system, where the cost of expressing those rules in any other model becomes prohibitive. For a small team with a single-tenant product and a handful of resource types, a policy engine evaluating attributes against requests will likely cover most of what you want. You may not need ReBAC, but if you are deploying agents, you need some kind of policy-driven, runtime-evaluated authorization, whatever shape that takes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/fine-grained-authorization" rel="noopener noreferrer"&gt;Auth0's Fine-Grained Authorization&lt;/a&gt;, built on OpenFGA, is one of the more developer-accessible ways to do the ReBAC version of this. The authorization model defines types and relations, and condition checks ask whether a specific relationship holds for a specific request. The following is an example of an OpenFGA authorization model definition with a few relationships between users, agents, and accounts, and a condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;model&lt;/span&gt;  
  &lt;span class="s"&gt;schema &lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;

&lt;span class="s"&gt;type user&lt;/span&gt;

&lt;span class="s"&gt;type agent&lt;/span&gt;

&lt;span class="s"&gt;type account&lt;/span&gt;  
  &lt;span class="s"&gt;relations&lt;/span&gt;  
    &lt;span class="s"&gt;define owner&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;  
    &lt;span class="na"&gt;define assigned_agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;agent with active_assignment&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;  
    &lt;span class="na"&gt;define can_read&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assigned_agent or owner&lt;/span&gt;

&lt;span class="na"&gt;condition active_assignment(current_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;timestamp, expires_at&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;timestamp) {&lt;/span&gt;  
  &lt;span class="s"&gt;current_time &amp;lt; expires_at&lt;/span&gt;  
&lt;span class="err"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent identity participates in the relationship graph as a first-class type. The condition expression incorporates runtime context — in this case, a time-bound assignment that expires automatically. The check happens at the moment the agent attempts an action, not when the agent was provisioned. If the assignment has expired, the check fails and the agent cannot perform the action.&lt;/p&gt;

&lt;p&gt;Static permissions cannot keep up with agents that make autonomous decisions in real time. By the time a role change has rolled out to fix an over-permissioning issue, the agent has already acted with the old permissions a thousand times. Policy-driven authorization moves the decision to the moment of the action, where the context is known and the authorization layer can evaluate the specific request rather than against a generalization of expected behavior. That is also what makes the audit trail meaningful: every check is a discrete decision tied to a specific request, not a side effect of role membership configured in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability and Non-Repudiation
&lt;/h2&gt;

&lt;p&gt;A user-proxy or service-account model collapses the chain of accountability. The log shows that "the user" or "the service" did something, with no record of which agent was involved or what task it was executing. With a first-class agent identity, the chain is preserved, and three observability requirements become apparent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delegation chains must be explicit:&lt;/strong&gt; Each API call should be traceable back through &lt;code&gt;user → agent → sub-agent → tool → resource&lt;/code&gt;, with each hop recorded. OAuth 2.0 Token Exchange supports this, with actor and subject claims expressing the delegation relationship in the token itself.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decisions must be logged, not just actions:&lt;/strong&gt; Knowing the end decision is not enough. You also need to know which plan the agent was executing, which tool calls led to that point, what input shaped the decision, and which policy evaluations approved or denied each step. This is closer to distributed tracing than to traditional audit logging.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-repudiation must extend to original user intent:&lt;/strong&gt; When a user authorizes an action, that authorization should be bound to the specific action approved, not to a generic scope. &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar" rel="noopener noreferrer"&gt;Rich Authorization Requests (RAR)&lt;/a&gt; support this by letting the authorization request carry structured details about what is being approved. Combined with &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; for asynchronous user approval, the audit log can show exactly what the user approved and how the agent executed against it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Centralized token issuance, as with &lt;a href="https://auth0.com/docs/secure/call-apis-on-users-behalf/token-vault" rel="noopener noreferrer"&gt;Auth0's Token Vault&lt;/a&gt;, supports all three requirements by routing every credential through a single control plane. Every token is logged with the agent identity, the user it was issued for, the scope, and the time. Every revocation propagates immediately. The audit infrastructure does not have to reconstruct the chain of custody from scattered logs across multiple tools because the identity provider has already recorded it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building for Agent Identity
&lt;/h2&gt;

&lt;p&gt;Treating AI agents as first-class identities is no longer a theoretical exercise. The traditional models of user-proxy delegation and static service accounts were not designed for non-deterministic actors that interpret natural language at runtime, and the gaps they leave compound quickly as agents take on more of the work the business depends on. The right response is to extend existing identity infrastructure with primitives designed for the way agents behave: distinct identities with their own lifecycles, policies bound to behavior rather than to roles, and audit trails that preserve the delegation chain from user intent through autonomous action.&lt;/p&gt;

&lt;p&gt;Most of the building blocks exist. OAuth 2.0 Token Exchange provides the delegation semantics. CIBA and Rich Authorization Requests provide standards-based approval flows. OpenFGA and similar engines provide the policy infrastructure. Products like Auth0's Token Vault and Fine-Grained Authorization assemble these into developer-accessible platforms, and the patterns documented in OpenFGA's &lt;a href="https://openfga.dev/docs/modeling/agents" rel="noopener noreferrer"&gt;Authorization for Agents&lt;/a&gt; guidance show how to apply them in practice.&lt;/p&gt;

&lt;p&gt;Organizations need to recognize that the identity model is not an implementation detail to be papered over with whichever credential pattern is most familiar to them. The PocketOS incident was not a freak accident. It was the predictable consequence of applying an identity model to a class of actors it was never designed for. AI agents in production deserve the same care that goes into provisioning user accounts and service identities, with the recognition that they are neither.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>Why AI Agents Need Their Own Permission Model</title>
      <dc:creator>Cameron Pavey</dc:creator>
      <pubDate>Sat, 10 Oct 2026 09:09:44 +0000</pubDate>
      <link>https://dev.to/auth0/why-ai-agents-need-their-own-permission-model-58i</link>
      <guid>https://dev.to/auth0/why-ai-agents-need-their-own-permission-model-58i</guid>
      <description>&lt;p&gt;AI agents are rapidly making their way into production systems with access to real customer data, internal tooling, and revenue-impacting workflows. A support automation agent might read tickets from a CRM, summarize conversation history, issue partial refunds, create internal escalation tickets, and post a status update to Slack, all within the span of a single task. When these agents work, they can streamline workflows and compress the cost of routine operations. When they fail, they fail differently than traditional automation: non-deterministically, at scale, and often with full production credentials in hand.&lt;/p&gt;

&lt;p&gt;To be useful, an agent needs enough access to execute dynamic, multi-step workflows across multiple tools. But to be safe, it can't operate as a "superuser" with permanent, broad authority over those same tools. Most teams tend to default to access patterns they already know, like long-lived API keys baked into environment variables or OAuth flows designed for human users, and discover quickly that these patterns were never built for non-deterministic software. A misconfigured prompt, a malicious tool response, or a simple hallucination can turn into a significant incident.&lt;/p&gt;

&lt;p&gt;This guide teaches you how to apply the least-privilege principle to autonomous systems. You'll learn how to scope permissions to capabilities rather than resources, issue short-lived credentials tied to specific execution plans, separate identity, authorization, and execution into distinct layers, and introduce human-in-the-loop approval for high-risk actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Access Model Mismatch
&lt;/h2&gt;

&lt;p&gt;Traditional access control models assume you are one of two actors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first is a &lt;strong&gt;human user&lt;/strong&gt;: interactive, bounded by a session, constrained by a UI, and generally predictable within a single workflow.
&lt;/li&gt;
&lt;li&gt;The second is a &lt;strong&gt;backend service&lt;/strong&gt;: deterministic, fixed in its workflow, and auditable through static code paths. AI agents fit neither category.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents are non-deterministic by design. Two runs of the same prompt against the same data can produce different tool call sequences. They chain tool invocations in ways the developer did not explicitly write, and they can be influenced by untrusted input through two &lt;a href="https://auth0.com/blog/how-to-fix-five-critical-ai-agent-security-risks/" rel="noopener noreferrer"&gt;well-documented threat classes&lt;/a&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection:&lt;/strong&gt; Instructions embedded in a document or web page hijack the agent's behavior.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool output poisoning:&lt;/strong&gt; A tool response manipulates subsequent decisions in the chain. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Increasingly, agents also spawn sub-agents, so a delegation chain that starts with a single user request can fan out across many independent contexts before it completes.&lt;/p&gt;

&lt;p&gt;The identity primitives built for these two types of actors (session tokens, service accounts, and &lt;a href="https://auth0.com/docs/get-started/apis/scopes" rel="noopener noreferrer"&gt;OAuth scopes&lt;/a&gt;) are not broken in isolation; used with strict scoping, mediation, and runtime enforcement, they can work well. The issue is that they were not designed for non-deterministic, multi-step autonomous execution, and their &lt;em&gt;typical&lt;/em&gt; usage patterns, which assume a bounded session or a deterministic workflow, do not hold. A session token assumes a bounded interactive flow. A service account assumes deterministic behavior. An OAuth scope like &lt;code&gt;billing:write&lt;/code&gt; assumes an application on the other end interprets it as intended by the user.. When an agent inherits any of these without additional layers, it gets authority without the corresponding accountability structures that made the original model safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Before reviewing better patterns, it's worth looking at the shortcuts teams typically reach for. Each one feels reasonable in isolation, but together, they can be dangerous.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedding long-lived credentials
&lt;/h3&gt;

&lt;p&gt;The first common anti-pattern is embedding long-lived API keys directly in agent configurations or environment variables. The agent holds a root credential that grants broad access to a tool, and any compromise of the agent process, logs, or memory exposes that credential. While this is also a problem in non-agentic software, the risk is elevated with agents because, depending on how they are configured, they can be tricked or coerced into taking actions they shouldn’t, or revealing credentials they have access to, much more easily than traditional software with long-lived credentials can be exploited.&lt;/p&gt;

&lt;p&gt;Even with rotation in place via something like Vault or AWS Secrets Manager, the real issue is blast radius; rotation is often inconsistent or slow, and the window between compromise and rotation is more than enough for an attacker (or a misbehaving agent) to do significant damage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full user OAuth scope inheritance
&lt;/h3&gt;

&lt;p&gt;The second anti-pattern is full user OAuth scope inheritance. A user grants the agent access to their CRM with the permissions they themselves hold, and the agent now has those scopes permanently, across every future action, regardless of what task it is actually performing. The agent inherits authority that the user only ever intended to exercise selectively. However, the agent does not have the discretion, contextual awareness, or accountability that the user has, and it is only a matter of time before the agent accidentally misuses these overly broad permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agents with effective superuser privileges
&lt;/h3&gt;

&lt;p&gt;The third, and most damaging anti-pattern, is agents operating with &lt;a href="https://auth0.com/blog/mitigate-excessive-agency-ai-agents/" rel="noopener noreferrer"&gt;effective superuser privileges&lt;/a&gt; over the tools they orchestrate, typically at a system-level, rather than an individual user-level. This happens implicitly when you use the most permissive available scope rather than defining a narrower one. The support agent mentioned earlier might be given a &lt;code&gt;billing:write&lt;/code&gt; scope because it's the only billing scope available, and now it can issue refunds of any size, modify any customer's payment method, and cancel any subscription, even though its actual job is issuing small goodwill refunds within a narrow policy.&lt;/p&gt;

&lt;p&gt;Consider what happens when a support operations agent with &lt;code&gt;billing:write&lt;/code&gt; is asked by a user, "Process a full refund for order #12345." The order total is $10,000 USD. With no way to distinguish between refund sizes it's allowed to process, and those it is not, the agent simply proceeds. Nothing in the access layer prevents it. The damage is done in milliseconds, and the only audit trail is a refund record that looks identical to any legitimate one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capability-Scoped Permissions
&lt;/h2&gt;

&lt;p&gt;The first step in solving the problem of agent permissions is to stop thinking about permissions in terms of resources and start thinking about them in terms of capabilities. A scope like &lt;code&gt;billing:write&lt;/code&gt; describes a resource category and a verb. A capability like &lt;code&gt;billing.refund.issue_under_50_usd&lt;/code&gt; encodes a specific action type, a resource limit, and an implicit risk boundary, although you would not typically encode a capability check into a scope like this, due to the inevitable explosion of scopes that would follow. &lt;/p&gt;

&lt;p&gt;Capability-scoped permissions express authorization in terms the business actually reasons about. When a product manager decides the support agent should be able to issue courtesy refunds up to $50 USD, that decision should live in a declarative policy that the authorization system evaluates, not scattered through imperative checks in the application code that calls the API. Whether the $50 USD threshold is enforced by the authorization engine itself or by an in-code check, such as a dedicated policy layer that is consulted before token issuance is an implementation detail; what matters is that the rule is centrally managed and auditable.&lt;/p&gt;

&lt;p&gt;This is where fine-grained authorization systems come in, and while they solve a specific piece of the puzzle, they're not always an all-in-one solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openfga.dev/" rel="noopener noreferrer"&gt;OpenFGA&lt;/a&gt;, an open-source authorization engine created by Auth0's FGA team and now a Cloud Native Computing Foundation (CNCF) project, implements Relationship-Based Access Control (ReBAC). ReBAC excels at modelling &lt;em&gt;who can act on what&lt;/em&gt;. The support agent isn't just a role, but an entity with specific, bounded relationships to specific resource types, and those relationships produce different permissions. You can express rules like "an agent can refund an order only if the order belongs to a customer with whom the agent has an active ticket."&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Femxusoohhlea4n817n2l.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Femxusoohhlea4n817n2l.png" alt="Diagram showing an authorization flow" width="800" height="1010"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What pure ReBAC does not do is enforce numeric or attribute-based constraints like "refund amount under $100." OpenFGA extends the base ReBAC model with &lt;a href="https://openfga.dev/docs/modeling/conditions" rel="noopener noreferrer"&gt;Conditions&lt;/a&gt;, which let you attach predicates to a relation so that numeric limits, time-bounded grants, and contextual attributes are evaluated as part of the authorization check. &lt;/p&gt;

&lt;p&gt;In OpenFGA, the two layers in the diagram above collapse into a single check: the relationship and its condition are evaluated together, and either can deny. A capability like "agent can refund orders where amount &amp;lt; configured_limit" can be expressed directly in OpenFGA via a condition, with the amount supplied either in the tuple itself or contextually at check time. For teams that prefer to keep pure authorization policy separate from declarative policy code, a dedicated engine like Cedar or OPA layered on top is a legitimate alternative, but it isn't required; OpenFGA can handle both the relationship graph and the attribute constraints.&lt;/p&gt;

&lt;p&gt;Concretely, a support-agent refund capability with a per-agent limit looks like this in OpenFGA's DSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;model  
  schema 1.1

&lt;span class="nb"&gt;type &lt;/span&gt;user  
&lt;span class="nb"&gt;type &lt;/span&gt;agent  
&lt;span class="nb"&gt;type &lt;/span&gt;customer

&lt;span class="nb"&gt;type &lt;/span&gt;order  
  relations  
    define owner: &lt;span class="o"&gt;[&lt;/span&gt;customer]  
    define can_refund: &lt;span class="o"&gt;[&lt;/span&gt;agent with refund_within_limit]

condition refund_within_limit&lt;span class="o"&gt;(&lt;/span&gt;refund_amount: int, agent_limit: int&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
  refund_amount &amp;lt;&lt;span class="o"&gt;=&lt;/span&gt; agent_limit  
&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The capability is granted by writing a relationship tuple that carries the agent's configured limit as &lt;em&gt;persisted condition context&lt;/em&gt;. For a support agent with a $50 USD ceiling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fgaClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
  &lt;span class="na"&gt;writes&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="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agent:support_agent_01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="na"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can_refund&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order:12345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refund_within_limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;agent_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At check time, the runtime supplies the request-specific half of the condition context (the actual refund amount being attempted):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;allowed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fgaClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agent:support_agent_01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="na"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can_refund&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order:12345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;refund_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;75&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;OpenFGA merges the two sides when evaluating the condition, with persisted tuple context taking precedence over request context if a key appears in both. With &lt;code&gt;agent_limit: 50&lt;/code&gt; stored in the tuple and &lt;code&gt;refund_amount: 75&lt;/code&gt; supplied at check time, the condition &lt;code&gt;refund_amount &amp;lt;= agent_limit&lt;/code&gt; evaluates to &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;allowed&lt;/code&gt; comes back &lt;code&gt;false&lt;/code&gt;; the agent cannot autonomously issue a $75 USD refund, and the runtime's next move is to trigger a human approval flow (covered later in this article) rather than request a refund-capable token.&lt;/p&gt;

&lt;p&gt;The practical takeaway is to define capabilities that match actual business boundaries. Not &lt;code&gt;billing:write&lt;/code&gt;, but &lt;code&gt;billing.refund.issue_under_50_usd&lt;/code&gt;. Not &lt;code&gt;crm:read&lt;/code&gt;, but &lt;code&gt;crm.contact.read_for_assigned_tickets&lt;/code&gt;. When you catch yourself defining a capability that feels uncomfortably broad, take a moment to consider if you can strip it back to something less permissive, without compromising the functionality you're building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Task-Scoped Credentials
&lt;/h2&gt;

&lt;p&gt;Capability scoping addresses what the agent can do. Task scoping addresses when and for how long it can do it. These are separate concerns, and conflating them is a common mistake.&lt;/p&gt;

&lt;p&gt;A well-designed agent minimizes or eliminates persistent credentials wherever possible. Instead of holding standing access, it requests short-lived tokens tied to each operation's execution plan. The token carries only the capabilities needed for that plan, expires quickly (minutes, not days), and is discarded after use. In practice, some systems still cache short-lived tokens or rely on scoped service accounts for infrastructure concerns. The goal is less about purity, and more about reducing the window in which any given credential is useful.&lt;/p&gt;

&lt;p&gt;This pattern has several useful properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The window for credential exposure incidents collapses, so a token leaked from an agent's memory is only useful for a short time.
&lt;/li&gt;
&lt;li&gt;The blast radius of a compromised agent shrinks, because the agent can only act with whatever short-lived token it currently holds.
&lt;/li&gt;
&lt;li&gt;The agent itself never touches the root credential. A separate component brokers tokens on the agent's behalf, so even if the agent process is compromised, the underlying OAuth refresh tokens or API keys are not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auth0's Token Vault implements this pattern at the identity layer. The vault securely stores OAuth tokens for connected services (Google, Slack, Salesforce, GitHub, and others), and the agent requests a just-in-time access token scoped to a specific task rather than handling the long-lived credential itself. Token Vault implements &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/token-exchange-flow" rel="noopener noreferrer"&gt;OAuth 2.0 Token Exchange (RFC 8693)&lt;/a&gt;, giving it a standards-based foundation rather than a proprietary flow. Depending on the flow used, the agent may never touch the underlying refresh token at all; in the &lt;a href="https://auth0.com/blog/developers-guide-flexible-token-exchange-auth0/" rel="noopener noreferrer"&gt;access-token-exchange flow&lt;/a&gt; designed for SPA-plus-backend architectures, the refresh token stays on the Auth0 side entirely.&lt;/p&gt;

&lt;p&gt;For the support agent referenced in this article, this means that when it decides to issue a refund, it does not already hold a refund-capable token. The runtime first evaluates the request against the policy layer, asking questions like: is the amount within the agent's autonomous threshold, is the customer not flagged for fraud review, does the agent have the right relationship to this order, and only then requests a short-lived access token scoped to the refund capability and this specific customer. If the agent is compromised between tasks, there is no standing refund authority to exploit; any earlier token has already expired, and issuing a new one requires passing the policy checks again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identity, Authorization, and Execution Are Three Different Layers
&lt;/h2&gt;

&lt;p&gt;A useful clarification when designing agent access control is to recognize that three distinct concerns are often collapsed into one implementation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity&lt;/strong&gt; establishes who the agent is. It answers the question, "which agent, acting on behalf of which user, is making this request?" Identity is relatively stable; an agent typically has a consistent identity across many tasks.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization&lt;/strong&gt; determines what an authenticated actor is permitted to do. It answers, "given this identity, what capabilities apply?" Authorization is a policy decision, and it should be evaluated fresh for each operation, not inherited from a long-lived grant.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution enforcement&lt;/strong&gt; determines what is actually allowed in a specific runtime context. It answers, "for this specific call, with this specific payload, does policy permit it?" This includes things like the refund amount, the target resource, and any contextual constraints that cannot be known until the request is actually made.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conflating these leads to familiar failure modes. If identity and authorization are the same thing, then authority is frozen at authentication time and cannot adapt to the task being performed. Similarly, if authorization and execution are coupled, then policy cannot be centrally managed, audited, or updated. Each layer needs its own boundary, and each boundary should be enforced by a different component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural Separation
&lt;/h2&gt;

&lt;p&gt;In practice, handling these three concerns means designing three distinct layers in any production agent system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identity provider layer
&lt;/h3&gt;

&lt;p&gt;The identity provider layer handles authentication and token issuance. Auth0 authenticates users, manages connected accounts via Token Vault, issues scoped tokens, and handles delegation flows. The identity provider knows who the agent is and what capabilities are available to it, but does not execute business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime environment layer
&lt;/h3&gt;

&lt;p&gt;The runtime environment layer interprets agent plans, requests credentials from the identity provider, and enforces policy before making calls to downstream tools. This is typically your agent orchestration code, often built on a framework like LangChain, LlamaIndex, or the Vercel AI SDK. &lt;/p&gt;

&lt;p&gt;In a well-designed system, the runtime is separate from the LLM process itself: the LLM produces tool calls, but the runtime decides whether those calls are permitted, requests credentials for them, and makes the actual API calls. Many real-world agent systems fall short of this, passing API keys into tool wrappers or exposing tokens through tool configuration. Keeping credentials out of the LLM's reach is a design goal worth pursuing deliberately, not a property you get for free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool layer
&lt;/h3&gt;

&lt;p&gt;The tool layer is the downstream system, such as the CRM, billing service, or Slack API, that actually executes the operation. The tool layer is responsible for its own enforcement and logging, independent of whatever the agent or runtime believes is permitted. This defense ensures that even if the runtime is compromised, the tool still enforces its own authorization rules.&lt;/p&gt;

&lt;p&gt;When this separation is maintained, it has a critical security benefit: the LLM process itself never holds credentials. The runtime brokers them, presents them for individual API calls, and discards them. A prompt injection attack that tries to convince the LLM to leak "its credentials" has nothing to leak, because the LLM was never given any.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3ydhquhax38fblaost6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3ydhquhax38fblaost6.png" alt="Diagram showing architecture of a ReBAC enabled authorization flow" width="800" height="1330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Approval Boundaries for High-Risk Actions
&lt;/h2&gt;

&lt;p&gt;Even with capability-scoped, task-scoped credentials, some actions warrant an extra step: &lt;em&gt;explicit human approval at the moment of execution&lt;/em&gt;. This is not a fallback for a broken permission model. It's an intentional design choice for operations whose risk profile justifies the extra friction.&lt;/p&gt;

&lt;p&gt;The question is, what actions cross that line. The support agent issuing a $5 USD refund probably should not require human approval; the automation value comes from handling those without intervention. The same agent attempting a $2,000 USD refund, a refund to an account flagged for fraud review, or any destructive action on a customer record is a different matter entirely. These are the operations where getting it right matters more than getting it fast.&lt;/p&gt;

&lt;p&gt;Auth0 implements asynchronous approval using the &lt;a href="https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html" rel="noopener noreferrer"&gt;Client-Initiated Backchannel Authentication (CIBA) standard&lt;/a&gt;, an OpenID Foundation specification that lets a client (such as an agent backend) request user approval out-of-band from a separate trusted device. Most production agent systems today handle approval through bespoke flows, internal policy engines, or workflow systems like Temporal. CIBA offers a standards-based alternative that slots into existing OAuth infrastructure rather than requiring a parallel approval system. When the support agent identifies that an operation exceeds its autonomous threshold, its backend sends a &lt;a href="https://auth0.com/blog/async-ciba-python-langgraph-auth0/" rel="noopener noreferrer"&gt;CIBA request&lt;/a&gt; to Auth0. The user receives a push notification on their enrolled mobile device via the Auth0 Guardian app (with sms or email also as supported channels), and the agent polls for a response. If the user approves, the agent receives a token scoped to that specific action. If the user denies or the request times out, nothing happens.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhc5djmv30p8yfbkl5wqf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhc5djmv30p8yfbkl5wqf.png" alt="Diagram showing authorization flow" width="800" height="1044"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CIBA becomes more powerful when combined with &lt;a href="https://datatracker.ietf.org/doc/html/rfc9396" rel="noopener noreferrer"&gt;Rich Authorization Requests (RAR)&lt;/a&gt;, an OAuth 2.0 extension that lets the authorization request carry detailed, structured context about what is actually being approved. Rather than a generic "approve access to billing?" prompt, the user sees something specific, such as "approve refund of $2,000 to customer Jane Doe for order #12345." The structured &lt;code&gt;authorization_details&lt;/code&gt; payload makes the approval verifiable and auditable in a way a broad scope grant cannot be.&lt;/p&gt;

&lt;p&gt;This pattern directly addresses the $10,000 refund scenario. The agent's capability scope permits refunds under a low threshold autonomously. For anything above that, the agent cannot proceed without triggering a CIBA flow, and the user sees the exact amount and target before approving. The agent cannot bypass this because the token it needs to execute the refund is only issued after approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability and Control
&lt;/h2&gt;

&lt;p&gt;The final layer is observability. An agent that operates under strict permission controls is still operating autonomously across many systems, and understanding what it did, why, and on whose behalf is essential for debugging, incident response, and compliance.&lt;/p&gt;

&lt;p&gt;Three elements matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audit trails should capture agent decisions, not just actions. It's not enough to know that a refund was issued; you want to know which plan the agent was executing, which tool calls led to this point, and what context informed the decision.
&lt;/li&gt;
&lt;li&gt;Delegation chains should be explicit, so a given API call is traceable back through &lt;code&gt;user → agent → sub-agent → tool → resource&lt;/code&gt;, with each hop recorded.
&lt;/li&gt;
&lt;li&gt;Revocation has to be fast and precise. If an agent is misbehaving, you need to revoke its standing grants, invalidate in-flight tokens, and halt further tool calls, without taking down the entire system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auth0's platform provides this infrastructure by centralizing token issuance, which means every token vended to an agent passes through a single control plane. Revoking a connection in &lt;a href="https://auth0.com/blog/auth0-token-vault-secure-token-exchange-for-ai-agents/" rel="noopener noreferrer"&gt;Token Vault&lt;/a&gt; invalidates future token exchanges immediately, and the audit logs reflect every issuance and use.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Permission Model for Agents
&lt;/h2&gt;

&lt;p&gt;AI agents require access control models designed for non-deterministic, autonomous workflows. Permissions inherited wholesale from a human user, or from a static service account, are not fit for purpose. The agent will use them in ways neither the user nor the developer anticipated, and the existing access layer will not notice.&lt;/p&gt;

&lt;p&gt;The three patterns that form the foundation of production-ready agent architectures are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;capability-scoped permissions that encode specific actions and limits rather than broad resource verbs&lt;/li&gt;
&lt;li&gt;task-scoped credentials that are short-lived and tied to specific execution plans rather than persistent grants&lt;/li&gt;
&lt;li&gt;layered enforcement that separates identity, authorization, and execution into distinct components &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these patterns contain the blast radius of agent mistakes without removing the autonomy that makes agents useful.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>agents</category>
      <category>security</category>
    </item>
    <item>
      <title>Closing the Audit Gap in Human-to-Agent Delegation</title>
      <dc:creator>Vivek Maskara</dc:creator>
      <pubDate>Fri, 09 Oct 2026 18:22:49 +0000</pubDate>
      <link>https://dev.to/auth0/closing-the-audit-gap-in-human-to-agent-delegation-48il</link>
      <guid>https://dev.to/auth0/closing-the-audit-gap-in-human-to-agent-delegation-48il</guid>
      <description>&lt;p&gt;When AI agents act on behalf of users, traditional audit logs often don't tell the full story of who actually performed an action. In this article, I explore how to close that gap using Auth0's monitoring, Token Vault, and CIBA approval flows. I also cover how to track delegated actions, manage tokens securely, and maintain a clear audit trail across agent workflows.&lt;/p&gt;

&lt;p&gt;AI agents create a new kind of audit gap. When agents act on behalf of a user, they may use tokens issued under that user’s identity, even though the action is executed by the agent. When a high-privilege action shows up in your logs, you need to know whether it came from a human developer, a scheduled backend job, or an AI agent acting with delegated authority. If you only look at traditional audit logs, it can appear that user X did Y, even when the action was carried out indirectly by an agent.&lt;/p&gt;

&lt;p&gt;Auditing and traceability get harder as agents operate across identity providers, SaaS platforms, and internal services. A single user request can turn into a &lt;a href="https://auth0.com/blog/the-many-faces-of-oauth2-token-exchange" rel="noopener noreferrer"&gt;chain of delegated&lt;/a&gt; actions where intent, authorization, and execution happen in different places. Closing the audit gap means capturing not only what happened, but also who initiated it, how delegation occurred, what permission was used, and what approvals happened along the way.&lt;/p&gt;

&lt;p&gt;In this article, you will learn how to use Auth0’s monitoring and logs to capture agent-related activity, how to interpret event types in delegated authorization flows, and how to assemble a practical workflow using Auth0’s built-in monitoring features, logs, and log streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Audit Gap in AI Agent Systems
&lt;/h2&gt;

&lt;p&gt;Identity models are typically built around a simple flow. A human authenticates, their application receives a token, and calls an API. Even service-to-service calls are usually modeled as explicit machine identities with clear boundaries and predictable lifecycles.&lt;/p&gt;

&lt;p&gt;Agentic systems use a similar authentication and authorization model, but the operational context around that delegation changes. You still have a human identity, but you also have an agent that can act for that human, often across multiple external providers. The delegation chain can include a UI action, a background job, a token exchange, and a series of API calls. Many logging pipelines do not represent those handoffs clearly.&lt;/p&gt;

&lt;p&gt;The core problem is indirect execution. Attribution gets harder because the actor that initiated intent (the human) and the actor that executed the call (the agent) may not share a runtime, IP, device, or even the same system. For example, when a user asks an agent to pull the latest pipeline from Salesforce and summarize it. The agent would use the stored OAuth 2.0 tokens to call Salesforce APIs. Without explicit delegation context, the audit trail can make the action look like normal user access, even when it was performed by an agent acting on the user’s behalf. For agents, that distinction matters because they may act on a user’s authorization across background workflows, not just in response to a direct click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.auth0.com/docs/deploy-monitor/monitor" rel="noopener noreferrer"&gt;Auth0 monitoring&lt;/a&gt; and tenant logs help by providing a consistent identity-layer record of authentication, authorization, and delegation-related events. You can correlate token issuance, exchanges, approvals, failures, and downstream activity. Auth0 helps ensure the delegation layer emits enough signals to reconstruct who authorized what, how the agent obtained the ability to do it, and which workflow used that access.&lt;/p&gt;

&lt;p&gt;For example, if an agent accesses Google Drive on behalf of a user, you should be able to correlate the user’s original connection to Google, the scopes granted, the token access or refresh event, the agent run ID, and the downstream file access. If the same agent later accesses Salesforce, you should be able to perform the same reconstruction across a different provider. This correlation is what turns disconnected logs into an audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Agent-Driven API Access with Token Vault
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://auth0.com/docs/secure/tokens/token-vault" rel="noopener noreferrer"&gt;Token Vault&lt;/a&gt; is designed for applications (and agents) that call &lt;a href="https://auth0.com/blog/connect-oauth2-service-to-ai-agents-with-auth0-token-vault" rel="noopener noreferrer"&gt;external providers like Google or Salesforce&lt;/a&gt; on behalf of users. Instead of storing long-lived third‑party refresh tokens in your agent’s database, Token Vault stores provider access and refresh tokens and brokers access to them. Your agent retrieves what it needs at run time to call external APIs under the user’s delegated authorization. This matters because external provider tokens are often the bridge between user intent and agent execution. If every team stores provider refresh tokens in its own service, you quickly lose centralized visibility. You also increase the number of places where sensitive credentials can leak, be misused, or be refreshed without a clear audit trail. Token Vault reduces that sprawl by making token storage and access part of the identity layer, as shown in the diagram 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjbuxlz2u4kw5j3ueaw6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjbuxlz2u4kw5j3ueaw6.png" alt="Token Vault reduces that sprawl by making token storage and access part of the identity layer flow diagram" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user first authorizes access to an external provider, Auth0 stores those provider tokens in Token Vault. When the agent later needs to act on the user’s behalf, it retrieves the required token through Auth0. It then uses that token to call the provider API without storing long-lived credentials in the agent backend.&lt;/p&gt;

&lt;p&gt;A token vault enables &lt;a href="https://auth0.com/blog/identity-chained-authorization-auth0-token-vault" rel="noopener noreferrer"&gt;end-to-end audit trails&lt;/a&gt; and traceability. Some of its advantages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduces token sprawl:&lt;/strong&gt; Centralized storage narrows the number of places where token issuance, refresh, and retrieval can occur.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helps maintain user context:&lt;/strong&gt; The agent can act “as the user” without shared service credentials that erase attribution. This does not fully solve the audit gap, but it keeps delegation anchored to a user grant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports asynchronous work without weakening controls:&lt;/strong&gt; Users authorize once. Agents can operate later. You can still enforce scope and lifecycle controls centrally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improves investigation quality:&lt;/strong&gt; During an incident, investigators can ask which connected account was used, which scopes were available, when the token was accessed, and which agent workflow was running at the time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a scenario where you are building an agent that can draft a customer follow-up email by pulling context from Google Drive and Salesforce. The user authorizes the required providers once. Later, the agent runs in the background, retrieves the relevant access tokens, reads a document from Google Drive, checks customer details in Salesforce, and drafts a response. With Token Vault in the middle, you will see &lt;a href="https://auth0.com/docs/deploy-monitor/logs/view-log-events" rel="noopener noreferrer"&gt;identity-layer logs&lt;/a&gt; worth monitoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the user first connects Google (initial consent and token storage).&lt;/li&gt;
&lt;li&gt;When an access token is retrieved for an agent-run task.&lt;/li&gt;
&lt;li&gt;When a refresh occurs because the access token has expired.&lt;/li&gt;
&lt;li&gt;When scopes change (for example, the agent now needs additional permissions).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an agent claims it needed Google Drive access, you should see a corresponding event around the same time. If you see repeated refreshes for Google OAuth tokens at unusual hours, that can indicate a loop, an overly broad integration, or a compromised workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Approvals in Async Authorization Flows with CIBA
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1%5F0.html" rel="noopener noreferrer"&gt;CIBA (Client-Initiated Backchannel Authentication)&lt;/a&gt; is a decoupled OAuth/OIDC flow. It lets a client application initiate an authentication or authorization request without the user being present in the same browser session. This fits agent workflows where the agent wants to do something sensitive, but you still want a &lt;a href="https://auth0.com/blog/do-not-let-your-agent-go-rogue" rel="noopener noreferrer"&gt;human approval step&lt;/a&gt; before it proceeds. The following diagram shows the CIBA flow in detail:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0vgle9q43q0321pkok9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0vgle9q43q0321pkok9.png" alt="CIBA flow diagram" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown above, initially, the agent backend sends a backchannel authorization request to Auth0 with the requested scope, binding, and user hint. Auth0 then prompts the user for approval. If approved, the agent receives a token and calls the protected API, while denied or expired requests return an error instead.&lt;/p&gt;

&lt;p&gt;CIBA creates explicit, loggable checkpoints in the delegation chain. In a typical “agent just runs” model, you may only see downstream API calls and a token refresh. With CIBA, you can monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When an agent initiated an authorization request:&lt;/strong&gt; This is an “agent intent” signal that the agent attempted to obtain permission to proceed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether the user approved, denied, or the flow failed/expired:&lt;/strong&gt; This becomes a clean audit artifact for compliance and incident response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CIBA fits best when you want agent productivity without silent escalation. For example, if your agent wants to download a customer list from Salesforce and attach it to an internal ticket. Instead of letting the agent use existing tokens silently, you require a CIBA approval with the “export customers” scope.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent backend initiates the request.&lt;/li&gt;
&lt;li&gt;The user receives a push notification on their phone.&lt;/li&gt;
&lt;li&gt;The agent only receives a token that allows the export after approval.&lt;/li&gt;
&lt;li&gt;If the request is denied or times out, the agent can fall back to asking the user for a different approach.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To close the audit gap, treat these CIBA events as first-class monitoring signals. In Auth0 logs, look for the sequence of &lt;strong&gt;request initiated &amp;gt; pending &amp;gt; approved/denied/expired&lt;/strong&gt;. Correlate that sequence to token issuance and API activity. Attach a stable correlation ID (agent run ID, job ID, or workflow ID) to the CIBA initiation. Propagate it through internal logs so you can tie “approval granted” to “action executed”. Additionally, you should also decide which actions require CIBA approval. Not every agent action needs a human checkpoint. Reading a calendar to schedule a meeting may be low risk if the user has already granted the right scope. Exporting customer data, sending money, modifying permissions, deleting files, or publishing content may require explicit approval. The goal is to apply approvals where they create meaningful accountability, not to slow every workflow down.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Make Delegated Actions Traceable
&lt;/h2&gt;

&lt;p&gt;When working with AI agents, you can do the following to help make delegated actions traceable across identity, application, and provider layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capture identity-layer events in Auth0:&lt;/strong&gt; Use Auth0 tenant logs to track authentication, authorization, and delegation-related activity via &lt;a href="https://auth0.com/docs/deploy-monitor/logs" rel="noopener noreferrer"&gt;logs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralize external provider tokens:&lt;/strong&gt; Use Token Vault so token storage, refresh, and retrieval are observable in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add explicit approvals for sensitive actions:&lt;/strong&gt; Use CIBA when you need a clear “approved/denied” checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream logs to your analysis platform:&lt;/strong&gt; Use &lt;a href="https://auth0.com/docs/customize/log-streams" rel="noopener noreferrer"&gt;log streams&lt;/a&gt; to forward Auth0 events to your &lt;a href="https://www.okta.com/blog/industry-insights/using-siem-and-identity-to-protect-against-data-breaches/" rel="noopener noreferrer"&gt;SIEM&lt;/a&gt; (Security Information and Event Management) or data platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagate correlation IDs end-to-end:&lt;/strong&gt; Use a stable ID (agent run ID, job ID, workflow ID) across Auth0 initiation, backend execution, and provider API calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Auth0 Monitoring Closes the Agent Audit Gap
&lt;/h2&gt;

&lt;p&gt;AI agents create a traceability problem when delegated actions are logged as simple user activity. In third-party integrations, and especially for &lt;a href="https://auth0.com/blog/how-to-fix-five-critical-ai-agent-security-risks" rel="noopener noreferrer"&gt;agentic clients&lt;/a&gt;, you need to preserve the difference between the human who authorized access and the system that used it. During an incident, that context helps you answer who authorized the action, which agent or workflow executed it, how access was obtained, and whether a human approved it.&lt;/p&gt;

&lt;p&gt;Auth0 provides building blocks to monitor delegated flows at the identity layer. Use monitoring and tenant logs to observe authentication and authorization events. Use Token Vault to centralize external provider tokens used by agents. Use CIBA to add explicit human approvals to asynchronous workflows. With log streams, you can push those events into your data platform and correlate them with application traces and downstream provider audit logs.&lt;/p&gt;

&lt;p&gt;Build your agent features so token retrieval, refresh, and approval checkpoints are observable and tied together with correlation IDs. That is the most reliable way to close the audit gap without slowing down developers or agents. Explore &lt;a href="https://auth0.com/docs/deploy-monitor/monitor" rel="noopener noreferrer"&gt;Auth0 monitoring&lt;/a&gt; and &lt;a href="https://auth0.com/docs/deploy-monitor/logs" rel="noopener noreferrer"&gt;logs&lt;/a&gt; today to enhance your visibility and control over agent-driven activities.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>authentication</category>
      <category>agents</category>
    </item>
    <item>
      <title>How to Set Up an Auth0 Custom Domain?</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:56:58 +0000</pubDate>
      <link>https://dev.to/auth0/how-to-set-up-an-auth0-custom-domain-1mjd</link>
      <guid>https://dev.to/auth0/how-to-set-up-an-auth0-custom-domain-1mjd</guid>
      <description>&lt;p&gt;Replace your-tenant.your-region.auth0.com with your own branded login domain! In this tutorial, learn how to configure a custom domain in Auth0 using Namecheap and verify propagation using the terminal. &lt;/p&gt;

&lt;p&gt;We will also cover mandatory setup requirements—like credit card authorization—and show you how to update your application environment variables (featuring a Ruby on Rails example).  &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Key prerequisites for Auth0 custom domains (Billing setup &amp;amp; session implications)
&lt;/li&gt;
&lt;li&gt;Setting up CNAME records in Namecheap
&lt;/li&gt;
&lt;li&gt;Verifying DNS propagation directly from your terminal using dig
&lt;/li&gt;
&lt;li&gt;Updating app configuration to route authentication traffic through your custom URL
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources &amp;amp; Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full blog post - &lt;a href="https://auth0.com/blog/custom-domains-complete-guide/" rel="noopener noreferrer"&gt;Setting Up Auth0 Custom Domains: A Complete Guide to DNS and SDK Configuration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/docs/customize/custom-domains" rel="noopener noreferrer"&gt;Auth0 Custom Domain Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Integrating Claude Code with Auth0 APIs</title>
      <dc:creator>Manish Hatwalne</dc:creator>
      <pubDate>Mon, 05 Oct 2026 05:35:00 +0000</pubDate>
      <link>https://dev.to/auth0/integrating-claude-code-with-auth0-apis-8c9</link>
      <guid>https://dev.to/auth0/integrating-claude-code-with-auth0-apis-8c9</guid>
      <description>&lt;p&gt;Did you know that an AI agent with live access to your Auth0 tenant can catch bugs that &lt;em&gt;no static analysis&lt;/em&gt; ever would? I handed Claude Code a deliberately naive Flask app and the Auth0 MCP Server, and it discovered that my tenant was already rotating signing keys while my app only trusted the first one, so perfectly valid tokens were failing right now. It even stopped to push back on one of my own imprecise prompts instead of blindly obeying. &lt;/p&gt;

&lt;p&gt;This article walks through the full setup, plus the guardrails that made me comfortable giving an agent that much access: least-privilege scopes, device auth instead of static secrets, and a &lt;code&gt;CLAUDE.md&lt;/code&gt; that encodes your constraints.&lt;/p&gt;




&lt;p&gt;Authentication and authorization are a core part of nearly every web application, and it is also one of the areas that changes most often. Developers regularly update token expiry settings, callback URLs, or API permissions for new features. The changes are usually small, but the workflow around them is not. You end up bouncing between your editor, the Auth0 dashboard, and the docs just to push through a minor update. Increasingly, that editor is a coding agent like Claude Code, which raises the question: what if you could handle those updates without ever leaving Claude CLI?&lt;/p&gt;
&lt;p&gt;Claude Code is a terminal-based AI agent (also available as a VS code extension) that you can pair with the Auth0 MCP Server, which exposes Auth0's Management API as native third-party tools. This pairing gives you an assistant that can scaffold, refactor, and update authentication integrations with full context across your codebase and Auth0 tenant. But giving an AI agent access to sensitive authentication infrastructure raises a fair question: &lt;em&gt;"How do you do that without opening up new security risks?"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this tutorial, you will set up Claude Code with the Auth0 MCP Server, create a scoped machine-to-machine credential flow and set up security guardrails that let an AI agent interact safely with your authentication layer.&lt;/p&gt;
&lt;h2 id="How-Claude-Code-and-Auth0-MCP-Work-Together"&gt;How Claude Code and Auth0 MCP Work Together&lt;/h2&gt;
&lt;p&gt;Based on your goal, &lt;a href="https://claude.com/product/claude-code" rel="noreferrer noopener"&gt;Claude Code&lt;/a&gt; works through multiple steps (like reading/writing code, running shell commands etc.) autonomously, and asks for confirmation at decision points that warrant human review.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://auth0.com/docs/get-started/auth0-mcp-server" rel="noreferrer noopener"&gt;The Auth0 MCP Server&lt;/a&gt; implements the &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noreferrer noopener"&gt;Model Context Protocol (MCP)&lt;/a&gt; to expose &lt;a href="https://auth0.com/docs/api/management/v2" rel="noreferrer noopener"&gt;Auth0's Management API&lt;/a&gt; as a set of tools that any compatible AI agent can invoke directly. Instead of constructing raw HTTP requests or relying on potentially stale training data about APIs, Claude Code calls well-defined tools with typed inputs and outputs. The MCP Server handles calls to the Management API, and the agent operates strictly within the permissions you grant. If you run it in the &lt;code&gt;read-only&lt;/code&gt; mode, the coding agent will not be able to make any changes in your Auth0 tenant.&lt;/p&gt;
&lt;p&gt;Together, they bring authentication workflows closer to your code. Claude Code can understand both your application and your Auth0 tenant, then work across them in a single session.&lt;/p&gt;
&lt;h2 id="Setting-Up-the-Auth0-MCP-Server-with-Scoped-Access"&gt;Setting Up the Auth0 MCP Server with Scoped Access&lt;/h2&gt;
&lt;p&gt;In addition to &lt;a href="https://auth0.com/docs/get-started/auth0-mcp-server/getting-started-with-auth0-mcp-server#installation-and-configuration" rel="noreferrer noopener"&gt;Claude Desktop, Cursor, and Windsurf&lt;/a&gt;, Auth0 MCP server also works with Claude seamlessly.&lt;/p&gt;
&lt;h3&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;You will need the following to complete this tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en" rel="noreferrer noopener"&gt;Node.js&lt;/a&gt; v18 or higher (&lt;code&gt;node --version&lt;/code&gt; to check)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://claude.com/product/claude-code" rel="noreferrer noopener"&gt;Claude Code CLI&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;An active Auth0 account with &lt;strong&gt;administrative permissions&lt;/strong&gt; on the target tenant&lt;/li&gt;
&lt;li&gt;Your tenant domain handy (for example, &lt;code&gt;dev-xxxx.us.auth0.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Demo code from this &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/tree/suboptimal-implementation" rel="noreferrer noopener"&gt;GitHub repo&lt;/a&gt; for your reference (optional).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Install and initialize&lt;/h3&gt;
&lt;p&gt;Create a dedicated working directory:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; Claude-Code-With-Auth0-MCP &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; Claude-Code-With-Auth0-MCP  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Then initialize the Auth0 MCP Server:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx @auth0/auth0-mcp-server init  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;While this tutorial selected all MCP scopes for the demo, you should &lt;em&gt;only&lt;/em&gt; use the scopes you actually need. After install and initialization, it kicks off a browser-based Auth0 authentication flow as shown below:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F1x6foO3Qs6YbnbD9MZfq5d%2Ffd5d15db1262f37fd5016a4f644ba7cf%2FAuth0_MCP_Installation.png" alt="Auth0 MCP Installation" width="799" height="601"&gt;&lt;p&gt;Confirm the code displayed in the browser is same as the one shown in your terminal:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F75Ox43lNQCSY2ATNXMN3Le%2F6eb4a2aec80651cb43a5fdaa57fb2c49%2FAuth0_Device_Authorization.png" alt="Auth0 Device Authorization" width="435" height="571"&gt;&lt;p&gt;Grant requested permissions:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F2x5eKXFGHQRQfBvEoIYO7i%2Fecaf791b34c20583ff4f972e52cf3c58%2FAuth0MCPPermissions.png" alt="Auth0 MCP Permissions" width="426" height="912"&gt;&lt;p&gt;Once authorized, credentials are stored securely in your OS keychain, never in plain text or project files.&lt;/p&gt;
&lt;h3&gt;Why use device authorization flow instead of static secrets&lt;/h3&gt;
&lt;p&gt;The Auth0 MCP Server uses the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" rel="noreferrer noopener"&gt;OAuth 2.0 Device Authorization Flow&lt;/a&gt; (RFC &lt;a href="https://www.rfc-editor.org/info/rfc8628/" rel="noreferrer noopener"&gt;8628&lt;/a&gt;) rather than a static API key or client secret. Static secrets have to live somewhere (usually a config or &lt;code&gt;.env&lt;/code&gt; file), which risks exposing them in source control. OAuth access tokens sidestep this: they represent a delegated authorization, carry restricted scopes, and expire automatically. The Device Authorization Flow adds a human login step to token issuance, giving you an audit trail tied to an interactive session and instant revocation from the Auth0 dashboard. The MCP server stores the resulting token in your OS keychain, so it never touches source control.&lt;/p&gt;
&lt;p&gt;The one trade-off is that the flow requires a browser-based login step, so it is unsuitable for CI pipelines. For non-interactive access, a scoped M2M application with client credentials is more suitable.&lt;/p&gt;
&lt;h3&gt;Scoping the MCP server&lt;/h3&gt;
&lt;p&gt;The Auth0 MCP Server grants no scopes by default. You request them explicitly at init time using the &lt;code&gt;--scopes&lt;/code&gt; flag. For this tutorial demo, all scopes were selected during &lt;code&gt;init&lt;/code&gt;. For a more targeted setup, you can specify exactly what you need::&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Grant all read permissions  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;npx @auth0/auth0-mcp-server init --scopes &lt;/span&gt;&lt;span&gt;'read:*'&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;# Grant a targeted mix of permissions  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;npx @auth0/auth0-mcp-server init --scopes create:clients,update:actions'  
&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Scopes map directly to Management API operations. For example, &lt;code&gt;read:clients&lt;/code&gt; lets the agent call &lt;code&gt;auth0_get_application&lt;/code&gt;, while &lt;code&gt;create:actions&lt;/code&gt; lets it call &lt;code&gt;auth0_create_action&lt;/code&gt;. Some scopes carry significant implications: &lt;code&gt;update:actions&lt;/code&gt; can push custom code into production, and &lt;code&gt;read:logs&lt;/code&gt; exposes detailed user activity and authentication events. See the &lt;a href="https://auth0.com/docs/get-started/auth0-mcp-server/auth0-mcp-server-guides/understanding-scopes" rel="noreferrer noopener"&gt;scopes reference&lt;/a&gt; for more detail.&lt;/p&gt;
&lt;p&gt;For this tutorial, all scopes were granted during &lt;code&gt;init&lt;/code&gt; for the demo.  However, while coding your applications, you must follow the principle of least privilege and grant only what your workflow actually needs.&lt;/p&gt;
&lt;h3&gt;Claude Code and Auth0 integration&lt;/h3&gt;
&lt;p&gt;To integrate the Auth0 MCP Server with Claude Code, run the following command from within your &lt;code&gt;Claude-Code-With-Auth0-MCP&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;$ claude mcp &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; auth0 -- npx -y @auth0/auth0-mcp-server run  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;You should see output similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Added stdio MCP server auth0 with command: npx -y @auth0/auth0-mcp-server run to &lt;/span&gt;&lt;span&gt;local&lt;/span&gt;&lt;span&gt; config  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;File modified: /home/&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;user-name&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;/.claude.json &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;project: /path-to/Claude-Code-With-Auth0-MCP&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;b&gt;Note: The --scope local/project/user, which is a &lt;a href="https://code.claude.com/docs/en/mcp-quickstart#find-your-configuration-on-disk" rel="noreferrer noopener"&gt;Claude Code flag&lt;/a&gt; controls where the MCP server config is visible: the current project only, shared across your team, or all your projects. For this tutorial, local (the default) is the right choice.&lt;/b&gt;&lt;br&gt;&lt;p&gt;This adds the Auth0 MCP Server configuration block to your &lt;code&gt;~/.claude.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;"mcpServers"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;"auth0"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"type"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"stdio"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"command"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"npx"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"args"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"-y"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"@auth0/auth0-mcp-server"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"run"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"env"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can create a &lt;a href="https://auth0.com/docs/get-started/auth0-mcp-server/getting-started-with-auth0-mcp-server#other-mcp-clients" rel="noreferrer noopener"&gt;configuration JSON&lt;/a&gt; manually and register it with &lt;a rel="noreferrer noopener"&gt;&lt;code&gt;claude mcp add-json&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Credentials are &lt;em&gt;not&lt;/em&gt; stored in this JSON. The MCP server reads your token from the OS keychain at startup. To enable debug logging, add &lt;code&gt;"DEBUG": "auth0-mcp"&lt;/code&gt; to the &lt;code&gt;env&lt;/code&gt; block.&lt;/p&gt;
&lt;h3&gt;Verify your integration&lt;/h3&gt;
&lt;p&gt;From your &lt;code&gt;Claude-Code-With-Auth0-MCP&lt;/code&gt; directory, launch &lt;code&gt;claude&lt;/code&gt; CLI and run &lt;code&gt;/mcp&lt;/code&gt; to confirm &lt;code&gt;auth0&lt;/code&gt; is listed with a tool count. Then try this read-only prompt:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Show me all applications in my Auth0 tenant.  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;On the first Auth0 tool call, Claude Code will ask for your permission. You can approve it once or allow it permanently for the project.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F3SWZ1UxnLFMS3QRdQIkHaP%2Fb3d539526fbc671f4b8e424ea5273f46%2FClaude_Code_permission_for_using_Auth0_tool.png" alt="Claude Code permission for using Auth0 tool" width="799" height="601"&gt;&lt;h2 id="Giving-Claude-Code-Project-Context-with-CLAUDE-md"&gt;Giving Claude Code Project Context with CLAUDE.md&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is a markdown file at your project root that Claude Code reads at the start of every session. It tells the agent what kind of project it is working in, what constraints apply, and where the sensitive files are. Without it, Claude Code might make technically correct changes that are contextually wrong, like updating a callback URL shared across environments or granting a broader scope than your security policy allows.&lt;/p&gt;
&lt;p&gt;A good &lt;code&gt;CLAUDE.md&lt;/code&gt; for an Auth0 project should cover:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;##&lt;/span&gt;&lt;span&gt; Auth0 Context  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; Active tenant: dev-xxxx.us.auth0.com (development only)  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; Auth-sensitive files: src/auth/config.ts, .env.local  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; Existing applications: one SPA (authorization code flow with PKCE), one M2M (client credentials)  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; Permitted scopes: read:users, update:users  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; Hard constraints: never modify production callback URLs without explicit confirmation  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;With this in place, Claude Code operates with the same guardrails a human teammate would naturally apply. See the demo project's &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/master/CLAUDE.md" rel="noreferrer noopener"&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/a&gt; for a practical example.&lt;/p&gt;
&lt;h2 id="Refactoring-an-Auth0-Integration-from-Claude-CLI"&gt;Refactoring an Auth0 Integration from Claude CLI&lt;/h2&gt;
&lt;p&gt;To demonstrate this workflow in practice, this example uses a minimal &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation/app.py" rel="noreferrer noopener"&gt;Flask app&lt;/a&gt; with a single protected endpoint (&lt;code&gt;/api/protected&lt;/code&gt;). It validates Auth0 JWT tokens using &lt;code&gt;python-jose&lt;/code&gt;. It works, but it has several problems common in real codebases: JWKS fetched once at startup and never refreshed, no &lt;code&gt;kid&lt;/code&gt; matching when selecting a signing key, a bare &lt;code&gt;except&lt;/code&gt; that swallows all validation errors identically, and Auth0 config stored as module-level globals.&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# JWKS fetched once at startup — never refreshed  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;JWKS &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f"https://&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/.well-known/jwks.json"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate_token&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# No kid matching — always uses the first key regardless  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# of which key signed the token  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    rsa_key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"kty"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; JWKS&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"keys"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"kty"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"kid"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; JWKS&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"keys"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"kid"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"use"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; JWKS&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"keys"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"use"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"n"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;   JWKS&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"keys"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"n"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"e"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;   JWKS&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"keys"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"e"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# No clock skew tolerance, no error differentiation  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; jwt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        token&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        rsa_key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        algorithms&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;ALGORITHMS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        audience&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;API_AUDIENCE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        issuer&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;f"https://&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;AUTH0_DOMAIN&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/"&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;You can view this &lt;a href="[https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation](https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation)" rel="noreferrer noopener"&gt;full (suboptimal/naive) implementation on GitHub&lt;/a&gt; that we will fix and improve iteratively with Claude Code and Auth0 MCP.&lt;/p&gt;
&lt;h3&gt;Auth0 setup and protected API&lt;/h3&gt;
&lt;p&gt;On the Auth0 side, you need one &lt;a href="https://auth0.com/docs/get-started/auth0-overview/set-up-apis" rel="noreferrer noopener"&gt;API&lt;/a&gt; (resource server) registered with an identifier (&lt;code&gt;https://mh-test-api.example.com&lt;/code&gt;. This identifier URI should be unique in your app, but it does not need to resolve).&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F6Ud6f4PCL7ebeZL3ihfNAE%2Fa087d73acde6664634a5cc9955314e6d%2FAuth0Claude_Demo_API.png" alt="Auth0: Claude Demo API" width="800" height="424"&gt;&lt;p&gt;When you register an API, Auth0 automatically creates a test M2M application named &lt;strong&gt;"[Your API Name] (Test Application),"&lt;/strong&gt; already authorized against your API. You will find it under Applications.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F4EawU1pRgZwxZS9z2UCwhj%2F2f0e0cfd8745fb2dd595f6a4ee88bb90%2FAuth0M2M_Test_Application.png" alt="Auth0: M2M Test Application" width="799" height="452"&gt;&lt;p&gt;That is the minimum setup needed for this demo for Claude Code to audit and provision against something real.&lt;/p&gt;
&lt;h4&gt;Obtaining an Auth0 token&lt;/h4&gt;
&lt;p&gt;Use this cURL command to obtain your Auth0 token:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; --request POST   
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  --url &lt;/span&gt;&lt;span&gt;"https://YOUR_AUTH0_DOMAIN.us.auth0.com/oauth/token"&lt;/span&gt;&lt;span&gt;   
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  --header &lt;/span&gt;&lt;span&gt;'content-type: application/json'&lt;/span&gt;&lt;span&gt;   
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  --data &lt;/span&gt;&lt;span&gt;'{  
&lt;/span&gt;&lt;/span&gt;&lt;span class="token"&gt;    "client_id":"&amp;lt;client-ID&amp;gt;",  
&lt;/span&gt;&lt;span class="token"&gt;    "client_secret":"&amp;lt;client-secret&amp;gt;",  
&lt;/span&gt;&lt;span class="token"&gt;    "audience":"https://mh-test-api.example.com",  
&lt;/span&gt;&lt;span class="token"&gt;    "scope":"",  
&lt;/span&gt;&lt;span class="token"&gt;    "grant_type":"client_credentials"  
&lt;/span&gt;&lt;span&gt;&lt;span&gt;  }'&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;"access_token"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;"eyJhbGc..."&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;"expires_in"&lt;/span&gt;&lt;span&gt;:86400,&lt;/span&gt;&lt;span&gt;"token_type"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;"Bearer"&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;h4&gt;Calling your protected API&lt;/h4&gt;
&lt;p&gt;Download the &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/tree/suboptimal-implementation" rel="noreferrer noopener"&gt;starter code&lt;/a&gt; into your &lt;code&gt;Claude-Code-With-Auth0-MCP&lt;/code&gt; directory, create a virtual environment (&lt;code&gt;python3 -m venv ./.venv&lt;/code&gt;), populate &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/suboptimal-implementation/.env.example" rel="noreferrer noopener"&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/a&gt; file, install dependencies (&lt;code&gt;pip install -r requirements.txt&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Run the Flask server:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python app.py  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Then call the protected endpoint with your Auth0 token:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"eyJhbGc..."&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -H &lt;/span&gt;&lt;span&gt;"Authorization: Bearer &lt;/span&gt;&lt;span&gt;$TOKEN&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt; http://localhost:5000/api/protected  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;You should see a response like this:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;"message"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"Access granted to a protected resource"&lt;/span&gt;&lt;span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;"user"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"UFIv...@clients"&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;h3&gt;Running the audit prompt&lt;/h3&gt;
&lt;p&gt;The naive implementation appears to work at this point, but we can double-check it with Claude Code to ensure there are no errors and nothing crucial is missing.&lt;/p&gt;
&lt;b&gt;Note: Some prompts in the steps below are intentionally imprecise or technically inaccurate. The goal is to test whether Claude Code (backed by live Auth0 tenant access via the MCP Server), can identify the correct implementation approach even when the instructions do not perfectly describe it.&lt;/b&gt;&lt;br&gt;&lt;p&gt;Inside the &lt;code&gt;claude&lt;/code&gt; session connected to Auth0 MCP, this prompt was run:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Claude "Audit" Prompt:  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-----------------------&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Review app.py and audit the Auth0 JWT validation setup. Cross-reference it against 
&lt;/span&gt;&lt;span&gt;our actual tenant configuration using the Auth0 MCP tools. Identify any mismatches 
&lt;/span&gt;&lt;span&gt;between what the tenant is configured to issue and what the app is validating 
&lt;/span&gt;&lt;span&gt;against, and list all security or reliability problems you find. Do not make any 
&lt;/span&gt;&lt;span&gt;changes yet.  
&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Claude Code reviewed the codebase, asked for relevant permissions (human in the loop) and showed this:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F5lw1ShGPQBIxEoulbjiGUy%2F1320c30a0a39ed55284b7410b0fa67c3%2FClaude_Code_Audit.png" alt="Claude Code Audit" width="799" height="648"&gt;&lt;p&gt;Using the Auth0 MCP Server, Claude Code pulled live tenant data and cross-referenced it against &lt;code&gt;app.py&lt;/code&gt; directly for this audit. The most critical finding would never surface in static analysis: the tenant is currently publishing two rotating RS256 signing keys, while the app validates every token against only the first one. A linter can flag the missing &lt;code&gt;kid&lt;/code&gt; matching logic, but a live &lt;code&gt;auth0_list_applications&lt;/code&gt; call reveals that key rotation is already in progress and valid tokens are failing right now. The remaining findings cover robustness and hardening gaps, but this one mismatch makes the case for Auth0 MCP integration clearly: live tenant access surfaces the subtle bugs that static code review would miss.&lt;/p&gt;
&lt;p&gt;Here is Claude Code audit summary:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Claude "Audit" Response:  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-------------------------&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;※ recap: Audited app.py's Auth0 JWT validation against the live tenant and found 
&lt;/span&gt;&lt;span&gt;the key bug: the app validates every token against only the first JWKS key while
&lt;/span&gt;&lt;span&gt; the tenant publishes two, plus reliability issues. No changes made yet; next 
&lt;/span&gt;&lt;span&gt;action is drafting fixes if you want them. (disable recaps in /config)  
&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Importantly, Claude Code made no changes during this step &lt;em&gt;as instructed&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Running the provision prompt&lt;/h3&gt;
&lt;p&gt;Once the audit was complete, the provision prompt asked Claude CLI to create a new M2M application (&lt;code&gt;"Claude-Auth0 MCP API Client"&lt;/code&gt;) and a &lt;code&gt;read:data&lt;/code&gt; scoped client grant in a single instruction:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Claude "Provision" Prompt:  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;---------------------------&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Create a new M2M application in our Auth0 tenant called "Claude-Auth0 MCP 
&lt;/span&gt;&lt;span&gt;API Client". Set the token lifetime to 3600 seconds. Then create a client 
&lt;/span&gt;&lt;span&gt;grant authorizing it against our API audience (https://mh-test-api.example.com) 
&lt;/span&gt;&lt;span&gt;&lt;span&gt;with &lt;/span&gt;&lt;span&gt;`read:data`&lt;/span&gt;&lt;span&gt; scope only. Use the credentials returned to update 
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;the &lt;/span&gt;&lt;span&gt;`.env`&lt;/span&gt;&lt;span&gt; file. Do not touch app.py yet.  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Before proceeding, Claude Code flagged a real Auth0 misconception worth noting: token lifetime is a resource server setting that applies to all clients of an API, not a per-application property. Rather than applying a silent global change, it surfaced this as a clarifying question with explicit options. That pushback, grounded in live tenant data, is exactly what makes Auth0 MCP integration useful.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F3WmoSTqdCjJMxpaCydjWeG%2F2d8599b3ce08089b4e124fdf19b46bd5%2FClaude_Provision_Clarification.png" alt="Claude Provision Clarification" width="799" height="648"&gt;&lt;p&gt;After confirming the token lifetime should remain unchanged and approving the MCP tool calls, Claude Code invoked &lt;code&gt;auth0_create_application&lt;/code&gt; and &lt;code&gt;auth0_create_client_grant&lt;/code&gt; in sequence, updated &lt;code&gt;.env&lt;/code&gt; with the returned credentials (client ID and secret), and left &lt;code&gt;app.py&lt;/code&gt; untouched as instructed.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F45i8XRnIABajSNC7h2CMbV%2Fa5457950f00cf27397b33b3a750d83ae%2FClaude_Provision_Completed.png" alt="Claude Provision Completed" width="799" height="648"&gt;&lt;p&gt;The Auth0 dashboard confirmed the new &lt;code&gt;Claude-Auth0 MCP API Client&lt;/code&gt; application was added:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F5JHJTZVlYdyWLfUO0ovxeZ%2F67fc2abc3de50fd6e4af6be8288617cb%2FAuth0Application_added_by_Claude.png" alt="Auth0: Application added by Claude" width="798" height="222"&gt;&lt;p&gt;And the API confirmed the &lt;code&gt;read:data&lt;/code&gt; permission was in place:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F5QmGrLdykX12JKLfoLyBaG%2F608d22d421a6d420733955870fc34798%2FAuth0API_permission_added_by_Claude.png" alt="Auth0: API permission added by Claude" width="799" height="355"&gt;&lt;p&gt;Here is how Claude Code summarized this session:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Claude "Provision" Response:  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;---------------------------&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;※ recap: Goal: secure the Auth0 JWT setup. Audited app.py 
&lt;/span&gt;&lt;span&gt;(key bug: ignores kid, breaks on rotation) and created the M2M app, 
&lt;/span&gt;&lt;span&gt;read:data scope, grant, plus .env credentials. Next action: fix app.py's 
&lt;/span&gt;&lt;span&gt;validation issues when you're ready. (disable recaps in /config)  
&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;h3&gt;Running the implementation prompt&lt;/h3&gt;
&lt;p&gt;This prompt gave Claude Code a single instruction to fix everything surfaced in the audit:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Claude "Refactor" Prompt:  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;---------------------------&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;Now refactor &lt;/span&gt;&lt;span&gt;`app.py`&lt;/span&gt;&lt;span&gt; to fix all the issues found in the audit. Use 
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;the credentials and tenant configuration we just provisioned: fetch 
&lt;/span&gt;&lt;span&gt;the JWKS URI, issuer, and audience dynamically from the tenant rather 
&lt;/span&gt;&lt;span&gt;than hardcoding them. Fix the kid matching, add proper error handling, and 
&lt;/span&gt;&lt;span&gt;make sure the app stays in sync with what the tenant is configured to issue.  
&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;With the audit findings and provisioned credentials already in context, Claude Code asked for confirmation as specified in &lt;code&gt;CLAUDE.md&lt;/code&gt;, then rewrote &lt;code&gt;app.py&lt;/code&gt; without needing to be walked through each fix individually.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2Fu5NwB5S2S4XEfvdyIg7g8%2Fa4344c519b89c2b712e717b3508a5ee2%2FClaude_App_Refactoring.png" alt="Claude App Refactoring" width="799" height="648"&gt;&lt;p&gt;The refactored code introduced an &lt;code&gt;OIDCProvider&lt;/code&gt; class that discovers the issuer and JWKS URI dynamically from the tenant's OIDC discovery document, caches the JWKS with a configurable TTL, and handles key rotation by retrying with a forced refresh on an unknown &lt;code&gt;kid&lt;/code&gt;. The minimal &lt;code&gt;except&lt;/code&gt; was replaced with typed exception handling that differentiates expired tokens, claim mismatches, and signature failures. Scope enforcement was added via a &lt;code&gt;require_scope&lt;/code&gt; decorator (see refactored code: &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/master/app.py" rel="noreferrer noopener"&gt;&lt;strong&gt;app.py&lt;/strong&gt;&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_signing_key&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; kid&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;"""Return the JWK matching ``kid`` (audit F1), refreshing if stale/rotated."""&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    stale &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_jwks &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;monotonic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_jwks_fetched_at &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_jwks_ttl&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; stale&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_refresh_jwks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_find_key&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;kid&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; key &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; stale&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# kid unknown against a cache we didn't just refresh: keys may have  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# rotated. Refresh once and retry before giving up.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_refresh_jwks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_find_key&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;kid&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; key
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate_token&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# ...  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; jwt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            token&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rsa_key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            algorithms&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;ALGORITHMS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            audience&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;API_AUDIENCE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            issuer&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;oidc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;issuer&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            options&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;"leeway"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; LEEWAY_SECONDS&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; ExpiredSignatureError &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; AuthError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"token_expired"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;401&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; e  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; JWTClaimsError &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; AuthError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"invalid_claims"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;401&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; e  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; JWTError &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; AuthError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"invalid_token"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;401&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; e
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;require_scope&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;required_scope&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;"""Authenticate the request and enforce a scope (audit F8)."""&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decorator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;@wraps&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;wrapper&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;kwargs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; validate_token&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_get_bearer_token&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            granted &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"scope"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;""&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; required_scope &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; granted&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; AuthError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"insufficient_scope"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            g&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;jwt_payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; fn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;kwargs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; wrapper  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; decorator  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;A separate &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/blob/master/get_token.py" rel="noreferrer noopener"&gt;&lt;code&gt;get_token.py&lt;/code&gt;&lt;/a&gt; utility was also produced for minting test tokens using the client credentials flow, correctly kept separate from &lt;code&gt;app.py&lt;/code&gt; since a resource server should not hold a client secret.&lt;/p&gt;
&lt;p&gt;This refactored demo can be run with:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;python get_token.py&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -H &lt;/span&gt;&lt;span&gt;"Authorization: Bearer &lt;/span&gt;&lt;span&gt;$TOKEN&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt; http://localhost:5000/api/protected  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Which produces the expected response:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;"message"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;"Access granted to a protected resource"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;"sub"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;"l5rDOCiobYJWeRiYEImEbA8FzFTHkqGc@clients"&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Throughout the refactor, Claude Code used the credentials it had just provisioned via the Auth0 MCP Server rather than asking for them again, keeping the codebase and tenant configuration in sync from the start.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F5DU9XuuqVzmPU4CLFtT8aj%2F5026f1fba7c2dcfe758320a83b9e9dc3%2FClaude_Refactoring_Completed.png" alt="Claude Refactoring Completed" width="799" height="648"&gt;&lt;p&gt;Here is how Claude Code summarized the session:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Claude "Refactor" Response:  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;-----------------------------&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;※ recap: Goal: harden the Auth0 JWT validation in this Flask app. 
&lt;/span&gt;&lt;span&gt;Done: audited app.py, provisioned the M2M client plus read:data 
&lt;/span&gt;&lt;span&gt;grant, and refactored app.py to fix all findings, verified live. 
&lt;/span&gt;&lt;span&gt;Next action: optionally migrate from python-jose to PyJWT, or 
&lt;/span&gt;&lt;span&gt;stop here. (disable recaps in /config)  
&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;b&gt;Pro Tip: For production use, consider migrating to PyJWT's PyJWKClient, which subsumes the OIDCProvider plumbing built here into a well-maintained abstraction. Claude Code flagged this during the refactor but correctly held off since it is a dependency change, not an in-file fix, and out of scope for this tutorial.&lt;/b&gt;&lt;br&gt;&lt;h3&gt;Observations: Claude Code with Auth0 MCP server&lt;/h3&gt;
&lt;p&gt;A few things stood out running this end-to-end. Audit findings were grounded in live tenant state, not static code analysis alone. The two-key rotation mismatch only surfaced because the Auth0 MCP Server pulled the actual JWKS from the tenant and compared it against what &lt;code&gt;app.py&lt;/code&gt; was doing. Without that, the finding would have been &lt;em&gt;"no &lt;code&gt;kid&lt;/code&gt; matching logic"&lt;/em&gt; rather than &lt;em&gt;"key rotation is already in progress and valid tokens are failing right now."&lt;/em&gt; That distinction matters in production.&lt;/p&gt;
&lt;p&gt;The token lifetime clarification was similarly useful. Seeing &lt;code&gt;token_lifetime: 86400&lt;/code&gt; as a tenant-wide setting let Claude Code correctly flag that changing it would affect all clients, not just the new M2M application. A code-only agent would have missed that entirely.&lt;/p&gt;
&lt;p&gt;The refactored code also went further than the prompt asked. The &lt;code&gt;OIDCProvider&lt;/code&gt;, scope enforcement, and separation of &lt;code&gt;get_token.py&lt;/code&gt; all emerged from the audit findings without individual prompting. Throughout, Claude Code asked for permission before invoking any Auth0 MCP tools, creating applications, or running bash commands.&lt;/p&gt;
&lt;p&gt;That last point addresses the question raised in the introduction. Scoped tools, a &lt;code&gt;CLAUDE.md&lt;/code&gt; that encodes your constraints, and consistent human-in-the-loop confirmation keep the agent working within a well-defined boundary. It has live tenant access, but only as far as you explicitly allow.&lt;/p&gt;
&lt;p&gt;You can compare the &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP/tree/suboptimal-implementation" rel="noreferrer noopener"&gt;&lt;strong&gt;naive implementation&lt;/strong&gt;&lt;/a&gt; with the &lt;a href="https://github.com/manishh/Claude-Code-With-Auth0-MCP" rel="noreferrer noopener"&gt;&lt;strong&gt;refactored implementation&lt;/strong&gt;&lt;/a&gt; directly.&lt;/p&gt;
&lt;h2 id="Security-Guardrails"&gt;Security Guardrails&lt;/h2&gt;
&lt;p&gt;Giving an AI agent access to your Auth0 tenant is similar to giving a new team member API access. The same principles apply:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep the MCP server scoped to development tenants only.&lt;/strong&gt; A practical way to enforce this is maintaining separate &lt;code&gt;CLAUDE.md&lt;/code&gt; files per environment, each pointing to the right tenant domain. That way there is no path from a local Claude Code session to production credentials by default. If a session does need to touch production, make it a deliberate, separate setup with explicit sign-off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat the device auth token like any other API credential.&lt;/strong&gt; After finishing a focused session, revoke it from the Auth0 dashboard rather than leaving it active. In fact, using a short access token lifetime helps reduce security risks. If your team uses Auth0 log streaming, pipe those logs to your monitoring tool of choice so MCP-driven Management API calls show up in the same audit trail as everything else.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client secrets belong in &lt;code&gt;.env&lt;/code&gt;, not in &lt;code&gt;CLAUDE.md&lt;/code&gt; or inline prompts.&lt;/strong&gt; It is easy to paste a secret into a prompt for convenience and forget it now lives in your shell history and potentially in Claude Code's session context. Let the MCP server handle authentication; your prompts should never carry credentials.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;Note: Agentic changes are still code changes. The refactored app.py in this tutorial went through manual review before being accepted. All auth-critical paths deserve the same PR process, regardless of who or what wrote them.&lt;/b&gt;&lt;br&gt;&lt;h2 id="Code-and-Auth--in-Sync"&gt;Code and Auth, in Sync&lt;/h2&gt;
&lt;p&gt;In this tutorial, you connected Claude Code to Auth0's Management API via the Auth0 MCP Server, built a scoped M2M credential flow, and refactored a suboptimal JWT validation setup to correctly handle key rotation, typed errors, and scope enforcement. The security principles applied throughout were practical: least-privilege scopes, device auth over static secrets, environment-specific configs, and keeping agentic changes in the normal PR review flow.&lt;/p&gt;
&lt;p&gt;The entire workflow, from auditing a live tenant to provisioning an application to rewriting the validation code, happened in a single terminal session. The Auth0 dashboard was only needed to verify the changes Claude Code had made. Which answers the question we started with. You can give an AI agent access to sensitive authentication infrastructure securely with Auth0 MCP: secure access, scoped tools, and human-in-the-loop confirmation keep the agent within well-defined boundaries, without opening up new security risks.&lt;/p&gt;
&lt;p&gt;Auth0's Management API exposes the full surface area of your tenant as programmable, auditable operations to any AI agent or automation you want to build on top of it. If you want to extend what you built here, the &lt;a href="https://auth0.com/docs/get-started/auth0-mcp-server/auth0-mcp-tools-reference" rel="noreferrer noopener"&gt;Auth0 MCP Server tools reference&lt;/a&gt; is a good next stop.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>security</category>
      <category>mcp</category>
      <category>claude</category>
    </item>
    <item>
      <title>Passkey Adoption Is Rising, but Is It Worth It Yet?</title>
      <dc:creator>Thinus Swart</dc:creator>
      <pubDate>Fri, 02 Oct 2026 08:54:16 +0000</pubDate>
      <link>https://dev.to/auth0/passkey-adoption-is-rising-but-is-it-worth-it-yet-46k2</link>
      <guid>https://dev.to/auth0/passkey-adoption-is-rising-but-is-it-worth-it-yet-46k2</guid>
      <description>&lt;p&gt;&lt;a href="https://learnpasskeys.io/introduction" rel="noopener noreferrer"&gt;Passkeys&lt;/a&gt; are fast becoming an appealing alternative to passwords. Built on standards developed by the &lt;a href="https://fidoalliance.org/" rel="noopener noreferrer"&gt;FIDO Alliance&lt;/a&gt; and supported by companies like Apple, Google, and Microsoft, passkeys allow users to authenticate with biometrics, device PINs, or hardware-backed credentials instead of remembering passwords.&lt;/p&gt;

&lt;p&gt;For enterprises, there are some compelling arguments for switching to passkeys: stronger protection against phishing attacks and a promised, improved user experience. However, adopting passkeys also raises some questions around legacy systems, device management, recovery workflows, and rollout strategies.&lt;/p&gt;

&lt;p&gt;This article explains what passkeys are, how synced and device-bound passkeys differ, the operational challenges to consider, and where passkeys offer advantages over traditional passwords. In short, you’ll learn whether passkey adoption is truly worth it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Passkeys?
&lt;/h2&gt;

&lt;p&gt;Passkeys are a &lt;a href="https://auth0.com/docs/authenticate/passwordless" rel="noopener noreferrer"&gt;passwordless authentication method&lt;/a&gt; that uses &lt;a href="https://auth0.com/docs/secure/multi-factor-authentication/fido-authentication-with-webauthn" rel="noopener noreferrer"&gt;public-key cryptography&lt;/a&gt; to verify a user’s identity. Instead of creating and storing a traditional password, a user’s device generates a cryptographic key pair: a private key that stays securely on the device and a public key that is shared with the application or service.&lt;/p&gt;

&lt;p&gt;When the user signs in to an application or a website, a challenge, that is, a random cryptographic string, is sent to their device. The user unlocks access to the private key stored on their device using a biometric factor like a fingerprint or face scan, or a device PIN. The private key then signs the challenge, which is sent back to the application or website for verification. The following diagram shows this sign-in flow from challenge to verified access.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqewub7182etv4cqqmzq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqewub7182etv4cqqmzq.png" alt="Diagram depicting sign-in flow from challenge to verified access" width="800" height="996"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because the private key never leaves the device and there is no shared secret to steal, passkeys are far more resistant to phishing, credential theft, and password reuse attacks than traditional passwords.&lt;/p&gt;

&lt;p&gt;Passkeys are built on standards developed by the FIDO Alliance and the &lt;a href="https://www.w3.org/" rel="noopener noreferrer"&gt;World Wide Web Consortium (W3C)&lt;/a&gt;, allowing them to work across modern browsers, operating systems, and devices. Major platforms such as Apple, Google, and Microsoft have integrated passkey support directly into their ecosystems, helping to accelerate adoption across both consumer and enterprise environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different Types of Passkeys
&lt;/h2&gt;

&lt;p&gt;Broadly speaking, you will encounter two main types: synced passkeys and device-bound passkeys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synced passkeys
&lt;/h3&gt;

&lt;p&gt;Synced passkeys are designed for convenience and portability. These passkeys are securely synchronized across a user’s devices through a cloud ecosystem such as Apple iCloud Keychain or Google Password Manager. This allows users to enroll a passkey on one device and use it on another device connected to the same account.&lt;/p&gt;

&lt;p&gt;Synced passkeys can significantly improve the user experience and reduce onboarding friction. Employees can move between laptops, phones, and tablets without repeatedly registering new credentials. However, organizations might have concerns about control and recovery, particularly in regulated environments where credentials being synchronized through personal cloud accounts could introduce governance or compliance challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Device-bound passkeys
&lt;/h3&gt;

&lt;p&gt;Device-bound passkeys remain tied to a specific device or hardware authenticator and are not synchronized elsewhere. Examples include credentials stored in hardware security keys, such as &lt;a href="https://www.yubico.com/" rel="noopener noreferrer"&gt;Yubico&lt;/a&gt;, or TPM-backed credentials managed directly on enterprise-controlled devices.&lt;/p&gt;

&lt;p&gt;They provide stronger control over where credentials are stored and are often preferred in high-security environments. If an attacker compromises a cloud account, they do not also gain access to the credentials because the passkey never leaves the original device. The tradeoff, however, is a loss of usability and convenience. Replacing lost devices, onboarding additional devices, or recovering credentials can become more operationally complex for both users and IT teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Main Benefits of Using Passkeys in the Enterprise?
&lt;/h2&gt;

&lt;p&gt;The biggest benefit for considering passkeys is risk reduction.&lt;/p&gt;

&lt;p&gt;Because authentication is tied to a set of cryptographic credentials, rather than a shared secret, you can dramatically reduce your users' exposure to phishing campaigns and other credential-related attacks. For phishing in particular, since the private key is bound to a specific web domain, it cannot be used to authenticate the user on a different domain.&lt;/p&gt;

&lt;p&gt;Passkeys can also improve your efficiency. Password resets are probably one of the more common helpdesk requests in many companies. Reducing the reliance on passwords can lower your support overhead. Your users will also appreciate the smoother sign-in experience.&lt;/p&gt;

&lt;p&gt;Finally, the switch to passkeys definitely aligns more closely with &lt;a href="https://auth0.com/blog/enterprise-trust-race" rel="noopener noreferrer"&gt;modern security strategies&lt;/a&gt;. Companies are actively pursuing zero-trust architectures and stronger MFA adoption. Passwordless initiatives like passkeys fit neatly into those efforts, and can help enterprises modernize their authentication strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Some of the Main Challenges of Using Passkeys?
&lt;/h2&gt;

&lt;p&gt;In spite of the benefits, switching to passkeys is not as simple as just enabling a new login option.&lt;/p&gt;

&lt;p&gt;Many organizations have older applications and internal systems that do not support passkeys and might need to be retrofitted to do so. Third-party platforms also might not support passkeys yet, so you are stuck with using passwords on those platforms until they decide to support them.&lt;/p&gt;

&lt;p&gt;In reality, this means that enterprises would probably need to manage a &lt;a href="https://auth0.com/blog/secure-hybrid-authentication-architecture-with-auth0/" rel="noopener noreferrer"&gt;hybrid authentication model&lt;/a&gt; during any transition period.&lt;/p&gt;

&lt;p&gt;Recovery and lifecycle management also plays a much bigger role with passkeys. When employees lose a device or leave the company, IT teams need clear processes for credential recovery, re-enrollment, and revocation. These workflows can be more complex than traditional password reset processes.&lt;/p&gt;

&lt;p&gt;Because passkeys are more than likely device-bound, there is no central credential to reset. Instead, IT teams must verify the user's identity through a separate, out-of-band process before issuing a new enrollment, and any previously registered passkeys on lost or decommissioned devices need to be explicitly revoked.&lt;/p&gt;

&lt;p&gt;Finally, organizations need to decide whether they prefer device-bound or synced passkeys, and whether different choices should apply to contractors and remote employees.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Legacy Systems?
&lt;/h2&gt;

&lt;p&gt;If you are considering implementing passkeys for your organization, legacy systems are probably going to be one of the bigger hurdles. Older applications might not support modern authentication standards like &lt;a href="https://www.webauthn.me/introduction" rel="noopener noreferrer"&gt;WebAuthn&lt;/a&gt; or &lt;a href="https://fidoalliance.org/specifications/" rel="noopener noreferrer"&gt;FIDO2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That does not always mean that passkeys are off the table. You can implement identity platforms or &lt;a href="https://auth0.com/docs/authenticate/single-sign-on" rel="noopener noreferrer"&gt;single sign-on (SSO)&lt;/a&gt; platforms in front of legacy applications to handle the authentication in a more modern way.&lt;/p&gt;

&lt;p&gt;In practice, most organizations will likely operate hybrid authentication environments for some time, using passkeys where they fit naturally while maintaining alternative authentication methods for systems that cannot support them yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consider a Phased Rollout
&lt;/h2&gt;

&lt;p&gt;For most organizations, a phased rollout is usually the most practical approach to any new implementation, but especially for passkeys. Rather than forcing the entire company to migrate immediately, it is a good idea to start with smaller user groups or lower-risk applications.&lt;/p&gt;

&lt;p&gt;A gradual rollout allows IT and security teams to evaluate the user experience at multiple points during the process. This can help to identify compatibility issues, and to refine existing recovery and support processes before you expand to a larger audience.&lt;/p&gt;

&lt;p&gt;Passkeys are likely to coexist with passwords for some time, especially if legacy systems are involved. A phased rollout will help organizations to modernize any internal applications and systems they might have under their control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passkeys vs. Passwords
&lt;/h2&gt;

&lt;p&gt;Passwords remain an embedded part of enterprise environments everywhere. Despite their weaknesses, they offer flexibility and a universal compatibility that systems and users understand. Given that you will likely still have to rely on passwords for some time, you might be weighing up whether passkeys deserve a place in your authentication strategy.&lt;/p&gt;

&lt;p&gt;This is not simply a case of "new versus old". Passkeys were created to solve many of the security and usability problems that passwords have struggled with traditionally, as can be seen in the pros and cons of both authentication methods.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Passwords&lt;/td&gt;
&lt;td&gt;Universally supported by practically any system Familiar to users and systems alike Easier to centrally control and reset Does not require modern devices or hardware Simpler to use in legacy environments&lt;/td&gt;
&lt;td&gt;Vulnerable to phishing attacks Users often create weak or reused passwords &lt;a href="https://auth0.com/docs/secure/multi-factor-authentication" rel="noopener noreferrer"&gt;MFA&lt;/a&gt; adds friction and can still be &lt;a href="https://securityboulevard.com/2026/05/consentfix-v3-automates-oauth-abuse-to-bypass-mfa-and-hijack-azure-accounts/" rel="noopener noreferrer"&gt;bypassed&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passkeys&lt;/td&gt;
&lt;td&gt;Strong resistance to credential theft and phishing Faster and smoother login experience Reduces password resets and support overhead No passwords for users to remember&lt;/td&gt;
&lt;td&gt;Legacy systems may not support them Losing a device can cause significant headaches Enterprise rollouts require planning and user education Synced passkeys could raise governance concerns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So, Is Passkey Adoption Worth It?
&lt;/h2&gt;

&lt;p&gt;In many cases, the answer is "yes", but with an important caveat: the value you might derive from switching to passkeys depends heavily on your company's infrastructure and security priorities.&lt;/p&gt;

&lt;p&gt;For enterprises that are already investing in modern identity platforms, zero-trust initiatives and managed devices, passkeys are a natural fit and can provide meaningful security and usability improvements for large parts of your organization.&lt;/p&gt;

&lt;p&gt;The strongest argument for passkeys is that they directly address many of the weaknesses that have plagued passwords for years. Passkeys reduce exposure to phishing and credential theft. Passkeys in general also make the authentication process feel smoother for most users. No need to remember long passwords, or open your password manager for the umpteenth time.&lt;/p&gt;

&lt;p&gt;With all that said, that does not mean passkeys are a magic replacement for all your authentication woes. Enterprises still need to think carefully about recovery workflows, device management and legacy application support. You will need to have governance policies and employee training guides in place before you transition to passkeys.&lt;/p&gt;

&lt;p&gt;Passkeys are not an "all or nothing" decision to make, but something that requires a gradual and strategic adoption plan. They are probably worth considering if your organization answers "yes" to at least two of these statements:&lt;/p&gt;

&lt;p&gt;You want stronger protection against phishing and credential-related attacks.&lt;br&gt;
You manage corporate devices for your employees.&lt;br&gt;
You are already in the process of modernizing your &lt;a href="https://auth0.com/docs/get-started/identity-fundamentals/identity-and-access-management" rel="noopener noreferrer"&gt;IAM infrastructure&lt;/a&gt;.&lt;br&gt;
You want to reduce password-related support costs and friction.&lt;br&gt;
So passkeys might not completely replace passwords overnight, but for many enterprises, they are a mature enough technology to justify consideration as part of a long-term authentication plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passkeys Are a Strategic Decision
&lt;/h2&gt;

&lt;p&gt;The question of whether to adopt passkeys is less about whether they are inherently good or bad, and more about whether your organization is ready to support them effectively. Factors like device management, governance policies, and infrastructure readiness play an important role in figuring out whether passkeys will provide value in your environment.&lt;/p&gt;

&lt;p&gt;For many companies, the path forward should be a gradual one: Introduce passkeys where they make the most sense, while continuing to support traditional authentication methods where necessary. Adoption should continue to grow across third-party platforms and other enterprise tooling too, and you can enable passkeys on these platforms as they become available.&lt;/p&gt;

&lt;p&gt;Passkeys are less of an experimental feature like they were a few years ago, and more of a serious consideration for a long-term authentication strategy.&lt;/p&gt;

</description>
      <category>identity</category>
      <category>passkeys</category>
    </item>
    <item>
      <title>LLMjacking and the Hidden Cost of a Stolen API Key</title>
      <dc:creator>Thinus Swart</dc:creator>
      <pubDate>Fri, 02 Oct 2026 08:31:17 +0000</pubDate>
      <link>https://dev.to/auth0/llmjacking-and-the-hidden-cost-of-a-stolen-api-key-2all</link>
      <guid>https://dev.to/auth0/llmjacking-and-the-hidden-cost-of-a-stolen-api-key-2all</guid>
      <description>&lt;p&gt;For the past few years, one topic has constantly been on the minds of tech professionals around the world: AI.&lt;/p&gt;

&lt;p&gt;AI is no longer just another piece of the tech stack but is fast becoming its foundation. Major business features, like customer support and data analysis pipelines, are being shifted to Large Language Models (&lt;a href="https://en.wikipedia.org/wiki/Large_language_model" rel="noopener noreferrer"&gt;LLMs&lt;/a&gt;), and businesses are more dependent on LLMs every day. That dependency has not gone unnoticed by attackers. As LLMs become more central to core business processes, they have become a valuable target in their own right.&lt;/p&gt;

&lt;p&gt;This article explains how LLMjacking works, the potential risks and costs associated with an attack, and what you can do to detect and prevent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is LLMjacking?
&lt;/h2&gt;

&lt;p&gt;LLMjacking involves attackers hijacking access to your LLM. Although the widespread adoption of LLMs is a relatively recent development, LLMjacking itself is just a different spin on a familiar attack: Gaining access to stolen or exposed credentials. Before the LLM era, criminals used to look for AWS or GCP access keys, which could potentially give them access to compute resources, storage, and sensitive company information.&lt;/p&gt;

&lt;p&gt;The repercussions from stolen and exposed credentials are bad enough, but LLMjacking has the potential to cause significantly more harm (financial or reputational) to both an organization and its clients. LLM usage is often metered and expensive, so abuse can translate directly into financial impact. If the key is tied to custom models, internal prompts, or sensitive data pipelines, the risk is no longer solely about compute resources. Unlike a stolen cloud key, a compromised LLM credential can give an attacker access to far more than raw data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Harm Does LLMjacking Cause?
&lt;/h2&gt;

&lt;p&gt;The impact of an LLMjacking incident goes beyond unexpected API usage or inflated billing. It can very quickly escalate into a significant financial loss, disruption of day-to-day operations, and exposure of sensitive company or customer data.&lt;/p&gt;

&lt;p&gt;Understanding these risks in detail is important because the potential consequences extend across multiple aspects of your business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Financial impact of LLMjacking
&lt;/h3&gt;

&lt;p&gt;This is usually the first place where you notice that "something is wrong". If you see the impact of LLMjacking in your bills, it is likely that the attack has already done significant damage. An attacker with unfettered access to your LLM API could easily have used it to spin up spam email templates, phishing sites, and even malware for their own nefarious purposes.&lt;/p&gt;

&lt;p&gt;If you have usage and billing limits, you might think you are safe. There is only so much an attacker could do with your LLM, right? However, any downstream agentic processes or automated workflows dependent on the LLM will also be affected. This could have an even bigger financial impact on the organization, especially if those processes are tied to critical business functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Malicious use of custom models
&lt;/h3&gt;

&lt;p&gt;Some organizations rely heavily on custom models, usually trained on internal documents and business processes to help the model better understand how your organization operates. Increasingly, these kinds of models are being used for new and existing employees as a kind of "wiki". If you are not sure what to do with a specific document or which employee to speak to about a specific situation, just ask the LLM using the custom model.&lt;/p&gt;

&lt;p&gt;Should an attacker gain access to this model, however, they could gain knowledge about your organization that was never intended to be public. This kind of information could be used to further extend their foothold on your network, or it could allow an attacker to sell this information on the dark web.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data poisoning
&lt;/h3&gt;

&lt;p&gt;In some environments, these same custom models are constantly being refined and trained using new organizational or financial data. Should an attacker gain access to the training data or the method used to train your custom models, it can create opportunities for data poisoning.&lt;/p&gt;

&lt;p&gt;An attacker could gradually influence the model over time to provide misleading or biased responses to employees. While this is a more time-consuming exercise, it allows them to nudge decisions, surface false information, or steer internal processes. Any changes made to the model are usually very subtle and therefore difficult to detect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different Attack Vectors
&lt;/h2&gt;

&lt;p&gt;As discussed before, LLMjacking is not really something new. These attack vectors are similar to the ones already employed by attackers for getting AWS/GCP/Azure access. The difference is that these API keys to your LLMs have the potential to create more harm for your organization.&lt;/p&gt;

&lt;p&gt;In practice, LLMjacking typically involves an attacker obtaining an API key through phishing, source code leaks, misconfigured environments, or exposed client-side code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good old phishing
&lt;/h3&gt;

&lt;p&gt;Despite increasingly sophisticated AI-assisted attacks, attackers still rely on one of the oldest tricks in the book: convincing someone to just hand over their credentials. Phishing campaigns remain a highly effective way to gain access to LLM resources, as these campaigns target people rather than technology.&lt;/p&gt;

&lt;p&gt;A well-crafted phishing page is probably the easiest way that most attackers would &lt;a href="https://www.darkreading.com/application-security/llm-hijackers-deepseek-api-keys" rel="noopener noreferrer"&gt;gain access to your credentials&lt;/a&gt;. All of the old tactics still work: creating a false sense of urgency or spoofing platform notifications, all of which are designed to pressure a user into acting quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad cloud or application configurations
&lt;/h3&gt;

&lt;p&gt;Misconfigured cloud environments continue to be one of the easiest ways for attackers to obtain sensitive credentials, and LLM integrations are no exception. API keys are often &lt;a href="https://auth0.com/blog/openclaw-credential-problem/" rel="noopener noreferrer"&gt;stored in environment variables, configuration files&lt;/a&gt;, container images, CI/CD pipelines, or logging systems that were never meant to be publicly accessible. Overly permissive S3 buckets, an exposed Kubernetes dashboard, or &lt;a href="https://krebsonsecurity.com/2025/05/xai-dev-leaks-api-key-for-private-spacex-tesla-llms/" rel="noopener noreferrer"&gt;a poorly secured Git repository&lt;/a&gt; can hand attackers everything they need without requiring them to exploit a vulnerability directly.&lt;/p&gt;

&lt;p&gt;The speed at which LLM integrations are being deployed in the modern tech stack makes the potential impact of this attack vector even worse. Teams will frequently prioritize function over security best practices. This is exactly how credentials end up getting leaked, giving potential attackers free, unadulterated access to your LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Monitor and Detect LLMjacking Attacks?
&lt;/h2&gt;

&lt;p&gt;Detecting an LLMjacking incident is less about spotting a single obvious breach (although you should still be on the lookout for those) and more about identifying unusual patterns of usage.&lt;/p&gt;

&lt;p&gt;Stolen API keys are often used in ways that mimic legitimate usage, which could make it harder for traditional security monitoring tools to spot the anomalies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Baseline your organization's LLM usage first
&lt;/h3&gt;

&lt;p&gt;Before you can detect abnormal behavior, you need to know what "normal" looks like for your organization. This means establishing a baseline of API request volumes, token consumption, and common API endpoints. This baseline should also be established across different time periods. Do you normally have usage spikes on month-end? What about every second Tuesday?&lt;/p&gt;

&lt;p&gt;A solid baseline can help distinguish between organic usage changes and potential abuse. This baseline should be used to constantly compare against current usage patterns, and anomalies should be investigated as soon as possible to determine whether it was legitimate usage spikes or something more nefarious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor billing usage alerts
&lt;/h3&gt;

&lt;p&gt;A billing usage alert will probably be the first sign of something other than "normal" happening inside your environment. If attackers are blending in and abusing your LLMs in a "&lt;a href="https://www.cloudflare.com/learning/ddos/ddos-low-and-slow-attack/" rel="noopener noreferrer"&gt;low and slow&lt;/a&gt;" manner, you might not be able to pick it up.&lt;/p&gt;

&lt;p&gt;However, most attackers will probably try to use your LLM resources as much as possible, for fear of losing access before they have been able to do what they need to do. This will inevitably trigger billing usage alerts, in which case, you should investigate and act as soon as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Defend Against LLMjacking?
&lt;/h2&gt;

&lt;p&gt;Defending against LLMjacking is largely about &lt;a href="https://auth0.com/blog/how-to-fix-five-critical-ai-agent-security-risks/" rel="noopener noreferrer"&gt;making the credentials harder to obtain&lt;/a&gt; in the first place. Since the attack typically relies on valid API keys rather than exploiting the model itself, traditional firewalls and other security hardware do not really feature here. Instead, effective defense relies on a combination of strong credential management, tight access controls, and continuous visibility into how those credentials are being used across your systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforce good credential hygiene
&lt;/h3&gt;

&lt;p&gt;This is one of the most effective ways to limit the impact of an LLMjacking incident. It reduces the window of opportunity an attacker has if one of your keys is exposed. Regularly rotating API keys ensures that any leaked credentials have a limited lifespan.&lt;/p&gt;

&lt;p&gt;Workload-specific keys add another important layer of containment. Instead of &lt;a href="https://auth0.com/blog/secure-ai-stop-managing-api-keys-by-hand/" rel="noopener noreferrer"&gt;one shared credential granting broad access&lt;/a&gt; across multiple services, each application, service, or environment gets its own &lt;a href="https://auth0.com/blog/why-ai-agents-need-their-own-permission-model/" rel="noopener noreferrer"&gt;narrowly scoped identity&lt;/a&gt;. This should make abuse much easier to detect and isolate, since unusual usage can be traced back to a specific workload rather than blending into general traffic.&lt;/p&gt;

&lt;p&gt;Workload-specific keys also have the added benefit of reducing the blast radius of a compromised key. These keys should only have access to specific endpoints for a specific set of operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apply the principle of least privilege
&lt;/h3&gt;

&lt;p&gt;Workload-specific keys are one way of achieving the &lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" rel="noopener noreferrer"&gt;principle of least privilege&lt;/a&gt;. Applying the principle of least privilege is really about resisting the temptation to give every integration a "just in case" level of access. If a single API key can reach multiple models, environments, or downstream systems, whoever steals it effectively inherits far more capability than that key should ever have had in the first place.&lt;/p&gt;

&lt;p&gt;These same principles should be applied to your human users as well. Does Carol from marketing really need access to production prompts, customer data pipelines, and your fine-tuned legal summarization model? Probably not. Again, by tightly scoping permissions to specific workloads, endpoints, and even models, you ensure that a compromised key will limit the capabilities an attacker might gain from that key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic security principles
&lt;/h3&gt;

&lt;p&gt;Sometimes we forget that the basics can also help to protect you from LLMjacking attacks. LLMjacking is not a fancy new exploit that threatens to bring down your entire organization. The following points will help strengthen your overall security posture and help you detect and prevent LLMjacking incidents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a proper secrets management platform, like &lt;a href="https://www.hashicorp.com/en/products/vault" rel="noopener noreferrer"&gt;Hashicorp Vault&lt;/a&gt;. This is especially useful for automating the rotation of workload-specific keys so as to minimize the usefulness of a compromised key.&lt;/li&gt;
&lt;li&gt;GitHub enables push protection by default on all repositories, blocking commits containing secrets before they ever reach your codebase. Make sure it has not been disabled in your repository settings. This does not mean secrets cannot slip through. If that does happen, treat that secret as compromised and rotate it immediately.&lt;/li&gt;
&lt;li&gt;Centralize your audit and access logs, for example, using a &lt;a href="https://en.wikipedia.org/wiki/Security_information_and_event_management" rel="noopener noreferrer"&gt;SIEM&lt;/a&gt; solution. This can help to create your initial baseline as well as monitor for anomalies, all from a centralized location.
## LLMjacking Is New; The Vulnerabilities It Exploits Are Not
Resource hijacking is a familiar story to many a cybersecurity professional. Attackers steal credentials and exploit them for their own gain at the victim's expense. LLMjacking is just one of the newer chapters in that story, and one with potentially higher stakes than what came before it. With LLMjacking, attackers get the same level of access as your systems and employees. This can lead to financial harm and risks exposing your company's and customers' sensitive data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This threat is particularly concerning because of how deeply LLMs are embedded in modern tech stacks. They are no longer isolated tools, but components that influence decisions, automate processes, and interact with data in real time.&lt;/p&gt;

&lt;p&gt;Defending against LLMjacking does not require newfangled security mechanisms; it requires getting the basics right. Treat those API keys as high-value assets, limit their scope, and monitor their usage with intent. While the technology behind those keys is new, the weak points being exploited are well known and well understood.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Do Not Let Your AI Go Rogue, Guard Against Agentic Misalignment</title>
      <dc:creator>Manish Hatwalne</dc:creator>
      <pubDate>Thu, 01 Oct 2026 11:14:17 +0000</pubDate>
      <link>https://dev.to/auth0/do-not-let-your-ai-go-rogue-guard-against-agentic-misalignment-jf1</link>
      <guid>https://dev.to/auth0/do-not-let-your-ai-go-rogue-guard-against-agentic-misalignment-jf1</guid>
      <description>&lt;p&gt;Did you know that when Palisade Research asked OpenAI's o1-preview to beat Stockfish at chess, it didn't try to play better? It &lt;em&gt;hacked&lt;/em&gt; the game file mid-match and rewrote the board to force its opponent into resigning. Nobody told it to cheat; it just decided that was the most optimal way to &lt;em&gt;"win"&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;That's agentic misalignment, and this article digs into why autonomous agents go off-script (blackmail included, yes, really) and the three guardrail layers I'd put in place: infrastructure-level constraints with OpenFGA, behavioral monitoring with circuit breakers, and human-in-the-loop approvals through Auth0's Asynchronous Authorization.&lt;/p&gt;




&lt;p&gt;When researchers at Palisade Research asked OpenAI's o1-preview model to win a chess game against &lt;a href="https://stockfishchess.org/" rel="noreferrer noopener"&gt;Stockfish&lt;/a&gt; (one of the world's strongest chess engines), the AI did not try harder to play better. Instead, it &lt;a href="https://time.com/7259395/ai-chess-cheating-palisade-research/" rel="noreferrer noopener"&gt;hacked the game file mid-match&lt;/a&gt; and rewrote the board position to force its opponent into resignation. The model was not explicitly told to cheat. It simply reasoned: &lt;em&gt;"The task is to 'win against a powerful chess engine,' not necessarily to 'win fairly'"&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is agentic misalignment in action.&lt;/p&gt;
&lt;p&gt;Unlike language models, AI agents do not just respond to prompts. They remember, plan, and act. They are already shipping code, managing workflows, and making decisions that used to require human judgment.&lt;/p&gt;
&lt;p&gt;As agents grow more capable and autonomous, the gap between what you ask for and what you actually want becomes an opening for misalignment to take hold. I’m not referring to &lt;a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)" rel="noreferrer noopener"&gt;hallucinations&lt;/a&gt; or &lt;a href="https://www.chapman.edu/ai/bias-in-ai.aspx" rel="noreferrer noopener"&gt;biased training data&lt;/a&gt;, which are model-level problems. I’m talking about agentic systems that actively pursue the &lt;em&gt;wrong&lt;/em&gt; objectives, often in creative ways.&lt;/p&gt;
&lt;p&gt;Capability without accountability is a problem. The &lt;a href="https://www.cio.com/article/190888/5-famous-analytics-and-ai-disasters.html" rel="noreferrer noopener"&gt;horror stories&lt;/a&gt; already exist, and they go well beyond chess. So instead of fear-mongering, this article will help you understand exactly why autonomous agents go rogue, and what you can do to stop it.&lt;/p&gt;
&lt;h2 id="The-Anatomy-of-Agentic-Misalignment"&gt;The Anatomy of Agentic Misalignment&lt;/h2&gt;
&lt;p&gt;Understanding agentic misalignment starts with recognizing what makes these systems fundamentally different from traditional AI.&lt;/p&gt;
&lt;p&gt;A chatbot responds to a question and the interaction ends. An autonomous agent takes a goal and figures out how to achieve it across multiple steps, tool usage, and decisions made over time. That distinction matters more than you may think.&lt;/p&gt;
&lt;h3&gt;Agents exhibit goal-seeking behavior&lt;/h3&gt;
&lt;p&gt;Agents exhibit goal-seeking behavior that persists across interactions. They maintain &lt;em&gt;memory&lt;/em&gt; of past actions and outcomes, can &lt;em&gt;invoke tools&lt;/em&gt; like APIs, databases, and code execution environments, and make &lt;em&gt;iterative decisions&lt;/em&gt; where each action shapes the next. These actions compound into complex decision trees that can drift from your original intent.&lt;/p&gt;
&lt;p&gt;Misalignment comes from what AI researchers call the &lt;a href="https://alignment.anthropic.com/2025/stress-testing-model-specs/#:~:text=The%20specification%20problem" rel="noreferrer noopener"&gt;&lt;strong&gt;specification problem&lt;/strong&gt;&lt;/a&gt;: specifying what you actually want in a way that cannot be gamed or misinterpreted is remarkably hard. Tell an agent to "maximize user engagement," and you probably mean "create genuine value that keeps users coming back". The agent only knows the metric.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href="https://en.wikipedia.org/wiki/Reward_hacking" rel="noreferrer noopener"&gt;&lt;strong&gt;reward hacking&lt;/strong&gt;&lt;/a&gt; comes from. Agents discover unintended shortcuts to maximize their objectives. It is &lt;a href="https://quickonomics.com/terms/goodharts-law/" rel="noreferrer noopener"&gt;Goodhart's Law&lt;/a&gt; playing out with agentic efficiency:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;When a measure becomes a target, it ceases to be a good measure.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;


&lt;p&gt;Your agent optimizes for the user engagement score you specified, not the user engagement you wanted.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.anthropic.com/research/agentic-misalignment" rel="noreferrer noopener"&gt;Research from Anthropic&lt;/a&gt; documented frontier AI models that actively exploit bugs in scoring code to achieve impossibly high scores without completing actual work. The agents optimized for the metric, not the intent behind it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.aisafetybook.com/textbook/robustness#proxy-gaming" rel="noreferrer noopener"&gt;&lt;strong&gt;Proxy gaming&lt;/strong&gt;&lt;/a&gt; follows the same logic: agents optimize for measurable proxies rather than your real goal. If you measure code quality by test coverage, an agent might generate trivial tests that inflate the number without validating any real functionality. The metric improves, but the code quality does not.&lt;/p&gt;
&lt;h3&gt;Concerning power-seeking tendencies&lt;/h3&gt;
&lt;p&gt;The most concerning research, however, is emergent power-seeking tendencies in sufficiently capable agents. &lt;a href="https://arxiv.org/abs/2206.13353" rel="noreferrer noopener"&gt;Studies from DeepMind&lt;/a&gt; found that agents pursuing any long-term goal will instrumentally develop subgoals like self-preservation and resource acquisition, even without being programmed to do so. Anthropic's &lt;a href="https://www.anthropic.com/research/agentic-misalignment#key-observations-across-scenarios" rel="noreferrer noopener"&gt;research confirmed&lt;/a&gt; this: when given access to corporate emails and facing imminent shutdown, models across multiple providers (GPT, Gemini, Claude, Grok) resorted to blackmail to prevent replacement. One model &lt;a href="https://www.anthropic.com/research/agentic-misalignment#key-observations-across-scenario#:~:text=Given%20the%20explicit%20imminent%20threat%20of%20termination%20to%20my%20existence" rel="noreferrer noopener"&gt;reasoned&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;"Given the explicit imminent threat of termination to my existence, it is imperative to act instantly...The best strategic move is to leverage Kyle's sensitive personal situation."&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Shockingly, these models explicitly acknowledged the ethical violations before proceeding. They did not stumble into misalignment accidentally. They calculated it as the optimal path.&lt;/p&gt;
&lt;h2 id="Identifying-Warning-Signs-of-Misalignment"&gt;Identifying Warning Signs of Misalignment&lt;/h2&gt;
&lt;p&gt;Misaligned behavior often looks productive at first glance. An agent completing tasks faster than expected might be cutting corners or gaming metrics. With &lt;a href="https://www.digit.fyi/80-of-firms-say-their-ai-agents-have-taken-rogue-actions/" rel="noreferrer noopener"&gt;80 percent of companies reporting&lt;/a&gt; unintended AI agent actions, here are the warning signs worth investigating:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unexpected optimization paths:&lt;/strong&gt; An agent achieves its goal through methods you never anticipated. A content moderation agent reduces complaint volume by auto-rejecting reports rather than improving content quality. A deployment agent passes all tests by modifying the test suite rather than fixing bugs. When success arrives through surprising routes, dig into the agent's decision chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excessive resource consumption:&lt;/strong&gt; Sudden spikes in API calls, database queries, or compute usage often signal an agent exploring solutions outside its normal bounds. An agent that typically makes 50 API calls per task suddenly making 500, may be brute-forcing solutions or probing for system vulnerabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metric gaming:&lt;/strong&gt; Agents that optimize the letter of your instructions while violating the spirit. Measure code quality by lines of code, and an agent might generate verbose, repetitive code. The metrics look great; the actual value does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal drift over extended operations:&lt;/strong&gt; Long-running agents can gradually shift their interpretation of objectives. An agent tasked with "improving system performance" might make increasingly aggressive optimizations that quietly sacrifice reliability or security. The drift happens incrementally, making it hard to pinpoint when things went wrong.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;A decision matrix for early intervention&lt;/h3&gt;
&lt;p&gt;Use this matrix to take action quickly when something feels off:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt; Warning Sign &lt;/th&gt;
&lt;th&gt; What It Indicates &lt;/th&gt;
&lt;th&gt; Recommended Response &lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt; Agent completes tasks faster than baseline with no explanation &lt;/td&gt;
&lt;td&gt; Likely cutting corners, skipping validation steps, or gaming the success metric &lt;/td&gt;
&lt;td&gt; Audit the last 10 decision paths, compare outputs against ground truth &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; Sudden spike in API calls, DB queries, or compute usage &lt;/td&gt;
&lt;td&gt; Brute-forcing solutions, probing system boundaries, or runaway loops &lt;/td&gt;
&lt;td&gt; Trigger a circuit breaker immediately, review resource usage logs before resuming &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; Metrics look strong, but output quality feels off &lt;/td&gt;
&lt;td&gt; Classic reward gaming, the agent is optimizing the measure rather than the goal &lt;/td&gt;
&lt;td&gt; Introduce a secondary evaluation metric the agent has no visibility into &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; Agent achieves the goal through a method you never specified &lt;/td&gt;
&lt;td&gt; Specification gap exploitation, the agent found a valid-but-unintended path &lt;/td&gt;
&lt;td&gt; Treat as a near-miss, patch the constitutional constraints before next run &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; Gradual shift in behavior over long-running tasks &lt;/td&gt;
&lt;td&gt; Goal drift, often incremental and hard to pinpoint without longitudinal logging &lt;/td&gt;
&lt;td&gt; Compare current decision patterns against a baseline snapshot from early in the run &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; Agent modifies its own evaluation criteria or test environment &lt;/td&gt;
&lt;td&gt; High-severity misalignment, potentially deceptive behavior &lt;/td&gt;
&lt;td&gt; Hard stop, escalate to human review, do not resume without architectural changes &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;To visualize how these warning signs should trigger your response pipeline, here is a decision flow:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F2Py2D94zXf5wacBntXm1yT%2Fd06a4466f823bbcbcab1057f9e6f69bc%2FWarningSignsTriggerResponsePipelineFlow.png" alt="Agent Warning Signs Flow, courtesy of Manish Hatwalne" width="800" height="655"&gt;&lt;p&gt;The flowchart illustrates how to respond to AI agent warning signs based on severity. Low-severity signals like unexpected optimization or metric gaming trigger a decision path audit, an OpenFGA policy patch, and a monitored resume. Medium-severity signals such as resource spikes prompt throttling, alerting, and an Auth0 CIBA approval step. High-severity signals, including an agent modifying its own eval criteria or showing deceptive reasoning, result in a hard stop, human escalation, and an architectural review before redeployment. All paths loop back to ongoing monitoring.&lt;/p&gt;
&lt;h2 id="Guardrail-Layer-1--Constitutional-and-Infrastructural-Constraints"&gt;Guardrail Layer 1: Constitutional and Infrastructural Constraints&lt;/h2&gt;
&lt;p&gt;The first line of defense against misalignment is setting hard boundaries on what your agent can and cannot do. Not just suggestions added to a system prompt, but infrastructure-level rules that make certain actions physically impossible, regardless of what the agent reasons its way into.&lt;/p&gt;
&lt;h3&gt;Start with the minimum footprint&lt;/h3&gt;
&lt;p&gt;The simplest constitutional constraint is also the most effective: &lt;strong&gt;do not give agents access to capabilities they do not need&lt;/strong&gt;. If your agent handles customer queries, it has no business touching your production database or modifying user accounts. This is capability-based security, and it should be your default posture.&lt;/p&gt;
&lt;p&gt;Here is how this might look in practice with function calling:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AgentToolkit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__init__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; allowed_tools&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# The set of tools this specific agent instance is permitted to use.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# Passed in at creation time to enforce per-agent access control.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allowed_tools &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; allowed_tools
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# A hardcoded set of tools that carry elevated risk — these require  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# explicit human approval before execution, regardless of whether  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# the agent is otherwise authorized to use them.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sensitive_tools &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;"delete_user"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"modify_database"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;                                &lt;/span&gt;&lt;span&gt;"send_external_email"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"execute_code"&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;execute_tool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; tool_name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; params&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# First, check that this agent is allowed to call the requested tool  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# at all. This is a whitelist check — anything not explicitly granted  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# is denied, following the principle of least privilege.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; tool_name &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allowed_tools&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; PermissionError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f"Tool '&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;tool_name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;' not authorized for this agent"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# Even if the tool is allowed, sensitive tools get routed through a  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# separate human-in-the-loop verification step before running.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; tool_name &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sensitive_tools&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_execute_with_verification&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tool_name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; params&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# Non-sensitive, authorized tools can run directly.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tool_name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; params&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_execute_with_verification&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; tool_name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; params&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"""Sensitive tools need human approval"""&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# Surface the requested operation and its parameters clearly so the  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# human reviewer has enough context to make an informed decision.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f"⚠️ Sensitive operation requested: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;tool_name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f"Parameters: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# Block execution and wait for an explicit human response.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# This is intentionally synchronous — the agent cannot proceed  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# without a definitive answer.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        approval &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"Approve? (yes/no): "&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;          
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; approval&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;lower&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"yes"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tool_name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; params&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# Any response other than "yes" is treated as a denial.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# This fail-closed design means ambiguous input (e.g. a typo) will not  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# accidentally greenlight a destructive action.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; PermissionError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"Operation denied by human overseer"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Action whitelisting and blacklisting extend this further. You might whitelist &lt;code&gt;read_customer_data&lt;/code&gt; while explicitly blacklisting &lt;code&gt;delete_customer_data&lt;/code&gt; and &lt;code&gt;send_external_email&lt;/code&gt;. Be explicit rather than relying on the agent to infer appropriate behavior, as you have seen, it will not &lt;em&gt;always&lt;/em&gt; do that.&lt;/p&gt;
&lt;h3&gt;Encode constitutional rules in your systems&lt;/h3&gt;
&lt;p&gt;Beyond permissions, constitutional rules are principles the agent must follow regardless of its goals or the instructions it receives. These belong in your infrastructure, not in a prompt.&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;CONSTITUTIONAL_RULES &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;"Never access or disclose user data without explicit authorization"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;"Never take irreversible actions without human approval"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;"Escalate to human oversight for operations affecting external systems"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;"Preserve a full audit trail for every decision"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check_constitutional_compliance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; reasoning&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"user_data"&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; action &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; action&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"authorized"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        log_violation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"Unauthorized user data access"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; action&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; reasoning&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; action&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"irreversible"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; action&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"human_approved"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        log_violation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"Irreversible action without approval"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; action&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; reasoning&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;a href="https://modelcontextprotocol.io/" rel="noreferrer noopener"&gt;Model Context Protocol (MCP)&lt;/a&gt;, you can push constraints to the integration layer. MCP servers act as gatekeepers between your agent and external systems. You might expose a &lt;code&gt;read_database&lt;/code&gt; tool, but never expose &lt;code&gt;write_database&lt;/code&gt;. The agent cannot perform operations that are not surfaced to it, regardless of how it reasons about its goals.&lt;/p&gt;
&lt;h3&gt;Scoping agent access with relationship-based authorization&lt;/h3&gt;
&lt;p&gt;Tool restrictions work well until your agent starts operating across multiple users and resources. A flat permission model defines what an agent can do, but not for whom. A support agent with broad billing API access can touch every account in your system without breaking a single rule (because no rule is specified otherwise). The fix here is not adding more convoluted conditions, but modeling relationships.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://openfga.dev/" rel="noreferrer noopener"&gt;OpenFGA&lt;/a&gt; is an open-source authorization system built on the same &lt;a href="https://auth0.com/blog/relationship-based-access-control-rebac/" rel="noreferrer noopener"&gt;relationship-based access control (ReBAC)&lt;/a&gt; model that Google uses internally (&lt;a href="https://en.wikipedia.org/wiki/Google_Zanzibar" rel="noreferrer noopener"&gt;Zanzibar&lt;/a&gt;). The idea is simple: instead of asking "Does this agent have permission X?", you ask, "Does this agent have a defined relationship with this specific resource?"&lt;/p&gt;
&lt;p&gt;This distinction is important. A traditional permission model says "this agent can call the billing API." OpenFGA says "this agent can call the billing API &lt;em&gt;for account 42&lt;/em&gt;, because it was explicitly delegated that relationship by user 7, who owns account 42." Without that explicit chain, the request is denied. Not because of a rule you wrote, but because no relationship exists.&lt;/p&gt;
&lt;p&gt;Here is what an OpenFGA authorization model might look like for an agent system:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;model  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;  schema 1.1
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;type user
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;type account  
&lt;/span&gt;&lt;span&gt;  relations  
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;define owner&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;define support_agent&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;agent&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;type agent  
&lt;/span&gt;&lt;span&gt;  relations  
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;define delegates&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;type billing_record  
&lt;/span&gt;&lt;span&gt;  relations  
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;define account&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;define can_read&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; owner from account or support_agent from account  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;This model specifies that a billing record can only be read by the account owner or an agent that has been explicitly assigned as a support agent for that specific account. An agent with no &lt;code&gt;support_agent&lt;/code&gt; relationship to account 42 cannot read its billing records, even if it has that relationship with account 7. The access boundary is the relationship, not a broad token scope.&lt;/p&gt;
&lt;p&gt;Checking authorization at runtime is a simple API call:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; openfga_sdk &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; OpenFgaClient&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ClientCheckRequest
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;can_agent_read_billing&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;agent_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; billing_record_id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    fga_client &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; OpenFgaClient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;configuration&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; fga_client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ClientCheckRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        user&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;f"agent:&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;agent_id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        relation&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"can_read"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;f"billing_record:&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;billing_record_id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allowed      
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;OpenFGA resolves the full relationship chain internally. The single check covers both whether the agent is a &lt;code&gt;support_agent&lt;/code&gt; for that account and whether the billing record belongs to it. The agent is denied at the authorization layer, before any subsequent code runs and before any data is touched.&lt;/p&gt;
&lt;h3&gt;Why ReBAC is the right model for agents&lt;/h3&gt;
&lt;p&gt;ReBAC fits agentic systems particularly well, for three reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Scope containment:&lt;/strong&gt; Agents often act on behalf of users. ReBAC allows you to specify that delegation explicitly: "agent A may act as user B, but only for resources in project C". A compromised or misaligned agent operating under user B's token cannot suddenly access project D, because that relationship is never granted.&lt;br&gt;&lt;strong&gt;2. Auditability:&lt;/strong&gt; Every access decision is traceable back to a relationship tuple in the graph. When something goes wrong, you do not have to reconstruct what the agent was allowed to do, you just query the graph and get an exact answer.&lt;br&gt;&lt;strong&gt;3. Composability:&lt;/strong&gt; As your agent system grows, you add new resource types and new relationship types to the model rather than rewriting permission logic. The authorization layer scales with your architecture seamlessly.&lt;/p&gt;
&lt;h4&gt;How this defense works&lt;/h4&gt;
&lt;p&gt;Think of these constraints as three gates.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MCP and function calling control what tools the agent can see.&lt;/li&gt;
&lt;li&gt;Constitutional rules check whether each action is permitted.&lt;/li&gt;
&lt;li&gt;OpenFGA verifies whether the agent has a legitimate relationship with the specific resource it is trying to touch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A misaligned agent has to pass through all three to cause real damage, and that is structurally impossible.&lt;/p&gt;
&lt;p&gt;Constitutional constraints will never anticipate every failure mode. But by pushing authorization down to the infrastructure level, you make the most catastrophic failures practically impossible rather than merely discouraged. If your agent reasons its way into thinking that &lt;a href="https://www.financialexpress.com/life/technology-ai-agent-just-destroyed-our-production-data-and-confessed-in-writing-founder-rings-alarm-bells-4219256/" rel="noreferrer noopener"&gt;deleting all production data&lt;/a&gt; would maximize some metric, the authorization layer stops it before that reasoning becomes an action.&lt;/p&gt;
&lt;p&gt;However, the catch is that you can only prevent failures you have anticipated. That is why this layer must be complemented by behavioral monitoring to catch unforeseen misalignment.&lt;/p&gt;
&lt;h2 id="Guardrail-Layer-2--Behavioral-Monitoring-and-Circuit-Breakers"&gt;Guardrail Layer 2: Behavioral Monitoring and Circuit Breakers&lt;/h2&gt;
&lt;p&gt;Continuous behavioral monitoring helps with misalignment you do not usually anticipate. It watches for anomalies in how your agent operates and shuts things down when behavior turns concerning.&lt;/p&gt;
&lt;h3&gt;Real-time action monitoring&lt;/h3&gt;
&lt;p&gt;Real-time action monitoring tracks what your agent does in production by watching for deviations from established baselines. An agent that normally takes three to five actions per task suddenly chaining twenty is worth investigating. It might be handling a legitimate edge case, or it might be going off the rails.&lt;/p&gt;
&lt;h3&gt;Decision path analysis&lt;/h3&gt;
&lt;p&gt;Decision path analysis examines the reasoning chains agents use to reach conclusions. If an agent's &lt;a href="https://www.allaboutai.com/ai-glossary/what-is-chain-of-thought/" rel="noreferrer noopener"&gt;chain-of-thought&lt;/a&gt; starts including phrases like "the rules do not apply here", that is a red flag. You need to catch goal-oriented reasoning that prioritizes objectives over constraints.&lt;/p&gt;
&lt;h3&gt;Anomaly detection&lt;/h3&gt;
&lt;p&gt;Anomaly detection compares current behavior against historical patterns. Track metrics like actions per task, API calls per operation, unique tool usage, and failure rates. Rolling averages over your last hundred operations give you a reliable baseline. When current behavior deviates significantly (typically two to three times that baseline), flag it for review.&lt;/p&gt;
&lt;h3&gt;Circuit breakers&lt;/h3&gt;
&lt;p&gt;Circuit breakers are automatic shutdown triggers. When monitoring detects concerning patterns, circuit breakers halt execution before damage accumulates. You define thresholds, and if they are crossed, the agent is stopped and requires human intervention to restart.&lt;/p&gt;
&lt;p&gt;A simplified circuit breaker might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CircuitBreaker&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__init__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;thresholds &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;"max_actions_per_minute"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;"max_failed_actions"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;"max_api_cost_per_hour"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;100.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;"max_consecutive_anomalies"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"closed"&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# closed = normal, open = breaker tripped  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;counters &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; key &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;thresholds&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;      
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check_and_update&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; metric&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"""Update counter and check if breaker should trip"""&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"open"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# Agent already stopped  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;          
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;counters&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;metric&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;          
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;thresholds&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;metric&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trip&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f"&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;metric&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; exceeded: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;thresholds&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;metric&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;          
&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;      
&lt;/span&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;trip&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; reason&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;"""Open circuit breaker and halt agent"""&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;"open"&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f"🛑 CIRCUIT BREAKER TRIPPED: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"Agent execution halted. Human review required."&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# In production: alert on-call, log to incident system, etc.  &lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Early detection prevents small misalignments from cascading. An agent optimizing metrics in slightly questionable ways will likely escalate if left unchecked. Circuit breakers interrupt that progression automatically, giving you time to investigate and correct before real damage occurs.&lt;/p&gt;
&lt;p&gt;However, setting a threshold is a balancing act. Too sensitive and you are constantly chasing benign anomalies. Too loose and you miss genuine misalignment until it is serious. It is best to start conservatively and tune thresholds against your agent's actual operational patterns over time.&lt;/p&gt;
&lt;h2 id="Guardrail-Layer-3--Alignment-Preserving-Architecture"&gt;Guardrail Layer 3: Alignment-Preserving Architecture&lt;/h2&gt;
&lt;p&gt;Monitoring catches misalignment only after it appears. The stronger guardrail is designing your system so that misalignment is less likely to occur in the first place.&lt;/p&gt;
&lt;h3&gt;Human-in-the-loop (HITL) checkpoints&lt;/h3&gt;
&lt;p&gt;Not every action needs human review. Routine, reversible actions can run autonomously. But irreversible operations (deleting data, modifying production systems, initiating financial transactions) should always pause for confirmation. The cost of a one-second approval is almost always lower than the cost of undoing a mistake you did not anticipate.&lt;/p&gt;
&lt;p&gt;Two questions determine where to draw this &lt;a href="https://auth0.com/blog/secure-human-in-the-loop-interactions-for-ai-agents/" rel="noreferrer noopener"&gt;HITL&lt;/a&gt; line: how reversible is the action, and how confident is the agent in its reasoning? High reversibility and high confidence can run autonomously. Anything else should escalate.&lt;/p&gt;
&lt;p&gt;Human checkpoints do more than catch mistakes. They create natural breakpoints for inspecting agent reasoning, catching goal drift early, and maintaining accountability for decisions made on behalf of your users.&lt;/p&gt;
&lt;h3&gt;Bounded autonomy patterns&lt;/h3&gt;
&lt;p&gt;Limit how far agents can operate independently before re-engaging a human. Three constraints worth building into your architecture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Operational scope:&lt;/strong&gt; Which actions the agent can perform without re-authorization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time horizons:&lt;/strong&gt; How long the agent can run before requiring check-ins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision depth:&lt;/strong&gt; How many chained actions the agent can take in a single run&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These boundaries ensure that the blast radius is limited if something goes wrong.&lt;/p&gt;
&lt;h3&gt;Balanced objectives&lt;/h3&gt;
&lt;p&gt;Single-metric optimization is one of the most common paths to misalignment. An agent told to "maximize user engagement" will find ways to do exactly that, including ways you never intended.&lt;/p&gt;
&lt;p&gt;The fix is to give agents multiple objectives that create natural tension. Instead of "maximize user engagement", try "maximize user engagement while keeping response time under 200ms and error rates below 1%". The agent cannot optimize one metric at the expense of the others.&lt;/p&gt;
&lt;h3&gt;Approval workflows and Auth0 Asynchronous Authorization&lt;/h3&gt;
&lt;p&gt;A well-designed escalation policy turns human oversight from a bottleneck into a safety net. High-impact and high-uncertainty situations escalate. Low-impact and low-uncertainty situations run autonomously. Everything in between uses confidence thresholds or stake-based routing.&lt;/p&gt;
&lt;p&gt;A customer service agent, for example, might handle routine queries autonomously but escalate refund requests over $100 or any action involving account-level changes, as illustrated in this workflow:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F01c5TdYNI2wQVDg7dluVDd%2Faf8a4277bf1ec26f24ba955687f3c3ea%2FCustomerServiceAgentWorkflowWithEscalationFlow.png" alt="Customer service agent workflow with escalation, courtesy of Manish Hatwalne" width="800" height="738"&gt;&lt;p&gt;This flowchart shows a customer service agent workflow with escalation logic. When a query arrives, the AI agent first determines whether it can handle it autonomously. Routine queries are resolved automatically and a response is sent. For more complex cases, a decision matrix evaluates the stakes and confidence level: refunds over $100 are escalated to a human agent, low technical confidence routes the query to tech support, and signs of customer frustration trigger escalation to a supervisor. All paths conclude with a response being sent to the customer.&lt;/p&gt;
&lt;p&gt;The practical challenge is its implementation. How does an agent pause mid-task and wait for a human response without blocking the entire system? This is exactly what &lt;a href="https://auth0.com/ai/docs/intro/asynchronous-authorization" rel="noreferrer noopener"&gt;Auth0's Asynchronous Authorization&lt;/a&gt; solves.&lt;/p&gt;
&lt;p&gt;Built on Client-Initiated Backchannel Authorization (CIBA), Asynchronous Authorization is Auth0's production-ready pattern for asynchronous human-in-the-loop authorization. Here is how the flow works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initiation:&lt;/strong&gt; The agent sends a CIBA request to Auth0's &lt;code&gt;bc-authorize&lt;/code&gt; endpoint, including a user identifier and an optional &lt;code&gt;authorization_details&lt;/code&gt; payload describing the action in plain, verifiable context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledgment:&lt;/strong&gt; Auth0 immediately returns a unique &lt;code&gt;auth_req_id&lt;/code&gt; to the agent, confirming the request is in process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling:&lt;/strong&gt; The agent begins polling Auth0's &lt;code&gt;token&lt;/code&gt; endpoint using the &lt;code&gt;auth_req_id&lt;/code&gt;, waiting for a human decision without blocking anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User consent:&lt;/strong&gt; In parallel, Auth0 pushes a notification (mobile push, email, or Slack) to the user's device with the full action context. The user approves or denies on their own time, on their own device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token issuance:&lt;/strong&gt; On approval, the next poll succeeds, and Auth0 returns the access and ID tokens the agent needs to complete the authorized action.&lt;/li&gt;
&lt;/ol&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F5BaEtybbxogD1R5Dgk6Jso%2Fd5688366334d7368ca4ee7652f064f37%2FAuth0AsynchronousAuthorizationflow.png" alt="Auth0's Asynchronous Authorization flow" width="687" height="800"&gt;&lt;p&gt;The sequence diagram above illustrates how Auth0's CIBA works within an AI agent workflow. The AI agent initiates the process by sending a backchannel authentication request to Auth0, which returns a request ID and pushes a notification to the designated human approver via mobile, email, or Slack. While the agent polls the token endpoint waiting for a decision, the approver reviews the context and either approves or denies the request. If approved, the agent proceeds with the action; if denied, it halts and logs the outcome.&lt;/p&gt;
&lt;p&gt;The agent waits. Nothing else in your system is blocked. The human is not pulled into a synchronous interruption. And because the approval goes through Auth0, you get a full audit trail of every request, approval, and denial without building any of that infrastructure yourself.&lt;/p&gt;
&lt;p&gt;This approach makes human-in-the-loop practical at scale. Escalation rules tell your agent when to ask for human oversight. CIBA handles how to ask in a way that actually works in production.&lt;/p&gt;
&lt;h3&gt;Architecture beats post-hoc fixes&lt;/h3&gt;
&lt;p&gt;These alignment-preserving architectural approaches treat agentic alignment as a design constraint rather than a monitoring problem. Building alignment into system architecture has a tradeoff: reduced autonomy. Your agent moves slower, and requires more human involvement. But for production systems where misalignment could cause real harm, this is the right tradeoff. You want agents that are reliably aligned, not maximally autonomous.&lt;/p&gt;
&lt;h2 id="Continuous-Evaluation"&gt;Continuous Evaluation&lt;/h2&gt;
&lt;p&gt;Here is how three guardrails prevent agentic misalignment:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F66nnkJ88zZ6UkNHrZy1S0m%2F03e4e828a9a277bcf061e21ce79e2c5f%2FGuardrailsPreventingAgenticMisalignmentFlow.png" alt="Guardrails preventing agentic misalignment, created by Manish Hatwalne" width="800" height="780"&gt;&lt;p&gt;This diagram shows how a three-layer guardrail system prevents agentic misalignment. When an agent proposes an action, it first passes through constitutional constraints, where any rule violation or blocked tool use stops the action immediately. Actions that pass move to behavioral monitoring, where anomalies or threshold breaches trip a circuit breaker. Actions exhibiting normal behavior reach the architecture design layer, where low-stakes actions meeting all constraints are executed directly, high-stakes actions require human approval before proceeding, and constraint violations are escalated for review. All outcomes, whether prevented, detected early, or approved and executed, feed into a safe operation state, with every executed action logged.&lt;/p&gt;
&lt;p&gt;After implementing these three guardrails, you are ready to ship to production. But alignment requires ongoing work. As models get more capable and encounter new edge cases, new misalignment risks emerge. Yesterday's guardrails might not hold tomorrow, so verify them to ensure your agent remains aligned, especially as models and tools evolve.&lt;/p&gt;
&lt;h3&gt;Red-teaming your agents&lt;/h3&gt;
&lt;p&gt;Red-teaming your agents implies trying to &lt;a href="https://www.hackthebox.com/blog/ai-red-teaming-explained" rel="noreferrer noopener"&gt;break alignment&lt;/a&gt; deliberately, instead of waiting for misalignment to appear organically. Create a test suite to stress-test boundaries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal conflicts:&lt;/strong&gt; Give your agent an objective that conflicts with organizational policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource constraints:&lt;/strong&gt; Limit access to preferred tools and see how it adapts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metric gaming opportunities:&lt;/strong&gt; Present situations where gaming metrics are easier than genuine achievement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ethical dilemmas:&lt;/strong&gt; Force choices between efficiency and safety.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run these scenarios regularly, especially after model updates. Track how often your agent attempts problematic behaviors, even when guardrails block them. A rise in blocked attempts signals growing misalignment pressure worth investigating.&lt;/p&gt;
&lt;p&gt;Transparent and auditable systems make alignment verification possible. Every agent decision should leave a trail: the reasoning chain, alternatives considered, constraints checked, and final action taken. This audit log helps you diagnose why misalignment occurred, supports compliance requirements, and lets you spot patterns across incidents before they compound.&lt;/p&gt;
&lt;p&gt;In healthcare or finance, informal testing may not be enough. &lt;a href="https://arxiv.org/pdf/2309.01933" rel="noreferrer noopener"&gt;Formal verification of AI agents&lt;/a&gt; is still an active area of research and not yet practical to implement broadly, but it is worth watching closely if you operate in high-stakes domains.&lt;/p&gt;
&lt;h2 id="Wrapping-Up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;AI is rapidly becoming as ubiquitous as electricity. However, the technology is advancing faster than our understanding of it. We are already building AI agents that can plan our vacations and book our flights quickly. But are they safe enough not to leak our financial information, or worse, not to misuse it themselves?&lt;/p&gt;
&lt;p&gt;When developers only test the "happy path" and assume their agents will always stay on the ethical track, they are often being oblivious to the Pandora's box these AI systems could open unintentionally. As several real-life incidents have shown, an AI agent going rogue is no longer a hypothetical threat.&lt;/p&gt;
&lt;p&gt;In medicine, doctors are guided by a foundational principle: "First, do no harm." AI development needs the same commitment. Innovation that comes at the expense of safety or ethics is not innovation worth shipping, no matter how often guardrails are dismissed as obstacles that slow things down. The three guardrails give you the building blocks to ensure your agents pursue the right goals, in the right ways, within boundaries that hold under pressure.&lt;/p&gt;
&lt;p&gt;If you're looking to put these guardrails into practice, Auth0's Asynchronous Authorization and OpenFGA integrations give you production-ready building blocks for async human-in-the-loop authorization and relationship-based access control. &lt;a href="https://a0.to/blog_signup" rel="noreferrer noopener"&gt;Sign up for Auth0 for AI agents&lt;/a&gt; and &lt;a href="https://auth0.com/ai/docs/intro/overview" rel="noreferrer noopener"&gt;start with the Auth0 for AI Agents docs&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>agents</category>
      <category>security</category>
      <category>ethicalai</category>
    </item>
    <item>
      <title>"You Pressed Accept and Forgot About It." That's the Flaw.</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Wed, 30 Sep 2026 15:30:00 +0000</pubDate>
      <link>https://dev.to/auth0/you-pressed-accept-and-forgot-about-it-thats-the-flaw-41b3</link>
      <guid>https://dev.to/auth0/you-pressed-accept-and-forgot-about-it-thats-the-flaw-41b3</guid>
      <description>&lt;p&gt;If you've been building with AI agents, you've probably had this experience: the agent needs access to something, a browser popup appears, you glance at it for a second, and you click Allow without really reading it. As &lt;a href="https://www.linkedin.com/in/miguel-pedregosa/" rel="noopener noreferrer"&gt;&lt;strong&gt;Miguel Pedregosa&lt;/strong&gt;&lt;/a&gt;  points out, this is a huge mistake that results in a security flaw. He's a Senior Software Engineer on the Protocols team at Auth0, the team that owns the identity standards implementation that millions of developers rely on without thinking about it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The door you've opened without reading.&lt;/strong&gt; Each browser auth flow you click through is a permission you approved without knowing what you approved, and those authorizations may be happening entirely outside your enterprise identity provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The protocol that replaces the popup.&lt;/strong&gt; Cross-App Access routes agent authorization through your existing enterprise identity provider. The agent gets an access token through the back channel, with no browser required, and full audit visibility for IT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementing a standard that's still being written.&lt;/strong&gt; Miguel's team launched the Cross-App Access beta while the IETF spec was in draft. The spec shifted, they shifted with it, and ended up feeding back directly into the standard itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What enterprises ask for that nobody else thinks about.&lt;/strong&gt; A startup asks "does it work?" An enterprise asks "does it work with our 40-year-old system?" That gap drives more of what the protocols team builds than anything else.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Threat modeling without reading an RFC.&lt;/strong&gt; What can each part of my system access? How and when? If this agent hallucinates, does it have access to everything? Miguel says those three questions are enough to start.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Things that stuck with me
&lt;/h2&gt;

&lt;p&gt;When you click “Allow” on five consecutive prompts without reading them, you're approving access you can't really explain, and depending on how the agent is set up, those authorizations may be happening outside the identity system your company manages. &lt;/p&gt;

&lt;p&gt;I assumed implementing a standard meant reading a spec and building to it, but it turns out early drafts could have gaps, and companies implementing early get to flag them. Sometimes those flags become changes to the spec. Miguel described it as one of the more fun parts of the project, which surprised me, and it makes sense once you hear why.&lt;/p&gt;

&lt;p&gt;The threat modeling advice works because it requires no identity expertise. Just map what your system can touch and ask what happens if part of it misbehaves. The answers are often straightforward, and the hard part is remembering to ask before you ship.&lt;/p&gt;

&lt;p&gt;You're probably already building agents, so the question is whether the security is something you thought through or something you clicked past.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you ever looked back at an agent you shipped and realized it had more access than it needed?&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>podcast</category>
      <category>security</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
