<?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>Authentication and Authorization: What's New in .NET 11</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Mon, 09 Nov 2026 14:13:04 +0000</pubDate>
      <link>https://dev.to/auth0/authentication-and-authorization-whats-new-in-net-11-353l</link>
      <guid>https://dev.to/auth0/authentication-and-authorization-whats-new-in-net-11-353l</guid>
      <description>&lt;p&gt;When it comes to authentication and authorization, .NET 11 focuses on valuable refinements. It addresses key edge cases to enhance security and user experience: a WebSocket that outlives its access token, an authorization attribute that works on one endpoint type but not on another, a passkey list full of identical "Passkey" labels. Let's look at what the newest release brings to authentication and authorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh Authentication on Live SignalR Connections
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-11.0" rel="noopener noreferrer"&gt;SignalR&lt;/a&gt; connection can live for hours, while an access token usually doesn't. Until now, you had two unattractive options when the token expired: close the connection and make the client reconnect (missing messages in the gap), or leave it open and keep trusting an identity that is no longer valid.&lt;/p&gt;

&lt;p&gt;.NET 11 adds a third option: &lt;strong&gt;authentication refresh&lt;/strong&gt;. The client sends the old token over a dedicated &lt;code&gt;/refresh&lt;/code&gt; endpoint, and the server swaps the connection's &lt;code&gt;ClaimsPrincipal&lt;/code&gt; in place.&lt;/p&gt;

&lt;p&gt;You apply authentication refresh when you map your SignalR hub, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MapHub&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatHub&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CloseOnAuthenticationExpiration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnableAuthenticationRefresh&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting &lt;code&gt;EnableAuthenticationRefresh&lt;/code&gt; adds &lt;code&gt;/chat/refresh&lt;/code&gt; next to the existing &lt;code&gt;/negotiate&lt;/code&gt; route. If you pair it with &lt;code&gt;CloseOnAuthenticationExpiration&lt;/code&gt;, then a connection that fails to refresh in time is closed instead of lingering with stale credentials.&lt;/p&gt;

&lt;p&gt;If you run a reverse proxy or a web application firewall, remember to allow the new route. A proxy that forwards &lt;code&gt;/negotiate&lt;/code&gt; but blocks &lt;code&gt;/refresh&lt;/code&gt; will make every refresh fail.&lt;/p&gt;

&lt;p&gt;The server doesn't blindly accept whatever token the client sends. By default, it compares the &lt;code&gt;sub&lt;/code&gt; claim (falling back to &lt;code&gt;ClaimTypes.NameIdentifier&lt;/code&gt; or &lt;code&gt;ClaimTypes.Upn&lt;/code&gt;) of the old and new principals. A refresh that changes the subject is rejected with a 403 status code, so a connection can't quietly turn into a different user.&lt;/p&gt;

&lt;p&gt;You can add your own checks with &lt;code&gt;OnAuthenticationRefresh&lt;/code&gt;. It can reject a refresh, but it can't approve one that fails the built-in check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnAuthenticationRefresh&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Keep the connection inside the tenant it started in&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sameTenant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tenant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"contoso"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sameTenant&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;On the client side, the .NET client refreshes automatically by default. It asks the server how much lifetime the token has left and schedules a refresh ahead of expiration (five minutes before, unless you change it). Each refresh calls your &lt;code&gt;AccessTokenProvider&lt;/code&gt; again, so the provider is where you fetch a fresh token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HubConnectionBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/chat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessTokenProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GetAccessTokenAsync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithAuthenticationRefresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RefreshBeforeExpiration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use Auth0, &lt;code&gt;GetAccessTokenAsync&lt;/code&gt; is where you'd return a cached &lt;a href="https://auth0.com/docs/secure/tokens/access-tokens" rel="noopener noreferrer"&gt;access token&lt;/a&gt; or use a &lt;a href="https://auth0.com/docs/secure/tokens/refresh-tokens" rel="noopener noreferrer"&gt;refresh token&lt;/a&gt; to get a new one. The JavaScript client has the same capability through &lt;code&gt;withAuthenticationRefresh&lt;/code&gt; and &lt;code&gt;accessTokenFactory&lt;/code&gt;. &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-11.0" rel="noopener noreferrer"&gt;Blazor Server&lt;/a&gt; also benefits: interactive circuits receive the refreshed identity without reconnecting, and &lt;code&gt;AuthorizeView&lt;/code&gt; re-renders when the roles or permissions change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use One Authorization Model Everywhere
&lt;/h2&gt;

&lt;p&gt;You can write a custom attribute that implements &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/security/authorization/custom-authorization-policies-with-iauthorizationrequirementdata?view=aspnetcore-11.0" rel="noopener noreferrer"&gt;&lt;code&gt;IAuthorizationRequirementData&lt;/code&gt;&lt;/a&gt; and apply it to a Minimal API endpoint to restrict access to it. However, if you apply the same attribute to an MVC controller, a SignalR hub, or a Blazor component nothing is enforced. Before .NET 11, these attributes were only honored on Minimal API and routed endpoints.&lt;/p&gt;

&lt;p&gt;In .NET 11, MVC filters, SignalR hubs and hub methods, and Blazor's &lt;code&gt;AuthorizeView&lt;/code&gt; and &lt;code&gt;AuthorizeRouteView&lt;/code&gt; all evaluate authorization metadata the same way. Under the hood they share a new &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authorization.authorizationpolicy.combineasync" rel="noopener noreferrer"&gt;&lt;code&gt;AuthorizationPolicy.CombineAsync&lt;/code&gt;&lt;/a&gt; overload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuthorizationPolicy&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CombineAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IAuthorizationPolicyProvider&lt;/span&gt; &lt;span class="n"&gt;policyProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It merges &lt;code&gt;IAuthorizeData&lt;/code&gt;, &lt;code&gt;AuthorizationPolicy&lt;/code&gt; instances, and &lt;code&gt;IAuthorizationRequirementData&lt;/code&gt; attributes using the same rules as the authorization middleware. Take the minimum age attribute from the &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/security/authorization/custom-authorization-policies-with-iauthorizationrequirementdata?view=aspnetcore-11.0" rel="noopener noreferrer"&gt;Microsoft documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MinimumAgeAuthorizeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AuthorizeAttribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IAuthorizationRequirement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAuthorizationRequirementData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuthorizationRequirement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetRequirements&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You write the handler once and the attribute now works across the stack, for example on a hub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;MinimumAgeAuthorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PokerTableHub&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Hub&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;Notice that an attribute implementing both &lt;code&gt;IAuthorizeData&lt;/code&gt; and &lt;code&gt;IAuthorizationRequirementData&lt;/code&gt;, like the one above, contributes to the decision only once. Apps that opt out of endpoint routing (&lt;code&gt;EnableEndpointRouting = false&lt;/code&gt;) keep the legacy MVC behavior.&lt;/p&gt;

&lt;p&gt;The result is fewer surprises when you move an endpoint from one programming model to another. Your policy follows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give Passkeys Meaningful Names
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://auth0.com/blog/authentication-authorization-enhancements-dotnet-10/" rel="noopener noreferrer"&gt;ASP.NET Core Identity gained passkey support in .NET 10&lt;/a&gt;, and it came with a small usability problem. Users who registered several passkeys saw a list of credentials with no hint of which device or password manager held which one.&lt;/p&gt;

&lt;p&gt;.NET 11 improves this by inferring a display name from the authenticator's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data#attestedcredentialdata" rel="noopener noreferrer"&gt;AAGUID&lt;/a&gt;, the identifier that tells you what kind of authenticator created the passkey. Built-in mappings cover common ones such as Google Password Manager, iCloud Keychain, Windows Hello, 1Password, and Bitwarden. When the authenticator is known, Identity assigns the name automatically. When it isn't, the user lands on a rename page.&lt;/p&gt;

&lt;p&gt;If your organization issues its own hardware keys, you can add them to the &lt;code&gt;PasskeyAuthenticators&lt;/code&gt; dictionary in your project. It's a small change, but it's the kind of detail that gives a passkey management page a professional look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller Changes Worth Knowing
&lt;/h2&gt;

&lt;p&gt;A few more items deserve a mention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TimeProvider&lt;/code&gt; in ASP.NET Core Identity.&lt;/strong&gt; ASP.NET Core Identity now uses &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider" rel="noopener noreferrer"&gt;&lt;code&gt;TimeProvider&lt;/code&gt;&lt;/a&gt; instead of &lt;code&gt;DateTime&lt;/code&gt; and &lt;code&gt;DateTimeOffset&lt;/code&gt;. Register a &lt;code&gt;FakeTimeProvider&lt;/code&gt; in your unit tests and you can check lockout durations, token expiration, and security stamp validation without waiting or mocking clocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/security/authentication/jwt-authn" rel="noopener noreferrer"&gt;&lt;code&gt;dotnet user-jwts&lt;/code&gt;&lt;/a&gt; for file-based apps.&lt;/strong&gt; Running &lt;code&gt;dotnet user-jwts create --file app.cs&lt;/code&gt; creates a development JWT for a single-file app with no project file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLS channel binding for Negotiate.&lt;/strong&gt; On HTTPS, Kestrel now passes the TLS channel binding token to the Kerberos or NTLM exchange, which defends against relay attacks. There's nothing to configure. You can also read the token with &lt;code&gt;TryGetChannelBindingBytes&lt;/code&gt; on &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features.itlsconnectionfeature" rel="noopener noreferrer"&gt;&lt;code&gt;ITlsConnectionFeature&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[ShortCircuit]&lt;/code&gt;.&lt;/strong&gt; &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-11?view=aspnetcore-10.0&amp;amp;tabs=minimal-apis#short-circuit-endpoints-with-an-attribute" rel="noopener noreferrer"&gt;This attribute&lt;/a&gt; runs an endpoint right after routing and skips the rest of the pipeline, including authentication and CORS. Use it for health checks, never for anything that needs a user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Didn't Make It: Device Bound Session Credentials
&lt;/h2&gt;

&lt;p&gt;If you followed the earlier previews, you may remember an experimental package called &lt;code&gt;Microsoft.AspNetCore.Authentication.DeviceBoundSessions&lt;/code&gt;. It implemented the &lt;a href="https://www.w3.org/TR/dbsc/" rel="noopener noreferrer"&gt;Device Bound Session Credentials&lt;/a&gt; specification: a short-lived session cookie that the browser can only renew by proving it holds a private key, so a stolen cookie is useless after a few minutes.&lt;/p&gt;

&lt;p&gt;The idea is promising, but the package was pulled before RC2. The ASP.NET Core team &lt;a href="https://github.com/dotnet/aspnetcore/pull/69462" rel="noopener noreferrer"&gt;found the implementation incomplete&lt;/a&gt; and may bring it back in a later release. You can follow &lt;a href="https://github.com/dotnet/aspnetcore/issues/66478" rel="noopener noreferrer"&gt;the tracking issue&lt;/a&gt; to keep up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small Release, Sharper Edges
&lt;/h2&gt;

&lt;p&gt;.NET 11 doesn’t bring any revolutionary changes to authentication, and that's fine. SignalR token refresh removes a trade-off that developers worked around for years. Consistent authorization metadata removes a class of silent gaps. Friendlier passkeys polish the experience that .NET 10 started.&lt;/p&gt;

&lt;p&gt;Before you upgrade, check two things: allow the &lt;code&gt;/refresh&lt;/code&gt; route in any proxy in front of your hubs, and review any custom authorization attributes you wrote for one endpoint type.&lt;/p&gt;

&lt;p&gt;Which of these changes will you use first? Let me know in the comments.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>security</category>
      <category>authentication</category>
      <category>authorization</category>
    </item>
    <item>
      <title>You're Probably Doing Backend-for-Frontend (BFF) Wrong</title>
      <dc:creator>Auth0 DevRel</dc:creator>
      <pubDate>Mon, 09 Nov 2026 05:00:00 +0000</pubDate>
      <link>https://dev.to/auth0/youre-probably-doing-backend-for-frontend-bff-wrong-2770</link>
      <guid>https://dev.to/auth0/youre-probably-doing-backend-for-frontend-bff-wrong-2770</guid>
      <description>&lt;p&gt;The Backend for Frontend (BFF) pattern is crucial for securing modern applications, but it's widely misunderstood. In this video, we dive into the common pitfalls developers face when implementing BFFs. We break down why PKCE is not a replacement for a BFF, clarify the difference between a true BFF and a simple reverse proxy, and settle the debate on whether cookies are actually less secure than tokens. If you want to ensure your web apps are truly secure against XSS and token theft, this deep dive is for you.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Why PKCE protects authorization codes in transit, but doesn't secure token storage.&lt;/li&gt;
&lt;li&gt;The critical distinction between a reverse proxy (token mediation) and a proper BFF.&lt;/li&gt;
&lt;li&gt;The modern reality of HttpOnly cookies vs. localStorage tokens for XSS protection.&lt;/li&gt;
&lt;li&gt;The security responsibilities you still have after implementing a BFF (like CSRF protection and session invalidation).&lt;/li&gt;
&lt;li&gt;How to adopt the BFF pattern incrementally without rewriting your entire application.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Auth0 Blog Post: &lt;a href="https://auth0.com/blog/things-developers-get-wrong-about-the-backend-for-frontend-pattern/" rel="noopener noreferrer"&gt;Things Developers Get Wrong About the Backend for Frontend Pattern&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Checkout the &lt;a href="https://www.youtube.com/playlist?list=PLZ14qQz3cfJJjtFCuzSUzTtvRtMyd8U7l" rel="noopener noreferrer"&gt;Confused Developer Playlist&lt;/a&gt; for more videos like this.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>backend</category>
      <category>frontend</category>
    </item>
    <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>
  </channel>
</rss>
