CredentialsProvider session token cookie not updated on getServerSideProps
See original GitHub issueEnvironment
System:
OS: Windows 10 10.0.19042
CPU: (8) x64 Intel® Xeon® CPU E3-1505M v6 @ 3.00GHz
Memory: 5.44 GB / 15.86 GB
Binaries:
Node: 14.17.2 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.4 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
npm: 6.14.13 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.19041.1266.0)
Internet Explorer: 11.0.19041.1202
npmPackages:
next: ~12.1.0 => 12.1.0
next-auth: ~4.2.1 => 4.2.1
react: 17.0.2 => 17.0.2
Reproduction URL
https://github.com/killjoy2013/nextauth-credential-rotate
Describe the issue
Hi, Using CredentialsProvider and need to rotate the token. My […nextauth.ts] is below;
import NextAuth from 'next-auth';
import { JWT, JWTEncodeParams, JWTDecodeParams } from 'next-auth/jwt';
import CredentialsProvider from 'next-auth/providers/credentials';
import jsonwebtoken from 'jsonwebtoken';
function createToken(username: string) {
return {
username,
willExpire: Date.now() + parseInt(process.env.TOKEN_REFRESH_PERIOD) * 1000,
};
}
function refreshToken(token) {
const { iat, exp, ...others } = token;
return {
...others,
willExpire: Date.now() + parseInt(process.env.TOKEN_REFRESH_PERIOD) * 1000,
};
}
export default NextAuth({
secret: process.env.TOKEN_SECRET,
jwt: {
secret: process.env.TOKEN_SECRET,
maxAge: parseInt(process.env.TOKEN_MAX_AGE),
encode: async (params: JWTEncodeParams): Promise<string> => {
const { secret, token } = params;
let encodedToken = '';
if (token) {
const jwtClaims = {
username: token.username,
willExpire: token.willExpire,
};
encodedToken = jsonwebtoken.sign(jwtClaims, secret, {
expiresIn: parseInt(process.env.TOKEN_REFRESH_PERIOD),
algorithm: 'HS512',
});
} else {
console.log('TOKEN EMPTY. SO, LOGOUT!...');
return '';
}
return encodedToken;
},
decode: async (params: JWTDecodeParams) => {
const { token, secret } = params;
const decoded = jsonwebtoken.decode(token);
return { ...(decoded as JWT) };
},
},
session: {
maxAge: parseInt(process.env.TOKEN_MAX_AGE),
updateAge: 0,
strategy: 'jwt',
},
callbacks: {
async jwt({ token, user, account }) {
if (user) {
token = createToken(user.username as string);
}
let left = ((token.willExpire as number) - Date.now()) / 1000;
console.log({
now: Date.now(),
willExpire: token.willExpire,
left,
});
if (left > 0) {
return token;
} else {
let newToken = await refreshToken(token);
return { ...newToken };
}
},
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.username = token.username;
session.willExpire = token.willExpire;
return session;
},
},
providers: [
CredentialsProvider({
name: 'LDAP',
credentials: {
username: { label: 'Username', type: 'text' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const { username, password } = credentials;
if (!username || !password) {
throw new Error('enter username or password');
}
try {
let token = createToken(username);
return token;
} catch (error) {
console.log(error);
throw new Error('Authentication error');
}
},
}),
],
});
TOKEN_MAX_AGE
is 3600 seconds and TOKEN_REFRESH_PERIOD
is 60 seconds. So, in 60 seconds after JWT is created, token is supposed to get rotated. Initial token creation is in createToken
and tried to simulate token refresh in refreshToken
. I put username
& willExpire
as claims. In Jwt callback, a token is created and returned. Then, encode runs and encoded token returned. Both home page (index.tsx) and cities.tsx has
const session = await getSession({ req });
const token = await getToken({
req,
secret: process.env.TOKEN_SECRET,
raw: true,
});
in their getServerSideProps
. So, each navigation to home & cities page triggers jwt callback and even though encode function encodes and return a new token, next-auth.session-token
cookie never gets updated. It’s just created when user first login, and stays always the same. Normally, I’ll decide token rotation in jwt as in token rotation sample in tutorials like this;
let left = ((token.willExpire as number) - Date.now()) / 1000;
console.log({
now: Date.now(),
willExpire: token.willExpire,
left,
});
if (left > 0) {
return token;
} else {
let newToken = await refreshToken(token);
return { ...newToken };
}
Since the session token cookie is not updated, this rotation logic fails because the token received in jwt callback is always the same.
How to reproduce
npm i & npm run dev
navigate to http://localhost:3000/
you’ll be redirected to login page. Provide a dummy username and a password
You’ll be redirected to home page. Now you can display next-auth.session-token
cookie now.
click Cities
in the left menu and you’ll navigate to cities page.
click toolbar to go back home page.
In every navigation between pages, getServerSideProps
runs with getSession
and getToken
inside.
Note that, next-auth.session-token
is not changing 😦
Close the browser and wait until token refresh period ( 60 seconds ) has passed. Then open a browser and navigate to http://localhost:3000/cities
Now, notice that, next-auth.session-token
cookie still has the old token from last login before closing the browser.
Expected behavior
next-auth.session-token
is supposed to get updated with the returned token from encode.
On the client side, we can force the token to update using
const event = new Event('visibilitychange');
document.dispatchEvent(event);
However, users can directly navigate to http://localhost:3000/cities
In this case, on getServerSideProps
an updated token is supposed to be obtained because this token will be used in a graphql query that will be run from serverside.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:2
- Comments:11 (4 by maintainers)
It’s save my day! Thks so much! may it would be very welcome to official docs of next-auth!
@ThangHuuVu @balazsorban44 getServerSession works the way I need, thank you a lot 👍
One point I observed is, I need to supply default token properties explicitly;
If I try to create the token without them;
I received
Maybe those default props can be supplied internally. So, we wouldn’t have to supply them.
Thanks again & regards