v6 and Next 13 pages middleware #768
Replies: 13 comments 1 reply
-
Thank you! |
Beta Was this translation helpful? Give feedback.
-
Hello @viachaslau-latushkin , What makes you think otherwise? |
Beta Was this translation helpful? Give feedback.
-
@Thanaen in middleware I am trying to apply refresh token logic. |
Beta Was this translation helpful? Give feedback.
-
You can use sealData() and unsealData() to encrypt/decrypt the cookie values. Assuming you're doing an oauth/refresh token kind of thing, here's something I used recently: import {
IRON_COOKIE_NAME,
SessionData,
ironOptions,
} from './auth';
export async function middleware() {
const session = await getIronSession<SessionData>(cookies(), ironOptions);
const next = NextResponse.next();
if (!session.token?.expires_at) {
// no/invalid token, send user to auth
return NextResponse.redirect(getGoogleAuthURL());
}
if (session.token.expires_at < Date.now()) {
// refresh token
try {
const token = await refreshToken(session.token);
const sealed = await sealData(
{
...session,
token: { ...token, expires_at: Date.now() + token.expires_in * 1000 },
},
ironOptions,
);
next.cookies.set(IRON_COOKIE_NAME, sealed);
// await session.save(); // won't work
} catch (e) {
// some error while refreshing token, send user to auth
console.error(e);
return NextResponse.redirect(getGoogleAuthURL());
}
}
return next;
} |
Beta Was this translation helpful? Give feedback.
-
@arthurjdam |
Beta Was this translation helpful? Give feedback.
-
As @arthurjdam solution, here's the updated version that worked for me. Hope it may save time for others. This solution fixes the error middleware.ts export default async function middleware(req: NextRequest) {
const { isLoggedIn, exp } = await getSession();
// Refresh Token Rotation
if (isLoggedIn && exp && exp < Date.now() / 1000) {
try {
const newSession = await refreshToken();
const sealed = await sealData(newSession, sessionOptions);
const res = NextResponse.redirect(req.url);
res.cookies.set({
name: '[your-cookie-name]',
value: sealed,
});
return res;
} catch (e) {
await logout();
}
}
return NextResponse.next();
} authActions.ts export async function getSession() {
return await getIronSession<User>(cookies(), sessionOptions);
}
export async function refreshToken() {
const session = await getSession();
const res = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_BASE_URL}/user/refresh`,
{
method: 'POST',
headers: { Authorization: 'Bearer ' + session.refreshToken },
}
);
const { accessToken, refreshToken } = await res.json();
const newUser = jwt.decode(accessToken) as JwtPayload;
session.userToken = accessToken;
session.refreshToken = refreshToken;
session.exp = newUser.exp;
return session;
} P.S. I'm assuming the interface of my export interface User {
id: string;
isLoggedIn: boolean;
name: string;
userToken: string;
refreshToken: string;
exp: number | undefined;
} I'm available for any further clarifications or inquiries... |
Beta Was this translation helpful? Give feedback.
-
Please correct me if I'm wrong because this is doing my head in. I know this is an old thread but its the most relevant one I can find. I want to implement something similar, however I'm trying to avoid having the refresh token and the access token in the same session, which essentially means in the same cookie, no? Because iron-session uses a cookie (encrypted) to store the session on the client side. Because isn't the whole point of having a refresh token that you don't send it on every request like the access token? From what I can see here, if an attacker gets your cookie they got the whole pie, they can refresh tokens indefinitely and their session won't expire until the refresh token does. So you might as well just have an access token with an expiry of the refresh token, no? Am I missing something? I have been trying to find a way to have the refresh token separate and only send it up to the refresh endpoint when we need a new token, my thinking being that the fewer times it gets sent around the less likely it is for an attacker to steal it, but maybe that's not an accurate assumption. If it isn't, then it still leaves me with the question of why a refresh token in the first place, as this is the only case I can think of for having one. Please if someone could help me out and point out what I'm missing and how I should be thinking about this. Thanks. |
Beta Was this translation helpful? Give feedback.
-
@vurak sorry, but in your difficult case, separate access and refresh token but still save them in cookie - not really good solution. |
Beta Was this translation helpful? Give feedback.
-
@vurak I have implemented similar feature by having the refresh token encrypted in a separate cookie using the |
Beta Was this translation helpful? Give feedback.
-
@rifat-dhrubo thanks for the reply, that sounds exactly like the setup I've got. When you say you've made it a server only cookie, do you mean that because the token is always encrypted (through the use of I've been doing some reading to see if my assumptions about why a refresh token is used were right, I found this reply to be very good and confirmed my intuitions: that their main purpose is to reduce the opportunities for attack by allowing the lifetime of the access token (which is included in every request) to be shortened, meaning that if an attacker is to "steal" it, they would only have say a 30 minutes window to do bad stuff. Of course this only makes sense if the refresh token, which will have a much longer lifetime, say 30 days, is considerably less likely to be stolen; so the logic is that if the refresh token is only sent up to server a couple times an hour (i.e. only to the /refresh endpoint, or even better if you have a separate service for authentication) it supposedly reduces the opportunity window for an attack to steal it. So knowing all of that, what does you refresh logic look like? How do you determine when you should refresh without having the refresh cookie sent with every request (which would defeat the purpose of having one)? The problem I'm facing regarding this issue is that since iron-session encrypts the token info making it only accessible on back end, I can't determine on the client side when I need to get a new one, meaning I either have to have some annoying to-and-fro setup where when a request is made to the backend it checks how much longer the access token is valid for and if it needs to be refreshed, returns some response to client which tells it to make a refresh request. That way the refresh token can only be sent up a occasionally as intended. But this seems like quite an impractical and annoying solution. I am otherwise considering just implementing my own session solution with unencrypted (or maybe partially encrypted) JWTs, so that I can check the expiry of access token on the client side to skip the excessive back and forth of the aforementioned method. I hope this makes sense. Cheers. |
Beta Was this translation helpful? Give feedback.
-
@vurak you are welcome. Not necessarily. The refresh cookie is http only. So, it so can only be accessed in server side and, also it's encrypted. The only way for attackers to gain access is to have access to the server side and also break the encryption. I use middleware with an api route in next.js for refresh token. When the access token expires, I redirect the request to the api route. That route checks if the user has a valid session if so refreshes the cookie and sends the user back to the app. |
Beta Was this translation helpful? Give feedback.
-
Hey there, is there any bug in iron-session here or just looking for guidance? Thanks |
Beta Was this translation helpful? Give feedback.
-
Hi!
Thank you for your work!
I faced with problem in v6: save() and destroy() not working before return in nextjs middleware.
And changes applying only on next tick or after reloading page or on new request to pages/api folder.
"iron-session": "^6.3.1",
"next": "13.2.4"
Beta Was this translation helpful? Give feedback.
All reactions