We have internal website build with Next.js App Router (v14.2.28) and “@auth0/nextjs-auth0” Next.js SDK (v3.7.0) integration and we see this error in server logs quite a lot, like 4-10 times per second, depending on active user count at any given moment, and roughly in 1 minute intervals, so the error pattern is like so: at 10:10:11, 10:11:11, 10:11:12 and so on:
["Middleware error refreshing access token or session:",{"code":"ERR_MISSING_SESSION","name":"AccessTokenError","message":"The user does not have a valid session.","stack":"AccessTokenError: The user does not have a valid session.
at Object.getAccessToken (/usr/src/app/.next/server/src/middleware.js:2:73259)
...
The majority of users uses SAML / SSO login and we do account linking upon first login and then we force a user logout in our app (navigate them to ‘/api/auth/logout’), so they re-login and get a new set of tokens and session and can use our app without issues afterwards.
I am not sure how many users are impacted by this error. It seems that this error is not causing any major issues for the users, at least for the majority of them, but I suspect token exchange won’t happen for these particular users that get this session error, because in SDKs getAccessToken
function we exit early, if user does not have a cached session and we never reach token exchange logic, source code.
We decided to put getAccessToken
inside a middleware, because we have this example in SDK and mainly because of this doc:
Server Components in the App Directory (including Pages and Layouts) cannot write to a cookie.
If you rely solely on Server Components to read and update your session you should be aware of the following:
- If you have a rolling session (the default for this SDK), the expiry will not be updated when the user visits your site. So the session may expire sooner than you would expect (you can use
withMiddlewareAuthRequired
to mitigate this).- If you refresh the access token, the new access token will not be persisted in the session. So subsequent attempts to get an access token will always result in refreshing the expired access token in the session.
- If you make any other updates to the session, they will not be persisted between requests.
The cookie can be written from middleware, route handlers and server actions.
So I understand this as using getAccessToken
in the middleware is “allowed”, as it will write cookies as expected, persist tokens in the session and perform token exchange in one central place when access token expires.
Our middleware.tsx file looks like this:
import { NextRequest, NextResponse } from 'next/server'
import { touchSession, getAccessToken } from '@auth0/nextjs-auth0/edge' // Note the /edge import
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
try {
await getAccessToken(req, res)
await touchSession(req, res) // probably not needed, because getAccessToken also updates the session (?)
} catch (e: unknown) {
console.error(
'Middleware error refreshing access token or session:',
e
)
}
return res
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
In the server components - page.tsx or server actions we use getSession, to get the access token, needed for API requests to our BFF service, like so:
"use server"
import { getSession } from '@auth0/nextjs-auth0'
export const createOrderAction = async (
someParams
) => {
const requestName = 'Create Order'
const session = await getSession()
const { accessToken } = session || {}
try {
const res = await createOrderApi(
accessToken,
requestName,
someProps
)
} catch (e) {
console.error(e)
// fallback logic
}
All our pages are behind authentication, i.e. no public access and we use withPageAuthRequired for that. Page example:
export default withPageAuthRequired(
async function Users() {
// access token is taken from getSession() here
const { user, accessToken, navigationPermissions } = await getLayoutConfig()
if (!isUserSubCorrect(user?.sub)) {
return <ForcedLogout />
}
const [rolesListProcessedRes, fullTeamsListProcessedRes] = await getRolesAndTeamsAction(accessToken)
return (
<Layout user={user} navigationPermissions={navigationPermissions}>
<UserTabs
processedRolesRes={rolesListProcessedRes}
fullTeamsListProcessedRes={fullTeamsListProcessedRes}
/>
</Layout>
)
},
{
returnTo() {
return '/users'
}
}
)
Auth0 tenant setup is:
- Regular web app with token rotation enabled
- Access token lifetime 24 hours
- Refresh Token Expiration:
- Set Idle Refresh Token Lifetime - ON
- Idle Refresh Token Lifetime - 2592000 seconds
- Set Maximum Refresh Token Lifetime - ON
- Maximum Refresh Token Lifetime - 31557600 seconds
- Session Expiration:
- Persistent
- Idle Session Lifetime - 4320 minutes
- Maximum Session Lifetime - 10080 minutes
- ID Token Expiration:
- Maximum ID Token Lifetime - 36000 seconds
- Allow Offline Access - ON
Do you guys know what does this error mean? What’s the potential impact on the users? How can we mitigate it?
P.S. I know that probably the right thing would be to update to latest “@auth0/nextjs-auth0” SKD version, but it’s not an option right now (we have it planned in the future), since it’s a huge enterprise application, we just went live in prod and latest SDK version introduced many breaking changes and completely different setup.
Thank you very much for your help!
Lukas