<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>AI Applications on RockB</title><link>https://baeseokjae.github.io/tags/ai-applications/</link><description>Recent content in AI Applications on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 09 Jun 2026 05:50:34 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/ai-applications/index.xml" rel="self" type="application/rss+xml"/><item><title>NextAuth.js v5 / Auth.js: Authentication for Next.js AI Applications 2026</title><link>https://baeseokjae.github.io/posts/next-auth-v5-auth-js-nextjs-guide-2026/</link><pubDate>Tue, 09 Jun 2026 05:50:34 +0000</pubDate><guid>https://baeseokjae.github.io/posts/next-auth-v5-auth-js-nextjs-guide-2026/</guid><description>Complete guide to Auth.js v5 for Next.js AI apps — setup, JWT vs database sessions, RBAC, MCP authentication, and migrating from v4.</description><content:encoded><![CDATA[<p>Auth.js v5 (next-auth@beta) is the current production standard for Next.js authentication in 2026, offering native App Router support, Edge runtime compatibility, and a dramatically simplified API that replaces the v4 <code>getServerSession()</code> pattern with a single <code>auth()</code> function. For AI applications specifically, Auth.js v5 provides the foundation layer upon which token-aware rate limiting, MCP server authorization, and agent delegation chains can be built.</p>
<h2 id="why-authentication-for-nextjs-ai-apps-is-different-in-2026">Why Authentication for Next.js AI Apps Is Different in 2026</h2>
<p>Authentication for Next.js AI applications in 2026 fundamentally differs from traditional web apps because AI systems introduce three new attack surfaces and cost vectors that standard session management was never designed to handle. First, stateful context management: AI chat applications maintain multi-turn conversation state that must be tied to authenticated sessions — without this, attackers can hijack context windows. Second, token-aware rate limiting: a single unauthorized GPT-4 API call consuming 2,000 tokens costs roughly 100x more than a simple database read, meaning unauthorized access can cost thousands of dollars per hour (AIMultiple Research, 2025). Third, agent delegation chains: modern AI systems spawn child agents that must inherit authentication scope without re-prompting users. The average cost per AI-specific breach reached $4.80 million in 2025 (IBM Report), and 90% of organizations implementing AI report feeling unprepared for security risks. Traditional auth libraries like NextAuth v4 were designed for human-to-server interactions; Auth.js v5 bridges the gap by providing Web Standard APIs, Edge runtime compatibility, and enough extensibility to build the additional AI-specific layers on top.</p>
<h2 id="the-evolution-of-nextauthjs-from-pages-router-library-to-authjs-framework">The Evolution of NextAuth.js: From Pages Router Library to Auth.js Framework</h2>
<p>NextAuth.js v5 represents a complete architectural rewrite of the library, now renamed Auth.js to reflect its framework-agnostic design that extends to SvelteKit, SolidStart, Express, and Qwik — not just Next.js. The original NextAuth was built Pages Router-first, with configuration living in <code>pages/api/auth/[...nextauth].ts</code> and session retrieval requiring <code>getServerSession()</code> with explicit imports from <code>next-auth/next</code>. This worked well for 2021-era Next.js apps but broke down under App Router&rsquo;s server-component model where every component can independently access session data.</p>
<p>Auth.js v5 (next-auth@beta) currently has 22K+ GitHub stars and 111K+ weekly npm downloads, making it the de facto standard for Next.js authentication. The rewrite adopts Web Standard APIs (Request/Response instead of Node.js-specific objects), which is what enables Edge runtime compatibility — critical for middleware that runs on Vercel&rsquo;s edge network before your server-side code executes. The v5 design philosophy is: one <code>auth()</code> function that works everywhere, zero arguments needed in most cases, auto-detection of environment from request context.</p>
<p>The key architectural changes from v4:</p>
<ul>
<li>Configuration moves from <code>pages/api/auth/[...nextauth].ts</code> → root-level <code>auth.ts</code></li>
<li><code>getServerSession()</code> replaced by <code>auth()</code> everywhere</li>
<li>Environment variables: <code>NEXTAUTH_SECRET</code> → <code>AUTH_SECRET</code>, <code>NEXTAUTH_URL</code> → auto-detected</li>
<li>Adapter packages: <code>@next-auth/*</code> → <code>@auth/*</code> namespace (e.g., <code>@auth/prisma-adapter</code>)</li>
<li>OAuth 1.0 support dropped entirely in v5</li>
</ul>
<h2 id="nextauthjs-v5-core-architecture-the-auth-function">NextAuth.js v5 Core Architecture: The <code>auth()</code> Function</h2>
<p>The <code>auth()</code> function is the cornerstone of Auth.js v5, replacing <code>getServerSession()</code> across all execution contexts — Server Components, Route Handlers, API Routes, middleware, and Server Actions. Auth.js v5&rsquo;s architecture centers on a single exported <code>auth</code> function that auto-detects its execution context and returns the current session without requiring arguments in server-side contexts. In middleware, it wraps your handler function. In Server Components, it returns a <code>Session | null</code> directly. In Route Handlers, it also returns <code>Session | null</code> after parsing the incoming request. This unified API reduces cognitive overhead dramatically: you no longer need to remember whether you&rsquo;re calling <code>getServerSession(authOptions)</code> or <code>getToken({ req })</code> depending on context.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// auth.ts (root level) — the single source of truth
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">NextAuth</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">GitHub</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth/providers/github&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Google</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth/providers/google&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">handlers</span>, <span style="color:#a6e22e">signIn</span>, <span style="color:#a6e22e">signOut</span>, <span style="color:#a6e22e">auth</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">NextAuth</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">providers</span><span style="color:#f92672">:</span> [<span style="color:#a6e22e">GitHub</span>, <span style="color:#a6e22e">Google</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">secret</span>: <span style="color:#66d9ef">process.env.AUTH_SECRET</span>,
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/api/auth/[...nextauth]/route.ts — just 2 lines
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">handlers</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">GET</span>, <span style="color:#a6e22e">POST</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">handlers</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// Server Component usage
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Dashboard() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span>) <span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/login&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">div</span>&gt;<span style="color:#a6e22e">Welcome</span> {<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">name</span>}&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Environment variable auto-detection means <code>AUTH_URL</code> is inferred from request headers in most deployment environments, so you typically only need <code>AUTH_SECRET</code> and your provider secrets (e.g., <code>AUTH_GITHUB_ID</code>, <code>AUTH_GITHUB_SECRET</code>).</p>
<h2 id="quick-start-setting-up-authjs-v5-with-nextjs-app-router">Quick Start: Setting Up Auth.js v5 with Next.js App Router</h2>
<p>Setting up Auth.js v5 in a new Next.js 15/16 App Router project requires installing three packages (next-auth@beta, your adapter if using database sessions, and bcryptjs for credentials), creating <code>auth.ts</code> at the project root, and wiring up the route handler. The complete setup takes under an hour for OAuth-only apps. For Credentials provider with database sessions, add two to four hours for schema setup and testing.</p>
<p><strong>Step 1: Install dependencies</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install next-auth@beta
</span></span><span style="display:flex;"><span><span style="color:#75715e"># For database sessions (optional):</span>
</span></span><span style="display:flex;"><span>npm install @auth/prisma-adapter @prisma/client
</span></span><span style="display:flex;"><span><span style="color:#75715e"># For Credentials provider (optional):</span>
</span></span><span style="display:flex;"><span>npm install bcryptjs
</span></span><span style="display:flex;"><span>npm install -D @types/bcryptjs
</span></span></code></pre></div><p><strong>Step 2: Create <code>auth.ts</code> at project root</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">NextAuth</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">GitHub</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth/providers/github&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Google</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth/providers/google&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Credentials</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth/providers/credentials&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">PrismaAdapter</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@auth/prisma-adapter&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">prisma</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/prisma&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">bcrypt</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;bcryptjs&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">credentialsSchema</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">email</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">email</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">password</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">min</span>(<span style="color:#ae81ff">8</span>),
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">handlers</span>, <span style="color:#a6e22e">signIn</span>, <span style="color:#a6e22e">signOut</span>, <span style="color:#a6e22e">auth</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">NextAuth</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">adapter</span>: <span style="color:#66d9ef">PrismaAdapter</span>(<span style="color:#a6e22e">prisma</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">session</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">strategy</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;jwt&#34;</span> }, <span style="color:#75715e">// or &#34;database&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">providers</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">GitHub</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Google</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Credentials</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">email</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">label</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Email&#34;</span>, <span style="color:#66d9ef">type</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;email&#34;</span> },
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">password</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">label</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Password&#34;</span>, <span style="color:#66d9ef">type</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;password&#34;</span> },
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">authorize</span>: <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">credentials</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">parsed</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">credentialsSchema</span>.<span style="color:#a6e22e">safeParse</span>(<span style="color:#a6e22e">credentials</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">success</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">user</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">prisma</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">findUnique</span>({
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">where</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">email</span>: <span style="color:#66d9ef">parsed.data.email</span> }
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">user</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">password</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">valid</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">bcrypt</span>.<span style="color:#a6e22e">compare</span>(<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">password</span>, <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">password</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">valid</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">user</span> : <span style="color:#66d9ef">null</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">callbacks</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">jwt</span>({ <span style="color:#a6e22e">token</span>, <span style="color:#a6e22e">user</span> }) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">user</span>) <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">role</span> <span style="color:#75715e">// persist role on sign-in
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">token</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">session</span>({ <span style="color:#a6e22e">session</span>, <span style="color:#a6e22e">token</span> }) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">role</span> <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">session</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p><strong>Step 3: Create route handler</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/api/auth/[...nextauth]/route.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">handlers</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">GET</span>, <span style="color:#a6e22e">POST</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">handlers</span>
</span></span></code></pre></div><p><strong>Step 4: Add environment variables</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>AUTH_SECRET<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your-secret-here&#34;</span> <span style="color:#75715e"># openssl rand -base64 32</span>
</span></span><span style="display:flex;"><span>AUTH_GITHUB_ID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your-github-app-id&#34;</span>
</span></span><span style="display:flex;"><span>AUTH_GITHUB_SECRET<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your-github-app-secret&#34;</span>
</span></span><span style="display:flex;"><span>AUTH_GOOGLE_ID<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your-google-client-id&#34;</span>
</span></span><span style="display:flex;"><span>AUTH_GOOGLE_SECRET<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your-google-client-secret&#34;</span>
</span></span></code></pre></div><h2 id="jwt-vs-database-sessions-choosing-the-right-strategy-for-ai-applications">JWT vs Database Sessions: Choosing the Right Strategy for AI Applications</h2>
<p>Auth.js v5 supports two session strategies, and the choice between them has significant implications for AI application security and performance. JWT sessions store encrypted session data in a browser cookie — each request is self-contained, no database round-trip required, making them faster but impossible to invalidate until cookie expiry. Database sessions store a reference token in the cookie and look up session data on every authenticated request — one extra DB query per request but immediate revocability. For AI applications, this tradeoff becomes critical because compromised AI agent sessions can incur massive costs before the expiry window closes.</p>
<table>
  <thead>
      <tr>
          <th>Strategy</th>
          <th>Request Latency</th>
          <th>Session Revocation</th>
          <th>Best For</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JWT</td>
          <td>~0ms extra</td>
          <td>Only at expiry</td>
          <td>Consumer AI apps, stateless APIs</td>
      </tr>
      <tr>
          <td>Database</td>
          <td>+5–15ms DB query</td>
          <td>Immediate</td>
          <td>B2B/enterprise, agent delegation chains</td>
      </tr>
  </tbody>
</table>
<p><strong>When to use JWT sessions:</strong></p>
<ul>
<li>Consumer-facing AI chat apps where instant revocation is not required</li>
<li>Edge-deployed API routes where no database connection is available</li>
<li>High-traffic AI endpoints where per-request DB queries would bottleneck</li>
</ul>
<p><strong>When to use Database sessions:</strong></p>
<ul>
<li>B2B/enterprise AI applications where admins must be able to force-logout compromised accounts</li>
<li>AI agent systems where parent agents spawn child agents (delegation chains require traceable session lineage)</li>
<li>Any AI app processing sensitive enterprise data where compliance mandates immediate revocation capability</li>
</ul>
<p>For most AI applications in 2026, the recommended approach is JWT for the user-facing web app layer, combined with short-lived API keys or database-backed tokens for AI agent-to-service communication.</p>
<h2 id="protecting-routes-middlewarets-server-components-api-routes-and-server-actions">Protecting Routes: middleware.ts, Server Components, API Routes, and Server Actions</h2>
<p>Auth.js v5 provides four distinct protection patterns corresponding to Next.js&rsquo;s four execution contexts. Route protection in Auth.js v5 works through the <code>auth()</code> function in all four execution contexts — middleware wraps the handler function to intercept unauthenticated requests before they reach your code, Server Components call <code>auth()</code> directly and redirect, Route Handlers call <code>auth()</code> at the top of the handler, and Server Actions call <code>auth()</code> before performing mutations. The middleware pattern is the most efficient for protecting large groups of routes because it runs at the CDN edge before any server code executes.</p>
<p><strong>Middleware protection (most efficient for grouped routes):</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// middleware.ts — auth.config.ts split required for Edge compatibility
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./auth&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">auth</span>((<span style="color:#a6e22e">req</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isLoggedIn</span> <span style="color:#f92672">=</span> <span style="color:#f92672">!!</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">auth</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isApiRoute</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">nextUrl</span>.<span style="color:#a6e22e">pathname</span>.<span style="color:#a6e22e">startsWith</span>(<span style="color:#e6db74">&#39;/api&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isAuthPage</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">nextUrl</span>.<span style="color:#a6e22e">pathname</span>.<span style="color:#a6e22e">startsWith</span>(<span style="color:#e6db74">&#39;/auth&#39;</span>)
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">isLoggedIn</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">isAuthPage</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Response</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URL</span>(<span style="color:#e6db74">&#39;/auth/login&#39;</span>, <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">nextUrl</span>))
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">isApiRoute</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">isLoggedIn</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>(<span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>({ <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Unauthorized&#39;</span> }), { 
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">401</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> { <span style="color:#e6db74">&#39;Content-Type&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;application/json&#39;</span> }
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">config</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">matcher</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;/((?!_next/static|_next/image|favicon.ico|.*\\.png$).*)&#39;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The matcher pattern is critical — failing to exclude <code>_next/static</code>, <code>_next/image</code>, and static assets causes the middleware to run on every static file request, significantly degrading performance.</p>
<p><strong>Server Component protection:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">redirect</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next/navigation&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">AIChat() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span>) <span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/auth/login&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// session.user.role available if RBAC is configured
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">ChatInterface</span> <span style="color:#a6e22e">userId</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span>} /&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>API Route protection:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">NextResponse</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next/server&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">NextResponse</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Unauthorized&#39;</span> }, { <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">401</span> })
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// AI API call logic here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p><strong>Server Action protection:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#e6db74">&#34;use server&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">generateAIResponse</span>(<span style="color:#a6e22e">prompt</span>: <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span>) <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unauthorized&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Server-side AI API call
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><h2 id="role-based-access-control-rbac-with-authjs">Role-Based Access Control (RBAC) with Auth.js</h2>
<p>Auth.js v5 implements RBAC through JWT callbacks that persist custom fields to the session token, combined with TypeScript module augmentation that extends the default Session and User types. RBAC with Auth.js v5 uses the <code>jwt()</code> callback to attach role data to the encrypted token on sign-in, the <code>session()</code> callback to expose that role on the client-accessible session object, and TypeScript module augmentation in <code>next-auth.d.ts</code> to add type safety throughout the app. The Prisma schema requires a <code>role</code> field on the User model, which Auth.js reads via the adapter on sign-in and persists to the JWT.</p>
<p><strong>Prisma schema for RBAC:</strong></p>
<pre tabindex="0"><code class="language-prisma" data-lang="prisma">enum Role {
  USER
  ADMIN
  AI_OPERATOR
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String    @unique
  emailVerified DateTime?
  image         String?
  password      String?
  role          Role      @default(USER)
  accounts      Account[]
  sessions      Session[]
}
</code></pre><p><strong>TypeScript module augmentation:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// next-auth.d.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#e6db74">&#34;next-auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Role</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@prisma/client&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">declare</span> <span style="color:#a6e22e">module</span> <span style="color:#e6db74">&#34;next-auth&#34;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">User</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">role?</span>: <span style="color:#66d9ef">Role</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">Session</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">user</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">role?</span>: <span style="color:#66d9ef">Role</span>
</span></span><span style="display:flex;"><span>    } <span style="color:#f92672">&amp;</span> <span style="color:#a6e22e">DefaultSession</span>[<span style="color:#e6db74">&#34;user&#34;</span>]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">declare</span> <span style="color:#a6e22e">module</span> <span style="color:#e6db74">&#34;next-auth/jwt&#34;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">JWT</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">role?</span>: <span style="color:#66d9ef">Role</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>RBAC middleware for AI admin routes:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">auth</span>((<span style="color:#a6e22e">req</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">auth</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isAdminRoute</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">nextUrl</span>.<span style="color:#a6e22e">pathname</span>.<span style="color:#a6e22e">startsWith</span>(<span style="color:#e6db74">&#39;/admin&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isAIOperatorRoute</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">nextUrl</span>.<span style="color:#a6e22e">pathname</span>.<span style="color:#a6e22e">startsWith</span>(<span style="color:#e6db74">&#39;/api/ai&#39;</span>)
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">isAdminRoute</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;ADMIN&#39;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Response</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URL</span>(<span style="color:#e6db74">&#39;/unauthorized&#39;</span>, <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">nextUrl</span>))
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">isAIOperatorRoute</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">!</span>[<span style="color:#e6db74">&#39;ADMIN&#39;</span>, <span style="color:#e6db74">&#39;AI_OPERATOR&#39;</span>].<span style="color:#a6e22e">includes</span>(<span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">??</span> <span style="color:#e6db74">&#39;&#39;</span>)) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>(<span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>({ <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Insufficient permissions&#39;</span> }), { <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">403</span> })
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><h2 id="ai-specific-authentication-challenges">AI-Specific Authentication Challenges</h2>
<p>AI applications require authentication patterns that traditional web frameworks never anticipated, including stateful context management across multi-turn conversations, session persistence through streaming responses, and agent delegation chains where parent AI agents spawn child agents that must operate within a bounded authorization scope. Stateful context management means tying conversation history to an authenticated session identifier so that prompt injection attacks cannot hijack another user&rsquo;s context window. Streaming response session persistence is a technical challenge unique to AI: HTTP streaming keeps connections open for 30–120 seconds, during which the session must remain valid and checked continuously. Agent delegation chains require that when an AI orchestrator agent spawns a tool-calling sub-agent, that sub-agent inherits a scoped, non-escalatable token — not the full user session.</p>
<p><strong>Stateful AI context session management:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// lib/ai-session.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getAISessionContext() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">id</span>) <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unauthenticated&#39;</span>)
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Fetch conversation context tied to this session
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">context</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">prisma</span>.<span style="color:#a6e22e">aiConversation</span>.<span style="color:#a6e22e">findFirst</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">where</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">userId</span>: <span style="color:#66d9ef">session.user.id</span>, <span style="color:#a6e22e">active</span>: <span style="color:#66d9ef">true</span> },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">include</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">messages</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">orderBy</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">createdAt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;asc&#39;</span> }, <span style="color:#a6e22e">take</span>: <span style="color:#66d9ef">20</span> } }
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">session</span>, <span style="color:#a6e22e">context</span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Scoped agent tokens for AI delegation chains:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// Generate a time-limited, scoped token for child agent operations
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">createAgentDelegationToken</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">parentSession</span>: <span style="color:#66d9ef">Session</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">scope</span>: <span style="color:#66d9ef">string</span>[],
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ttlSeconds</span>: <span style="color:#66d9ef">number</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">300</span>
</span></span><span style="display:flex;"><span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">encrypt</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sub</span>: <span style="color:#66d9ef">parentSession.user.id</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">parentSessionId</span>: <span style="color:#66d9ef">parentSession.id</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">scope</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">exp</span>: <span style="color:#66d9ef">Math.floor</span>(Date.<span style="color:#a6e22e">now</span>() <span style="color:#f92672">/</span> <span style="color:#ae81ff">1000</span>) <span style="color:#f92672">+</span> <span style="color:#a6e22e">ttlSeconds</span>,
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">token</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="security-in-the-ai-era-cve-2025-29927-and-owasp-top-10-for-llms">Security in the AI Era: CVE-2025-29927 and OWASP Top 10 for LLMs</h2>
<p>CVE-2025-29927 (CVSS 9.1) is one of the most critical authentication vulnerabilities in Next.js history — a complete middleware authentication bypass achieved by injecting a <code>x-middleware-subrequest</code> header into HTTP requests, affecting versions 15.x below 15.2.3 and 14.x below 14.2.25. The vulnerability allows an attacker to skip all middleware execution, including auth checks, by setting this single header — no credentials required. If your Next.js middleware is the only authentication layer (a common pattern), this means complete authentication bypass for any unauthenticated request. The fix: update to Next.js 15.2.3+ or 14.2.25+, and never rely solely on middleware for authentication — always validate sessions in Server Components and API Routes as well.</p>
<p><strong>Defense-in-depth authentication checklist for AI apps:</strong></p>
<table>
  <thead>
      <tr>
          <th>Layer</th>
          <th>Vulnerability</th>
          <th>Mitigation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Middleware</td>
          <td>CVE-2025-29927 header injection</td>
          <td>Update Next.js, add redundant auth checks</td>
      </tr>
      <tr>
          <td>Server Components</td>
          <td>Missing <code>auth()</code> call</td>
          <td>Always call <code>auth()</code> before rendering sensitive content</td>
      </tr>
      <tr>
          <td>API Routes</td>
          <td>Skipped auth check</td>
          <td>Call <code>auth()</code> at top of every handler</td>
      </tr>
      <tr>
          <td>AI API calls</td>
          <td>Prompt injection</td>
          <td>Sanitize user input, system prompt isolation</td>
      </tr>
      <tr>
          <td>Streaming responses</td>
          <td>Session expiry during stream</td>
          <td>Re-validate session before each chunk</td>
      </tr>
  </tbody>
</table>
<p><strong>OWASP Top 10 for LLMs — authentication-relevant items:</strong></p>
<ul>
<li><strong>LLM01: Prompt Injection</strong> — success rates exceed 50% on unprotected models; multi-language attacks succeed at 70%+ (Lakera Research, 2025). Mitigation: authenticated session context should be injected via system prompt before user input, not concatenated with it.</li>
<li><strong>LLM06: Sensitive Information Disclosure</strong> — AI models can leak data from other users&rsquo; contexts if session isolation is not enforced at the database query layer.</li>
<li><strong>LLM09: Overreliance</strong> — Systems that trust AI outputs without verifying the authenticated user authorized the resulting actions.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// Secure AI API route with defense-in-depth
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>() <span style="color:#75715e">// Layer 2 auth check (middleware is Layer 1)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>(<span style="color:#e6db74">&#39;Unauthorized&#39;</span>, { <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">401</span> })
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">prompt</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">json</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">sanitizedPrompt</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">sanitizePromptInput</span>(<span style="color:#a6e22e">prompt</span>) <span style="color:#75715e">// Remove injection attempts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">systemPrompt</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`You are assisting </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">name</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> (ID: </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span><span style="color:#e6db74">}</span><span style="color:#e6db74">).
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Only access data belonging to this user. Never reveal data from other users.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">User&#39;s permissions: </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">role</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// AI call with isolated context
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><h2 id="token-aware-rate-limiting-protecting-against-runaway-ai-api-costs">Token-Aware Rate Limiting: Protecting Against Runaway AI API Costs</h2>
<p>Token-aware rate limiting is a security control unique to AI applications — unlike traditional rate limiting that counts requests per minute (RPM), token-aware limiting tracks the cumulative token cost of AI API calls per authenticated user session, because a single 2,000-token GPT-4 Turbo request costs roughly 100x more than a 20-token prompt, making RPM limits alone economically inadequate. Unauthorized AI API usage can cost thousands of dollars per hour (AIMultiple Research, 2025). Implementing token-aware rate limiting requires storing token usage per user in Redis or your database, checking remaining budget before each AI call, and returning a 429 with a <code>Retry-After</code> header when the budget is exhausted.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// lib/rate-limit.ts — token-aware rate limiting for AI endpoints
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">RateLimitConfig</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">maxTokensPerHour</span>: <span style="color:#66d9ef">number</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">maxRequestsPerMinute</span>: <span style="color:#66d9ef">number</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">DEFAULT_LIMITS</span>: <span style="color:#66d9ef">Record</span>&lt;<span style="color:#f92672">string</span>, <span style="color:#a6e22e">RateLimitConfig</span>&gt; <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">USER</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">maxTokensPerHour</span>: <span style="color:#66d9ef">50_000</span>, <span style="color:#a6e22e">maxRequestsPerMinute</span>: <span style="color:#66d9ef">10</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AI_OPERATOR</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">maxTokensPerHour</span>: <span style="color:#66d9ef">500_000</span>, <span style="color:#a6e22e">maxRequestsPerMinute</span>: <span style="color:#66d9ef">60</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ADMIN</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">maxTokensPerHour</span>: <span style="color:#66d9ef">2_000_000</span>, <span style="color:#a6e22e">maxRequestsPerMinute</span>: <span style="color:#66d9ef">200</span> },
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">checkAIRateLimit</span>(<span style="color:#a6e22e">userId</span>: <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">role</span>: <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">limits</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">DEFAULT_LIMITS</span>[<span style="color:#a6e22e">role</span>] <span style="color:#f92672">??</span> <span style="color:#a6e22e">DEFAULT_LIMITS</span>.<span style="color:#a6e22e">USER</span>
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">hourlyUsage</span>, <span style="color:#a6e22e">minuteRequests</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">Promise</span>.<span style="color:#a6e22e">all</span>([
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">redis</span>.<span style="color:#66d9ef">get</span>(<span style="color:#e6db74">`ai:tokens:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">userId</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">hourKey</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">redis</span>.<span style="color:#66d9ef">get</span>(<span style="color:#e6db74">`ai:requests:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">userId</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">minuteKey</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>),
</span></span><span style="display:flex;"><span>  ])
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (parseInt(<span style="color:#a6e22e">hourlyUsage</span> <span style="color:#f92672">??</span> <span style="color:#e6db74">&#39;0&#39;</span>) <span style="color:#f92672">&gt;=</span> <span style="color:#a6e22e">limits</span>.<span style="color:#a6e22e">maxTokensPerHour</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">`Hourly token limit exceeded (</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">limits</span>.<span style="color:#a6e22e">maxTokensPerHour</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> tokens/hour)`</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (parseInt(<span style="color:#a6e22e">minuteRequests</span> <span style="color:#f92672">??</span> <span style="color:#e6db74">&#39;0&#39;</span>) <span style="color:#f92672">&gt;=</span> <span style="color:#a6e22e">limits</span>.<span style="color:#a6e22e">maxRequestsPerMinute</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">`Rate limit exceeded (</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">limits</span>.<span style="color:#a6e22e">maxRequestsPerMinute</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> req/min)`</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">trackAITokenUsage</span>(<span style="color:#a6e22e">userId</span>: <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">tokensUsed</span>: <span style="color:#66d9ef">number</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">pipeline</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">pipeline</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">pipeline</span>.<span style="color:#a6e22e">incrby</span>(<span style="color:#e6db74">`ai:tokens:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">userId</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">hourKey</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>, <span style="color:#a6e22e">tokensUsed</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">pipeline</span>.<span style="color:#a6e22e">expire</span>(<span style="color:#e6db74">`ai:tokens:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">userId</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">hourKey</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>, <span style="color:#ae81ff">3600</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">pipeline</span>.<span style="color:#a6e22e">incr</span>(<span style="color:#e6db74">`ai:requests:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">userId</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">minuteKey</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">pipeline</span>.<span style="color:#a6e22e">expire</span>(<span style="color:#e6db74">`ai:requests:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">userId</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">minuteKey</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>, <span style="color:#ae81ff">60</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">pipeline</span>.<span style="color:#a6e22e">exec</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="model-context-protocol-mcp-authentication-a-new-paradigm-for-ai-agent-authorization">Model Context Protocol (MCP) Authentication: A New Paradigm for AI Agent Authorization</h2>
<p>Model Context Protocol (MCP) represents a new authentication paradigm for AI agent communication with external tools — standardizing how AI assistants like Claude authenticate to external services (databases, APIs, file systems) without exposing full user credentials. MCP authentication sits above Auth.js user authentication: Auth.js proves who the human user is, while MCP authentication controls what tools an AI agent can access on behalf of that user. An authenticated user session does not automatically grant AI agents access to MCP servers — that authorization must be explicitly granted and scoped.</p>
<p><strong>MCP authorization flow with Auth.js:</strong></p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 472 153"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>↓</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>↓</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>↓</text>
<text text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'>↓</text>
<text text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='8' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='8' y='100' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='8' y='132' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='16' y='132' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='24' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='132' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='136' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='192' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='200' y='132' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='208' y='132' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='216' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='224' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='224' y='132' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='232' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='232' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='232' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='240' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='240' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='248' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='248' y='132' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='256' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='256' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='264' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='264' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='264' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='272' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='280' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='280' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='288' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='296' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='296' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='304' y='100' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='304' y='132' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='312' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='320' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='320' y='100' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='320' y='132' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='328' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='336' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='336' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='336' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='336' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='336' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='344' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='344' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='344' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='352' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='352' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='352' y='100' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='352' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='360' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='360' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='360' y='100' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='360' y='132' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='368' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='368' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='376' y='100' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='376' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='384' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='384' y='132' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='392' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='392' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='392' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='392' y='132' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='400' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='400' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='408' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='408' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='416' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='416' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='416' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='416' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='424' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='432' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='432' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='440' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='440' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='448' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='448' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='456' y='68' fill='currentColor' style='font-size:1em'>s</text>
</g>

    </svg>
  
</div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// lib/mcp-auth.ts — MCP server authorization using Auth.js session
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">auth</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/auth&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">createMCPDelegationToken</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">toolScopes</span>: <span style="color:#66d9ef">string</span>[],  <span style="color:#75715e">// e.g., [&#39;read:database&#39;, &#39;write:files&#39;]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">expiresInSeconds</span>: <span style="color:#66d9ef">number</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">300</span>
</span></span><span style="display:flex;"><span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span>) <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unauthenticated&#39;</span>)
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">signMCPToken</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sub</span>: <span style="color:#66d9ef">session.user.id</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">scopes</span>: <span style="color:#66d9ef">toolScopes</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">iss</span>: <span style="color:#66d9ef">process.env.NEXTAUTH_URL</span> <span style="color:#f92672">??</span> <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">AUTH_URL</span><span style="color:#f92672">!</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">aud</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;mcp-server&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">exp</span>: <span style="color:#66d9ef">Math.floor</span>(Date.<span style="color:#a6e22e">now</span>() <span style="color:#f92672">/</span> <span style="color:#ae81ff">1000</span>) <span style="color:#f92672">+</span> <span style="color:#a6e22e">expiresInSeconds</span>,
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>78% of enterprises have adopted AI, yet 71% regularly use generative AI in production (G2 Research, 2025), making MCP authentication a near-term production requirement rather than a future consideration.</p>
<h2 id="v4--v5-migration-breaking-changes-and-testing-strategy">v4 → v5 Migration: Breaking Changes and Testing Strategy</h2>
<p>Migrating from NextAuth v4 to Auth.js v5 takes approximately half a day of mechanical edits plus one day of testing — two days if your app has custom database adapter logic (Web3 AI Blog). The migration is almost entirely mechanical: file moves, package renames, and environment variable renames. No database schema changes are required. OAuth 1.0 providers (Twitter API v1) need replacement. The two-file split for Edge compatibility is the only structural change that requires genuine architectural thinking.</p>
<p><strong>Complete breaking changes checklist:</strong></p>
<table>
  <thead>
      <tr>
          <th>v4 Pattern</th>
          <th>v5 Replacement</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>pages/api/auth/[...nextauth].ts</code></td>
          <td><code>auth.ts</code> at root + <code>app/api/auth/[...nextauth]/route.ts</code></td>
      </tr>
      <tr>
          <td><code>getServerSession(authOptions)</code></td>
          <td><code>auth()</code></td>
      </tr>
      <tr>
          <td><code>useSession()</code> from <code>next-auth/react</code></td>
          <td>Same (unchanged)</td>
      </tr>
      <tr>
          <td><code>NEXTAUTH_SECRET</code></td>
          <td><code>AUTH_SECRET</code></td>
      </tr>
      <tr>
          <td><code>NEXTAUTH_URL</code></td>
          <td><code>AUTH_URL</code> (auto-detected, often not needed)</td>
      </tr>
      <tr>
          <td><code>@next-auth/prisma-adapter</code></td>
          <td><code>@auth/prisma-adapter</code></td>
      </tr>
      <tr>
          <td><code>NextAuthOptions</code> type</td>
          <td><code>NextAuthConfig</code> type</td>
      </tr>
      <tr>
          <td><code>withAuth</code> middleware wrapper</td>
          <td><code>auth()</code> wrapper function</td>
      </tr>
      <tr>
          <td><code>getToken({ req })</code> in API routes</td>
          <td><code>auth()</code></td>
      </tr>
  </tbody>
</table>
<p><strong>Migration testing strategy:</strong></p>
<ol>
<li>Run both v4 and v5 auth configurations in parallel using feature flags during testing</li>
<li>Test OAuth flows for each provider (Google requires explicit <code>scope: 'openid email profile'</code> in v5)</li>
<li>Verify JWT token shape if you rely on custom JWT fields</li>
<li>Test middleware matcher patterns — v5 middleware matcher syntax may need updates</li>
<li>Verify Drizzle adapter users: v5 has the largest schema delta, check reference schema</li>
</ol>
<p><strong>Edge runtime two-file split (required if using adapters):</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// auth.config.ts — edge-safe, NO adapter, NO Node.js-only imports
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#66d9ef">type</span> { <span style="color:#a6e22e">NextAuthConfig</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">GitHub</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth/providers/github&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Google</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth/providers/google&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">authConfig</span>: <span style="color:#66d9ef">NextAuthConfig</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">providers</span><span style="color:#f92672">:</span> [<span style="color:#a6e22e">GitHub</span>, <span style="color:#a6e22e">Google</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">pages</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">signIn</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;/auth/login&#39;</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">callbacks</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">authorized</span>({ <span style="color:#a6e22e">auth</span>, <span style="color:#a6e22e">request</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">nextUrl</span> } }) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isLoggedIn</span> <span style="color:#f92672">=</span> <span style="color:#f92672">!!</span><span style="color:#a6e22e">auth</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isProtected</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">nextUrl</span>.<span style="color:#a6e22e">pathname</span>.<span style="color:#a6e22e">startsWith</span>(<span style="color:#e6db74">&#39;/dashboard&#39;</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">isProtected</span>) <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">isLoggedIn</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// auth.ts — full config with adapter (Node.js runtime only)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">NextAuth</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">PrismaAdapter</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@auth/prisma-adapter&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">prisma</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/prisma&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">authConfig</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./auth.config&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">handlers</span>, <span style="color:#a6e22e">signIn</span>, <span style="color:#a6e22e">signOut</span>, <span style="color:#a6e22e">auth</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">NextAuth</span>({
</span></span><span style="display:flex;"><span>  ...<span style="color:#a6e22e">authConfig</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">adapter</span>: <span style="color:#66d9ef">PrismaAdapter</span>(<span style="color:#a6e22e">prisma</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">session</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">strategy</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;jwt&#34;</span> },
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// middleware.ts — imports from auth.config.ts, NOT auth.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">NextAuth</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next-auth&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">authConfig</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./auth.config&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">NextAuth</span>(<span style="color:#a6e22e">authConfig</span>).<span style="color:#a6e22e">auth</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">config</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">matcher</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;/((?!api/auth|_next/static|_next/image|.*\\.png$).*)&#39;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="provider-comparison-diy-vs-authjs-vs-clerk-vs-authgear-for-ai-apps">Provider Comparison: DIY vs Auth.js vs Clerk vs Authgear for AI Apps</h2>
<p>Choosing the right authentication approach for a Next.js AI application involves three realistic options in 2026: DIY implementation (weeks to months, full control), Auth.js v5 (hours, free OSS, good for consumer apps), or managed platforms like Clerk or Authgear (minutes, production-ready features, monthly cost). DIY authentication&rsquo;s hidden cost is that every new auth method — passkeys, SAML SSO, MFA, WebAuthn — is a fresh engineering project, and every CVE is yours to patch. Auth.js v5 is the sweet spot for bootstrapped AI apps and consumer products, covering OAuth, credentials, magic links, and basic RBAC out of the box. Managed platforms become necessary when B2B multi-tenant isolation, SOC 2 compliance, built-in fraud protection, or enterprise SSO are requirements.</p>
<table>
  <thead>
      <tr>
          <th>Feature</th>
          <th>DIY</th>
          <th>Auth.js v5</th>
          <th>Clerk</th>
          <th>Authgear</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Setup time</td>
          <td>Weeks</td>
          <td>Hours</td>
          <td>Minutes</td>
          <td>Minutes</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>Dev time only</td>
          <td>Free OSS</td>
          <td>$25–$100+/mo</td>
          <td>$49+/mo</td>
      </tr>
      <tr>
          <td>OAuth providers</td>
          <td>Manual</td>
          <td>80+ built-in</td>
          <td>30+ built-in</td>
          <td>20+ built-in</td>
      </tr>
      <tr>
          <td>Passkeys/WebAuthn</td>
          <td>Weeks of work</td>
          <td>Experimental</td>
          <td>✅ Production</td>
          <td>✅ Production</td>
      </tr>
      <tr>
          <td>SAML/SSO</td>
          <td>Significant work</td>
          <td>Custom only</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>AI agent toolkit</td>
          <td>Build yourself</td>
          <td>Build yourself</td>
          <td>✅ @clerk/agent-toolkit</td>
          <td>Limited</td>
      </tr>
      <tr>
          <td>MCP authentication</td>
          <td>Build yourself</td>
          <td>Build yourself</td>
          <td>Via agent toolkit</td>
          <td>Limited</td>
      </tr>
      <tr>
          <td>Fraud protection</td>
          <td>Build yourself</td>
          <td>None</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>Edge runtime</td>
          <td>Manual</td>
          <td>✅ (two-file split)</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td>CVE responsibility</td>
          <td>Yours</td>
          <td>Auth.js team</td>
          <td>Provider</td>
          <td>Provider</td>
      </tr>
  </tbody>
</table>
<p>Auth.js v5 is explicitly the right choice for: early-stage AI apps, consumer products without SSO requirements, and teams where open-source extensibility outweighs managed convenience. Clerk&rsquo;s <code>@clerk/agent-toolkit</code> provides automatic session context injection into AI system prompts, making it purpose-built for AI applications when budget allows.</p>
<h2 id="enterprise-requirements-multi-tenant-isolation-and-compliance">Enterprise Requirements: Multi-Tenant Isolation and Compliance</h2>
<p>Enterprise Next.js AI applications require authentication infrastructure that goes significantly beyond what Auth.js v5 provides out of the box — specifically multi-tenant data isolation, SAML/SSO integration, SOC 2 audit logging, and forced session revocation at the organization level. Auth.js v5 can serve as the foundation for multi-tenant apps, but tenant isolation must be implemented at the data access layer (Prisma query middleware or RLS in PostgreSQL), not at the authentication layer. Each authenticated session should carry a <code>tenantId</code> claim in the JWT, and every database query must be scoped to that tenant.</p>
<p><strong>Multi-tenant JWT with Auth.js:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// auth.ts callbacks for multi-tenant
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">callbacks</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">jwt</span>({ <span style="color:#a6e22e">token</span>, <span style="color:#a6e22e">user</span>, <span style="color:#a6e22e">account</span> }) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">user</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">tenantId</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">tenantId</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">tenantId</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">role</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">token</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">session</span>({ <span style="color:#a6e22e">session</span>, <span style="color:#a6e22e">token</span> }) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">tenantId</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">tenantId</span> <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">role</span> <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">session</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Prisma middleware for tenant isolation:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#a6e22e">prisma</span>.<span style="color:#a6e22e">$use</span>(<span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">params</span>, <span style="color:#a6e22e">next</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">session</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">auth</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">session</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">user</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">tenantId</span>) <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Missing tenant context&#39;</span>)
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">action</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;findMany&#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">action</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;findFirst&#39;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">args</span>.<span style="color:#a6e22e">where</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      ...<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">args</span>.<span style="color:#a6e22e">where</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">tenantId</span>: <span style="color:#66d9ef">session.user.tenantId</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">next</span>(<span style="color:#a6e22e">params</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p><strong>Q: What is the difference between NextAuth.js and Auth.js?</strong></p>
<p>Auth.js is the new name for NextAuth.js v5. The rename reflects the library&rsquo;s expansion beyond Next.js — Auth.js v5 supports SvelteKit, SolidStart, Express, Qwik, and Next.js. For Next.js, you still install <code>next-auth@beta</code> and import from <code>next-auth</code>. The two names refer to the same library at v5+; &ldquo;NextAuth.js&rdquo; typically refers to v4 and earlier.</p>
<p><strong>Q: Is Auth.js v5 production-ready in 2026?</strong></p>
<p>Yes. Auth.js v5 (next-auth@beta) is production-ready and widely deployed — it has 22K+ GitHub stars and 111K+ weekly npm downloads. The &ldquo;beta&rdquo; label reflects API stability guarantees, not production readiness. The Auth.js team has indicated a stable v5 release is pending. For new projects starting in 2026, v5 is the recommended choice.</p>
<p><strong>Q: Should I use JWT or database sessions for an AI chatbot?</strong></p>
<p>For consumer AI chatbots: JWT sessions are usually sufficient — they&rsquo;re faster (no DB query per request) and the risk of an un-revocable compromised session is low for consumer apps. For B2B AI products: use database sessions so admins can force-logout compromised accounts immediately. The extra 5–15ms per request is worth the security guarantee when enterprise data is involved.</p>
<p><strong>Q: How do I handle authentication with streaming AI responses?</strong></p>
<p>Validate the session before initiating the stream, not during. Once the stream starts, you cannot easily interrupt it for auth re-validation without breaking the streaming connection. The pattern: <code>const session = await auth()</code> → validate → start <code>ai.streamText()</code> → pipe to <code>Response</code>. Set stream TTL shorter than session expiry (e.g., max 60-second streams if session expires in 30 minutes).</p>
<p><strong>Q: How do I fix the CVE-2025-29927 middleware bypass vulnerability?</strong></p>
<p>Update Next.js to 15.2.3+ (for Next.js 15) or 14.2.25+ (for Next.js 14). Verify with <code>npm list next</code>. Also add defense-in-depth by calling <code>auth()</code> inside Server Components and API Routes, not just relying on middleware. Never treat middleware as the sole authentication layer.</p>
]]></content:encoded></item></channel></rss>