Replies: 3 comments
-
I've resolved this by doing this: In the middleware call auth but then inject the session via a request header: if (session) {
headers.set('X-Serialized-Session', btoa(encodeURIComponent(JSON.stringify(session))));
} In your components, route handlers, wherever, just write and consume a helper method to deserialize and use the session: import { headers } from 'next/headers';
export function readSession() {
const encodedSession = headers().get('X-Serialized-Session');
const decodedSession = typeof encodedSession === 'string' ? decodeURIComponent(atob(encodedSession)) : undefined;
return decodedSession;
} This absolutely prevents multiple calls to |
Beta Was this translation helpful? Give feedback.
-
I'm running into the same issue, where you able to resolve it? |
Beta Was this translation helpful? Give feedback.
-
Based on @abencun-symphony input I was able to isolate which is the problem. The middleware and the route function should be considered as 2 different servers and they communicate over HTTP. The problem is that both invoke the My suggestion to fix it is on the const cookiesToObject = (cookies) => {
if(!cookies) {
return {};
}
return cookies.split(";").reduce((acc, cookie) => {
const [name, value] = cookie.split("=");
return { ...acc, [name.trim()]: value };
}, {});
};
const cookiesToString = (cookies) => {
if(cookies.length === 0) {
return "";
}
return Object.entries(cookies)
.map(([name, value]) => `${name}=${value}`)
.join(";");
};
const convertSetCookiesToCookies = (setCookies) => {
return setCookies.map((cookie) => {
const [nameValue, ...directives] = cookie.split(";");
return nameValue;
}).join(";");
}
async function getSession(headers, config) {
const url = createActionURL("session",
// @ts-expect-error `x-forwarded-proto` is not nullable, next.js sets it by default
headers.get("x-forwarded-proto"), headers, process.env, config.basePath);
const setCookies = headers.get("set-cookie");
// By default, try using the cookie header
let cookiesToUse = headers.get("cookie");
// If there is set-cookie header, we need to merge it with cookie
if(setCookies) {
// For some reason when we try using getSetCookie from header, it's empty. But if we recreate the headers, we have it
const headersToParse = new Headers(headers);
// Convert cookies to an object to easily merge
const cookiesObject = cookiesToObject(headersToParse.get("cookie"));
// Convert the set-cookies to the same format as cookies (removing the other info) and then to an object
const setCookiesObject = cookiesToObject(convertSetCookiesToCookies(headersToParse.getSetCookie() ?? []));
// Merge both objects and convert back to string
cookiesToUse = cookiesToString({ ...cookiesObject, ...setCookiesObject });
}
const request = new Request(url, {
headers: { cookie: cookiesToUse },
});
return Auth(request, {
...config,
callbacks: {
...config.callbacks,
// Since we are server-side, we don't need to filter out the session data
// See https://authjs.dev/getting-started/migrating-to-v5#authenticating-server-side
// TODO: Taint the session data to prevent accidental leakage to the client
// https://react.dev/reference/react/experimental_taintObjectReference
async session(...args) {
const session =
// If the user defined a custom session callback, use that instead
(await config.callbacks?.session?.(...args)) ?? {
...args[0].session,
expires: args[0].session.expires?.toISOString?.() ??
args[0].session.expires,
};
const user = args[0].user ?? args[0].token;
return { user, ...session };
},
},
});
} I did this change locally (that's why it's not on TS) and it's working as expected. The token is only refreshed on the middleware. Unfortunately I believe this is a problem we are not able to workaround in the client, it requires a change on |
Beta Was this translation helpful? Give feedback.
-
Environment
Reproduction URL
https://github.com/Vinnl/next-auth-bug-repro/tree/refresh-token-rotation-pkce
Describe the issue
I'm not entirely well-versed in OAuth, so please bear with me.
I've set up the Spotify provider in a Next.js app using auth.js, and tried to implement refresh token rotation as described in the guide. I'm using JWT, not a database, to maintain a user session.
As I understand it (I think this is a PKCE thing?), the refresh token gets invalidated after use - and along with the new access token, you also get a new refresh token for the next rotation.
However, if the Next-Auth middleware is enabled, it appears to attempt to rotate the same refresh token multiple times, the first of which is successful, but after that resulting in errors because the token has now been revoked.
How to reproduce
git clone --branch refresh-token-rotation-pkce git@github.com:Vinnl/next-auth-bug-repro.git
AUTH_SECRET
,AUTH_SPOTIFY_ID
andAUTH_SPOTIFY_SECRET
in.env.local
.pnpm install
pnpm run dev
Expected behavior
The JSON output showing the current session gets:
and if you look at the terminal in which you ran
pnpm run dev
, you'll see something like:If you now were to make any API calls with the access token shown on the page, they'd get rejected.
The expected behaviour is for the session to contain a valid access token, not have the error, and for the logs to not show the "Refresh token revoked" error. And in fact, that is what seems to happen if you
rm middleware.ts
, I think?Beta Was this translation helpful? Give feedback.
All reactions