Can't authenticate user on Next v15 API Route Handler, can't getSession or updateSession from auth0 client

I’m upgrading from NextJs 14 to 15. In 15 I cannot use auth0.updateSession() method in Server Action (Server Function) not triggered by Form, event handler or useEffect hook.

I got Server Function defined which I invoke in Server Component thus not allowed now in Next15. Due to Next implementation details I moved the auth0.updateSession to API Route Handler:

export async function POST(request: NextRequest) {
try {
const cookieStore = await cookies();
const session = await auth0.getSession(request);
const body = await request.json();
console.log("API: ", cookieStore.getAll())
console.log("SESSION: ", session)
await auth0.updateSession(body.user);
return Response.json({ status: “OK” })
} catch (e) {
console.error("Error on user update session: ", e)
return Response.json({ status: “Error” })
}
}

The problem is the error user is not authenticated, there is no session and I can’t update user session in my login flow.
web:dev: API:
web:dev: SESSION: null
web:dev: Error on user update session: Error: The user is not authenticated.
web:dev: at async POST (app/api/auth/update/route.ts:12:5)
web:dev: 10 | console.log("API: ", cookieStore.getAll())
web:dev: 11 | console.log("SESSION: ", session)
web:dev: > 12 | await auth0.updateSession(body.user);
web:dev: | ^
web:dev: 13 | return Response.json({ status: “OK” })
web:dev: 14 | } catch (e) {
web:dev: 15 | console.error("Error on user update session: ", e)

After user sign-ups he/she should verify it by clicking link in email. Afterward he should be able to refresh the page. This will trigger logic to getSession with auth0 client and get updated user with ManagementClient from ManagementAPI. After receiving update user I want to updateSession with updated user_metadata with signUpComplete flag, but the code errors with above error when I try to update it in new endpoint designed to do so.

const session: ExtendedSessionWithAppUserProfile | undefined = await auth0.getSession();

try {

if (!session?.user.sub) throw new Error(`session.user.sub is empty ${JSON.stringify(session)}`);

if (session.user.email_verified) return true;

const mc = getManagementClient();

const u = await mc.users.get({ id: session.user.sub });

const updatedUser = {

…session,

  user: {

…session.user,

    email_verified: u.data.email_verified,

  },

}

const res = await fetch(AUTH0_UPDATE_URL, {

  method: 'POST',

  headers: {

‘Content-Type’: ‘application/json’,

  },

  credentials: "same-origin",

  body: JSON.stringify({ user: updatedUser }),

});

if (!res.ok) {

throw new Error(`Failed to update user session: ${res.statusText}`);

}

I read the migration guide and examples readme from github and countless POSTS and I’m totally confused. I also not sure if I should somehow try to implement silent authentication, but I just don’t know how as many of the POSTs contain auth0 v3 implementation only code.

Hi @Piotr.Lenartowicz,

Welcome to the Auth0 Community!

The error The user is not authenticated and SESSION: null is occurring because your API Route Handler at app/api/auth/update/route.ts is not authenticated. It’s a standard Next.js route, and the Auth0 SDK has no context of the user’s session.

To fix this, you must wrap your route handler with withRouteHandlerAuthRequired from the @auth0/nextjs-auth0 SDK. This wrapper ensures that a valid session exists before executing your code, and it correctly populates the session context.

You’ll need to make two adjustments:

  1. Wrap your API Route Handler with withRouteHandlerAuthRequired to make it a protected, session-aware route.
  2. Update your getSession call inside the wrapper to use the new App Router syntax, which doesn’t require any arguments.

Here is the corrected code for your API Route Handler:

// app/api/auth/update/route.ts

import { getSession, updateSession, withRouteHandlerAuthRequired } from '@auth0/nextjs-auth0';
import { NextResponse } from 'next/server';

// 1. Wrap your export with withRouteHandlerAuthRequired
export const POST = withRouteHandlerAuthRequired(async function(req) {
  try {
    // 2. getSession() now requires no arguments inside the wrapper.
    //    It automatically finds the correct session.
    const session = await getSession();

    if (!session) {
      // This should technically not be reachable if the wrapper is working
      return NextResponse.json({ status: "Error", message: "Not Authenticated" }, { status: 401 });
    }
    
    const body = await req.json();
    
    console.log("SESSION: ", session); // This will no longer be null

    // Your logic looks correct: body.user contains the new session object
    await updateSession(body.user); 
    
    return NextResponse.json({ status: "OK" });
  } catch (e: any) {
    console.error("Error on user update session: ", e);
    return NextResponse.json({ status: "Error", message: e.message }, { status: 500 });
  }
});

Additionally, in your Server Component where you call this API, ensure you are also using the App Router version of getSession:

// In your Server Component
import { getSession } from '@auth0/nextjs-auth0';

// ...
const session = await getSession(); // No arguments needed here either
// ...

Be aware that updateSession modifies the session cookie. The updated session data will be available on the subsequent request or page navigation after the API route responds.

If you have any further questions, please don’t hesitate to reach out.

Have a good one,
Vlad