<?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>How to Add Auth0 Authentication to Vercel v0 Apps (Step-by-Step)</title>
      <dc:creator>Auth0 DevRel</dc:creator>
      <pubDate>Mon, 02 Nov 2026 05:00:00 +0000</pubDate>
      <link>https://dev.to/auth0/how-to-add-auth0-authentication-to-vercel-v0-apps-step-by-step-30d4</link>
      <guid>https://dev.to/auth0/how-to-add-auth0-authentication-to-vercel-v0-apps-step-by-step-30d4</guid>
      <description>&lt;p&gt;Check out this tutorial from Staff Developer Advocate, Michael Liendo on how to build production-ready apps with Auth0 and v0. We launched a new Auth0 integration for v0 that lets builders add real authentication to the applications they build through prompts. Ask v0 to add login, protect a route, support B2B use-cases, connect an enterprise identity provider, or require MFA, and v0 sets up Auth0 instead of generating a placeholder authentication screen.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;How to prompt v0 to generate a functional app with the Google Pose SDK&lt;/li&gt;
&lt;li&gt;Publishing and testing your v0 app on mobile with custom domain options&lt;/li&gt;
&lt;li&gt;Integrating Auth0 into your v0 project for quick, secure authentication&lt;/li&gt;
&lt;li&gt;Setting up Google Social Sign-In and database features using natural language prompts&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://a0.to/v0" rel="noopener noreferrer"&gt;🛠️ Try the new Auth0 with v0 by Vercel integration&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 our "Using Auth0" playlist for more guides&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>The Token That Survived the Patch: An Auth Horror Story 🎃</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 30 Oct 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/auth0/the-token-that-survived-the-patch-an-auth-horror-story-2o7o</link>
      <guid>https://dev.to/auth0/the-token-that-survived-the-patch-an-auth-horror-story-2o7o</guid>
      <description>&lt;p&gt;Pull up a log and get close to the fire 🪵🔥. &lt;/p&gt;

&lt;p&gt;In this special Halloween tech horror story, we dissect a real-world developer nightmare: an artifact registry compromised through a chain of small flaws, where a patched vulnerability left an active admin backdoor behind. &lt;/p&gt;

&lt;p&gt;The scariest breaches aren't always the ones you miss—they are the ones you believe you already resolved. A software patch closes the hole in your code, but it does not revoke the credentials minted while that hole was open. We break down how silent persistence works in CI/CD dependencies and examine the incident response steps needed to secure your infrastructure after an exploit.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Laptop Might Replace Most of Your AI Bill</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:58:00 +0000</pubDate>
      <link>https://dev.to/auth0/your-laptop-might-replace-most-of-your-ai-bill-4o1f</link>
      <guid>https://dev.to/auth0/your-laptop-might-replace-most-of-your-ai-bill-4o1f</guid>
      <description>&lt;p&gt;I've never run an AI model on my own machine, and I assumed the cloud was the best way to get good agentic coding. Then I talked to &lt;a href="https://deepu.tech/" rel="noopener noreferrer"&gt;&lt;strong&gt;Deepu K Sasidharan&lt;/strong&gt;&lt;/a&gt;, a Principal Developer Advocate at Auth0 and long-time open source maintainer, who now does most of his coding with a model running on his laptop. He's also the creator of &lt;a href="https://github.com/llamastash/llamastash" rel="noopener noreferrer"&gt;LlamaStash&lt;/a&gt;, a Rust tool for running local models.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;From skeptic to local-first.&lt;/strong&gt; Initially, Deepu tried every new model version and wasn't so impressed, until one found bugs in code he had written by hand. That was the moment he stopped resisting and started experimenting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local models vs. frontier models.&lt;/strong&gt; By his own benchmark, a local model on a 128GB laptop lands in the same range as a top cloud model on a complex task, with similar speed apart from a roughly twenty-second wait on the first big prompt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why he built his own tool.&lt;/strong&gt; Ollama felt slow and LM Studio didn't give him enough control, so an initial bash script turned into LlamaStash, a Rust CLI and terminal UI that can run any engine and model combination.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The engineer as architect and manager.&lt;/strong&gt; He doesn't write most of the code anymore. He plans, sets requirements, and manages a few agents, and he says he doesn't miss the old way.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I asked Deepu to explain llama.cpp like I'd never heard of it. It's the engine that loads the model file into your GPU or RAM and does the computation, and tools like Ollama are wrappers on top of it. His suggestion was to try LlamaStash's &lt;code&gt;init&lt;/code&gt; command, or LM Studio if you prefer a GUI.&lt;/p&gt;

&lt;p&gt;The part I keep coming back to is how he sets up his harness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One setup, two harnesses.&lt;/strong&gt; He uses a different harness for the cloud model and for the local model, and they share the same rules file, plugins, and memory location through symlinks. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud for planning, local for building.&lt;/strong&gt; An orchestrator skill has the cloud model write the plan and do the final review, while local agents implement it in parallel. A feature that would have cost around $20 on cloud alone now costs about $1, since the local part is just electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviews from many angles.&lt;/strong&gt; Three review rounds can look at a change from twelve angles, including security, correctness, and an adversarial one, which is more than one person skimming a diff would do. If problems remain after three rounds, it stops and tells him, and he says it's usually his own unclear requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small context on purpose.&lt;/strong&gt; A huge context window doesn't need to be filled, and the more you fill it, the slower and less accurate the model gets.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Deepu's setup isn't about picking local over cloud. It's about deciding what runs where, and staying in the seat of the person who sets the requirements and checks the work. Local models make that choice yours, including what happens to your code, your prompts, and your secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Would you give a local model the task of doing most of your coding?&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>llm</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>7 Auth0 Free Tier Limits You Must Know Before Building</title>
      <dc:creator>Auth0 DevRel</dc:creator>
      <pubDate>Mon, 26 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/auth0/7-auth0-free-tier-limits-you-must-know-before-building-1d92</link>
      <guid>https://dev.to/auth0/7-auth0-free-tier-limits-you-must-know-before-building-1d92</guid>
      <description>&lt;p&gt;Building on Auth0's free tier? While it is a powerful way to get started with identity management, hitting unexpected limits during production can break your system architecture. In this video, we break down the 7 things you must know before designing your auth flow around the Auth0 free plan.   &lt;/p&gt;

&lt;p&gt;Pricing is subject to change, please use  &lt;a href="https://auth0.com/pricing" rel="noopener noreferrer"&gt;https://auth0.com/pricing&lt;/a&gt; as the ultimate source of truth for pricing. &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Organizations: Managing the 5-organization cap across environments.
&lt;/li&gt;
&lt;li&gt;Enterprise Connections: How SAML, OIDC, and directory limits work (plus SAML vs. Social Logins).
&lt;/li&gt;
&lt;li&gt;Custom Domains: Enabling custom domains on the free plan without paying extra.
&lt;/li&gt;
&lt;li&gt;Password Hash Migration: Why lazy migration requires a custom database connection.
&lt;/li&gt;
&lt;li&gt;Fine-Grained Authorization (FGA): How resource-level permissions are billed separately.
&lt;/li&gt;
&lt;li&gt;Adaptive MFA: Understanding risk-based MFA vs. standard MFA availability.
&lt;/li&gt;
&lt;li&gt;Tenant Deletion: How to protect side projects and inactive tenants from automated cleanup.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related Resources:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=eR5ETQG6rbE&amp;amp;pp=0gcJCR0AztywvtLA" rel="noopener noreferrer"&gt;🎬 How to Set Up an Auth0 Custom Domain video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/pricing" rel="noopener noreferrer"&gt;📚 Auth0 Pricing &amp;amp; Tier Breakdown Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>saas</category>
    </item>
    <item>
      <title>Tools vs. MCP vs. Skills Explained</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Mon, 19 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/auth0/tools-vs-mcp-vs-skills-explained-2a4d</link>
      <guid>https://dev.to/auth0/tools-vs-mcp-vs-skills-explained-2a4d</guid>
      <description>&lt;p&gt;We follow Alice, a developer at Acme Corp, to demonstrate how missing any of these layers leads to multi-platform integration headaches and expensive $18,000 security flaws.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🧠 WHAT YOU'LL LEARN:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why AI tools act as the "hands" that allow models to produce structured intent and call functions&lt;/li&gt;
&lt;li&gt;How Model Context Protocol (MCP) acts as the "USB-C for AI," decoupling tools from specific client implementations&lt;/li&gt;
&lt;li&gt;How AI Skills provide the "brain" and recipes to enforce business logic, judgment, and guardrails&lt;/li&gt;
&lt;li&gt;Security risks across each layer—from prompt injection and tool poisoning to missing human-in-the-loop controls&lt;/li&gt;
&lt;li&gt;How to implement OAuth 2.1 authorization and scoping for secure remote MCP servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;🛠️ RESOURCES &amp;amp; LINKS:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLZ14qQz3cfJJjtFCuzSUzTtvRtMyd8U7l" rel="noopener noreferrer"&gt;The Confused Developer Playlist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/what-ai-tools-mcp-servers-and-skills-actually-do/" rel="noopener noreferrer"&gt;Full Blog Post&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, &lt;a href="https://www.youtube.com/@Auth0" rel="noopener noreferrer"&gt;subscribe to our channel&lt;/a&gt;! &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>architecture</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>On-Behalf-Of Is a Pattern. Token Exchange Is the Standard.</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:08:39 +0000</pubDate>
      <link>https://dev.to/auth0/on-behalf-of-is-a-pattern-token-exchange-is-the-standard-4h2a</link>
      <guid>https://dev.to/auth0/on-behalf-of-is-a-pattern-token-exchange-is-the-standard-4h2a</guid>
      <description>&lt;p&gt;Consider a web API that needs to call a second API, and the second API needs to know whose data it is accessing. The obvious shortcut is to just forward the user's original access token to the downstream service. Do not do this. Relaying a token to a party it was not issued for breaks the trust boundary the token was scoped to in the first place. The middle-tier service needs its own token, one that still carries the user's identity, without reusing the user's actual credential.&lt;/p&gt;

&lt;p&gt;This is the problem that On-Behalf-Of (OBO) flows address. But it is worth being precise about what the On-Behalf-Of name actually refers to, because the confusion that follows is not really about competing flows. On-Behalf-Of is an architectural pattern: a label for the shape of this problem, not a protocol for solving it. &lt;a href="https://datatracker.ietf.org/doc/html/rfc8693" rel="noopener noreferrer"&gt;OAuth 2.0 Token Exchange (RFC 8693)&lt;/a&gt; is the IETF-ratified standard eventually built to implement that pattern in an interoperable way. &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;Microsoft's own "OBO" flow&lt;/a&gt; is a third thing again: a specific, non-standardized implementation of the pattern that predates the standard and is not compliant with it.&lt;/p&gt;

&lt;p&gt;Keep those three apart: the pattern, the standard, and a vendor's implementation. Most of the confusion in this space will then resolve on its own. Including the part where RFC 8693 implementations are named "On-Behalf-Of" and people assume that means they are all doing what Microsoft does.&lt;/p&gt;

&lt;p&gt;Let's work through the pattern first, then the one legacy implementation worth understanding on its own terms, then the standard that formalized it, then the part neither one was designed to answer: explicit delegation from the user to the downstream service or agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: A Middle-Tier Service Needs to Act as the User
&lt;/h2&gt;

&lt;p&gt;Three actors show up in every version of this scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;client&lt;/strong&gt;, which is the application the user is interacting with,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;middle-tier API&lt;/strong&gt;, i.e., a service that received the user's request and now needs data from somewhere else,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;downstream API&lt;/strong&gt;: the service that actually holds that data. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client authenticated once and got a token for the middle-tier API. Now the middle-tier API needs a token for the downstream API, and it needs that token to represent the user, not itself.&lt;/p&gt;

&lt;p&gt;This rules out two tempting shortcuts: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forwarding the client's original token downstream is unsafe, since that token's audience is the middle-tier API, not the downstream one.&lt;/li&gt;
&lt;li&gt;Having the middle-tier API call the downstream API with its own service credentials solves the audience problem but creates a worse one: the downstream API can no longer tell whether the actual user is authorized to see this data, because all it sees is the middle tier's own identity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is needed is &lt;strong&gt;delegation&lt;/strong&gt;: a new token that identifies the user as the subject of the request, while also recording that the middle-tier API is the one making the call. On-Behalf-Of is the name for exactly that pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  OBO as the Pattern Implementation
&lt;/h2&gt;

&lt;p&gt;Microsoft's implementation (the one most developers mean when they say "OBO") is the &lt;a href="https://web.archive.org/web/20190120232437/https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;oldest widely-deployed answer&lt;/a&gt; to the problem above, and it's worth understanding on its own terms, since a lot of production systems still run on it. It follows a fixed sequence, as shown in this diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq0u8gsut0qnwv00m4hvs.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%2Fq0u8gsut0qnwv00m4hvs.png" alt="Microsoft's OBO flow diagram" width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Following the numbered steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client authenticates and requests an access token.&lt;/li&gt;
&lt;li&gt;The token endpoint issues &lt;strong&gt;Token A&lt;/strong&gt;, scoped to the middle-tier API.&lt;/li&gt;
&lt;li&gt;The client calls the middle-tier API using Token A.&lt;/li&gt;
&lt;li&gt;The middle-tier API authenticates itself to the token endpoint and requests a new token for the downstream API.&lt;/li&gt;
&lt;li&gt;The token endpoint validates the middle-tier API's own credentials and Token A&lt;/li&gt;
&lt;li&gt;The token endpoint issues &lt;strong&gt;Token B&lt;/strong&gt;, scoped to the downstream API.&lt;/li&gt;
&lt;li&gt;The middle-tier API calls the downstream API with Token B.&lt;/li&gt;
&lt;li&gt;The downstream API responds&lt;/li&gt;
&lt;li&gt;The middle-tier API returns the result to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Microsoft's implementation, a minimal request looks something like this (&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow#first-case-access-token-request-with-a-shared-secret" rel="noopener noreferrer"&gt;shared-secret variant&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/oauth2/v2.0/token&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;login.microsoftonline.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&amp;amp;client_id={middle-tier-api-client-id}
&amp;amp;client_secret={middle-tier-api-client-secret}
&amp;amp;assertion={token-a}
&amp;amp;scope=https://downstream-api.example.com/.default
&amp;amp;requested_token_use=on_behalf_of
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, this implementation only works for &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-on-behalf-of-flow#client-limitations" rel="noopener noreferrer"&gt;user principals&lt;/a&gt;. If the original token represents an app calling with its own client credentials rather than a signed-in user, there is no user identity to carry forward, and this flow is not the right tool (client credentials chaining is).&lt;/li&gt;
&lt;li&gt;Second, Token B should never be relayed back to the client. It was issued for the downstream API's audience, and sending it anywhere else turns a scoped delegation into exactly the kind of token-forwarding problem the pattern exists to prevent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most common place this shows up in practice is a chain of calls into Microsoft Graph or another Entra-protected resource: a web app calls its own backend, the backend needs Graph data scoped to the signed-in user, and this flow produces the Graph-scoped token without ever exposing the user's original session token to the backend's downstream dependencies.&lt;/p&gt;

&lt;p&gt;A less obvious but genuinely useful variant: this implementation's nonstandard extension for minting SAML assertions, which some SSO integrations still expect instead of an OAuth 2.0 access token. Rather than running a parallel SAML identity provider alongside Entra ID, a service can exchange a user's OAuth token for a SAML assertion scoped to the legacy downstream service. That bridges two protocol generations without forcing a full migration. That is a legacy-protocol bridge specific to this implementation, not something a standardized approach needs to replicate, since most RFC 8693 deployments are already OAuth2-to-OAuth2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token Exchange: The Standard That Formalizes the Pattern
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0 Token Exchange is the IETF-ratified, vendor-neutral protocol for the same delegation pattern described above. It defines its own grant type (&lt;code&gt;grant_type=urn:ietf:params:oauth:grant-type:token-exchange&lt;/code&gt;). It uses a &lt;code&gt;subject_token&lt;/code&gt; to carry the user's identity forward and an optional &lt;code&gt;actor_token&lt;/code&gt; to record who is making the call. Unlike Microsoft's implementation, it also specifies an explicit, chainable &lt;code&gt;act&lt;/code&gt; claim: &lt;strong&gt;each hop in a multi-service call can be nested inside the previous one, so a downstream service can see the full path a request traveled.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will not go into the details of how Token Exchange works here. For the full tour of Auth0's RFC 8693 implementation, take a look at &lt;a href="https://auth0.com/blog/the-many-faces-of-oauth2-token-exchange/" rel="noopener noreferrer"&gt;The Many Faces of OAuth 2.0 Token Exchange&lt;/a&gt; and &lt;a href="https://auth0.com/blog/developers-guide-flexible-token-exchange-auth0/" rel="noopener noreferrer"&gt;A Developer's Guide to Flexible Token Exchange on Auth0&lt;/a&gt; blog posts, and read the &lt;a href="https://auth0.com/docs/secure/call-apis-on-users-behalf/on-behalf-of-token-exchange" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The point that matters here is narrower: Token Exchange is nit a rival to On-Behalf-Of. It is what the pattern looks like once someone wrote it down as an interoperable standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Implements What
&lt;/h2&gt;

&lt;p&gt;The pattern now has two implementations, and which one you can reach for depends on your identity provider: Auth0, Okta, and other identity products implement RFC 8693 directly. Microsoft Entra ID and other products do nit. They still require the older, vendor-specific &lt;code&gt;jwt-bearer&lt;/code&gt; grant or use other custom implementations instead.&lt;/p&gt;

&lt;p&gt;This is also where the naming confusion actually lives. Several vendors, Auth0 included, have built an RFC 8693 implementation and named it "On-Behalf-Of." &lt;a href="https://auth0.com/docs/secure/call-apis-on-users-behalf/on-behalf-of-token-exchange" rel="noopener noreferrer"&gt;Auth0's On-Behalf-Of (OBO) Token Exchange&lt;/a&gt; uses &lt;code&gt;grant_type=urn:ietf:params:oauth:grant-type:token-exchange&lt;/code&gt;, the actual RFC 8693 grant, nesting the &lt;code&gt;act&lt;/code&gt; claim up to five levels deep. That's not a second, competing meaning of "OBO." It is the pattern's name attached to a standardized implementation of it, the same way Microsoft's flow is the pattern's name attached to a non-standardized one. Do nit assume which implementation you are reading about from the name alone. Check the actual &lt;code&gt;grant_type&lt;/code&gt; in the vendor's documentation.&lt;/p&gt;

&lt;p&gt;The practical decision rule survives all of this naming confusion intact: check what grant type your identity provider actually implements before assuming "On-Behalf-Of" describes one specific wire protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is There for AI Agent Delegation?
&lt;/h2&gt;

&lt;p&gt;Both the pattern and its standardized protocol assume a user token already exists to exchange. Neither one has &lt;a href="https://auth0.com/ai/docs/intro/delegated-authorization" rel="noopener noreferrer"&gt;a front-channel step where a user explicitly consents to a &lt;em&gt;named agent&lt;/em&gt; acting on their behalf&lt;/a&gt;. The exchange happens entirely back-channel, between services that already trust each other, with no moment where the user sees "Agent X wants to act for you" and approves it specifically.&lt;/p&gt;

&lt;p&gt;From the downstream service's point of view, the trail has traditionally looked the same whether a human, a cron job, or an autonomous agent made the call. That is starting to change on the audit side: Auth0's &lt;a href="https://auth0.com/docs/ai-agents-mcp/agent-as-principal" rel="noopener noreferrer"&gt;Agent as Principal&lt;/a&gt; feature (currently Early Access) lets a tenant register a persistent agent identity and surface it in the &lt;code&gt;act&lt;/code&gt; claim during token exchange, so a resource server that opts in can tell which specific agent acted, not just which client called. But that is a back-channel improvement to the audit trail, not a front-channel one to consent. Delegation flows still were not designed to let a user approve 'this specific agent' before the fact, and audit trails still cannot tell you whether the agent was authorized to decide to act in the first place, only that it did.&lt;/p&gt;

&lt;p&gt;A few individual IETF drafts aim to address this gap. Although none have reached consensus status yet, they are worth tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/draft-oauth-ai-agents-on-behalf-of-user/" rel="noopener noreferrer"&gt;On-Behalf-Of User Authorization for AI Agents&lt;/a&gt; proposes a &lt;code&gt;requested_actor&lt;/code&gt; parameter at the authorization request and an &lt;code&gt;actor_token&lt;/code&gt; parameter at the token request, so the specific agent's identity gets captured and consented to explicitly. It is front-channel, not back-channel, which is exactly the piece the existing pattern and standard both lack.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/draft-mcguinness-oauth-actor-profile/" rel="noopener noreferrer"&gt;OAuth Actor Profile for Delegation&lt;/a&gt; standardizes the &lt;em&gt;shape&lt;/em&gt; of the &lt;code&gt;act&lt;/code&gt; claim across implementations, without dictating authorization policy on top of it. By the way, this is the draft currently adopted by Auth0 for its Agent as Principal feature.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/draft-niyikiza-oauth-attenuating-agent-tokens/" rel="noopener noreferrer"&gt;Attenuating Authorization Tokens&lt;/a&gt; lets an agent derive a narrower token offline rather than round-tripping to an authorization server for every hop, trading central mediation for independence. That trade-off matters for multi-hop agent chains,  i.e., for long chains of agents calling other agents. In this context, RFC 8693's synchronous exchange-per-hop model starts to add real latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is an individual submission, not a working-group draft, and individual submissions carry no formal standing in the IETF process. Some of them have already expired or are about to expire at the time of writing. This is an area where the standards conversation is active but genuinely unsettled.&lt;/p&gt;

&lt;p&gt;However, the &lt;a href="https://datatracker.ietf.org/wg/wimse/about/" rel="noopener noreferrer"&gt;IETF's WIMSE working group&lt;/a&gt; is now pursuing further standardization in this general space: the &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-wimse-arch/" rel="noopener noreferrer"&gt;Workload Identity in a Multi System Environment (WIMSE) Architecture&lt;/a&gt;. It is early there as well, but it is the group to watch if you want to track where this settles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/ai/docs/mcp/intro/overview" rel="noopener noreferrer"&gt;MCP (Model Context Protocol)&lt;/a&gt; illustrates the practical shape of the problem. Map MCP's own actors onto OAuth 2.0 roles and the pattern is familiar: the MCP server is the resource server, the agent runtime is the client, the host application is the OAuth 2.0 client orchestrating the whole thing. But MCP itself treats authorization as optional and defers identity and delegation entirely to whatever external identity provider sits behind it. That mapping clarifies the protocol roles. It does nit tell you which user a given agent is acting for, and that remains the integrator's responsibility to solve, not something the spec hands you.&lt;/p&gt;

&lt;p&gt;This is nit a problem unique to agents acting on a user's behalf, either. It rhymes with the &lt;a href="https://auth0.com/blog/confused-deputy-the-old-bug-that-ai-agents-keep-reintroducing" rel="noopener noreferrer"&gt;confused deputy problem&lt;/a&gt;, the decades-old pattern where a privileged service gets tricked into misusing its own authority because it cannot tell a legitimate instruction from an injected one. Granting an agent broad delegated authority without a clear record of who authorized what just gives that old bug a new, more capable host.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Actually Do Today
&lt;/h2&gt;

&lt;p&gt;Do not wait for the drafts above to stabilize. Here os what holds up regardless of how that standards conversation resolves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reach for the standard, RFC 8693 Token Exchange, wherever your identity provider supports it.&lt;/strong&gt; Fall back to the vendor-specific &lt;code&gt;jwt-bearer&lt;/code&gt; pattern only where the vendor forces it. Check the actual grant type in the docs, not just the feature name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log the &lt;code&gt;act&lt;/code&gt; claim, or your provider's closest equivalent, on every delegated token&lt;/strong&gt;, even if your downstream systems cannot natively consume it yet. Record actor, task, and approver at the application layer if the token itself will not carry it. &lt;a href="https://auth0.com/blog/closing-audit-gap-human-to-agent-delegation" rel="noopener noreferrer"&gt;Closing the audit gap in human-to-agent delegation&lt;/a&gt; covers concrete ways to do this with correlation IDs and approval checkpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue one credential per agent per purpose.&lt;/strong&gt; Never let an agent hold or relay a human's raw token. If you are building agent-facing infrastructure, a mechanism like &lt;a href="https://auth0.com/ai/docs/intro/token-vault" rel="noopener noreferrer"&gt;Auth0's Token Vault&lt;/a&gt; is designed around exactly this constraint: managed, auditable, per-agent tokens for third-party API access. Auth0's &lt;a href="https://auth0.com/blog/agent-as-principal-purpose-built-identity-for-agents" rel="noopener noreferrer"&gt;Agent as Principal&lt;/a&gt; goes a step further by giving each agent its own registered identity with a stable ID, rather than relying on a shared M2M client or service account to stand in for every agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope every delegated token to the specific task&lt;/strong&gt;, not the full permission set of whatever credential is doing the calling. A narrower token limits the blast radius if an agent misbehaves or gets tricked into misusing its own authority.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the audit trail now, not when the standards settle.&lt;/strong&gt; Regulatory transparency requirements around automated decision-making are already in effect in a lot of jurisdictions, and historical logs cannot be reconstructed after the fact. Whatever you build today to answer "which agent did this, on whose authority" is worth more than a theoretically cleaner version you build next year.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Pattern, the Standard, and the Identity Question
&lt;/h2&gt;

&lt;p&gt;Consider the middle-tier API from the opening scenario. The primary question, how to invoke a downstream service using the user's identity without exposing their actual token, has been resolved for years. It began informally through patterns like On-Behalf-Of and is now standardized via RFC 8693 Token Exchange. The specific implementation depends on your identity provider, but the core design strategy is well established.&lt;/p&gt;

&lt;p&gt;However, AI agents introduce new complexity. The question is no longer simply whether a service can act on behalf of a user, but whether a specific, designated agent has explicit user consent and an auditable record of that authorization. Neither OBO nor Token Exchange was designed for this capability, and the proposed drafts addressing it have not yet achieved IETF consensus. Until standards emerge, this gap must be managed through detailed logging, fine-grained scoping, and dedicated per-agent credentials.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>tokenexchange</category>
      <category>obo</category>
      <category>agents</category>
    </item>
    <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>
  </channel>
</rss>
