Next auth refresh token strategy
Asked Answered
B

5

7

Using next.js auth (next auth) I'm creating CredentialsProvider, trying to connect it to django backend. All is working good except refresh token strategy: after obtaining new access token, access token and expire date not saving. So next auth is sending refresh token every request. I don't know why, but I can't update token data after "first time token created".

Here is the code:

async function refreshAccessToken(token) {
  console.log('UPDATE')
  const refresh = await axios
    .post('/token/refresh/', {
      refresh: token.refreshToken
    })
    .catch((error) => {
      console.log(error)
    })
  if (refresh && refresh.status === 200 && refresh.data.access) {
    return {
      ...token,
      accessToken: refresh.data.access,
      expiresAt: Date.now() + 10 * 1000
    }
  }
  return {
    ...token,
    error: 'RefreshAccessTokenError'
  }
}

export default NextAuth({
  providers: [
    CredentialsProvider({
      id: 'credentials',
      name: 'my-project',
      async authorize(credentials) {
        const auth = await axios
          .post('/token/', {
            username: credentials.username,
            password: credentials.password
          })
          .catch((error) => {
            console.log(error)
          })

        if (auth.status === 200 && auth.data.access) {
          const profile = await axios
            .get('/v1/profile/', {
              headers: {
                Authorization: `Bearer ${auth.data.access}`
              }
            })
            .catch((error) => {
              console.log(error)
            })
          if (profile.status === 200 && profile.data) {
            return {
              ...profile.data,
              tokens: auth.data
            }
          }
        }
        return null
      }
    })
  ],
  pages: {
    signIn: '/login'
  },
  callbacks: {
    jwt: async ({ token, user, account }) => {
      if (account && user) {
        return {
          // works normally
          accessToken: user.tokens.access,
          refreshToken: user.tokens.refresh,
          expiresAt: Date.now() + 10 * 1000,
          user
        }
      }

      if (Date.now() < token.expiresAt) {
        return token
      }

      // token is obtaining in refreshAccessToken but not saved...
      // next request refreshAccessToken will be called again...
      return refreshAccessToken(token)
    },
    session: async ({ session, token }) => {
      session.user = token.user
      session.token = token.accessToken
      return session
    }
  },
  debug: true
})

Any help please. Here is simplified example:

    jwt: async ({ token, user, account }) => {
      console.log(token.expiresAt)
      // here token.expiresAt will NEVER be equals 'hey'
      if (account && user) {
        return {
          accessToken: user.tokens.access,
          refreshToken: user.tokens.refresh,
          expiresAt: Date.now() + 10 * 1000,
          user
        }
      }

      if (Date.now() < token.expiresAt) {
        return token
      }

      token.expiresAt = 'hey'

      return token
    }
Bezique answered 8/2, 2022 at 23:5 Comment(2)
have you managed to find any solutions? im facing the same problem.Hosanna
Hi, i was wodnering in your project were you using interceptors?Chanel
S
4

It seems to be a next-auth bug which a lot of developers are experiencing even now. New refreshed token is just not being persisted and next-auth is always reusing the very first token received on the login. Here is discussions:

https://github.com/nextauthjs/next-auth/issues/7558 https://github.com/nextauthjs/next-auth/discussions/6642

Shofar answered 16/5, 2023 at 6:41 Comment(0)
B
1

It looks like part of the issue might be that timestamps are in milliseconds in JavaScript and seconds in GitHub tokens. You would need:

if (Date.now() < (token.expiresAt * 1000)) {
    return token
}
Butyraceous answered 17/4, 2023 at 2:21 Comment(0)
R
0

I don't know if this will cover exactly your use-case, as I think its more geared towards the built-in OAuth providers, but theres a tutorial on their docs site for how to handle refresh tokens.

See: https://next-auth.js.org/tutorials/refresh-token-rotation

I've also implemented something similar in a simple project using NextAuth of mine, check it out here: https://github.com/ndom91/newtelco-tab/blob/3f4153c7aa94c7f6cfeeb3778be3cdb1ec6ee243/src/pages/api/auth/%5B...nextauth%5D.ts#L32

Rior answered 11/2, 2022 at 23:4 Comment(0)
C
0

I'm suspicious of the

if (Date.now() < token.expiresAt) {
    return token
  }

It sounds like this is not being "hit" at all and the program is just continuing onto

return refreshAccessToken(token)

I'd suggest checking your Date() objects. They can be VERY tricky!

Put console.log() statements after each line and confirm the program's behaviour. Print out the date objects. You should see them in the NextJS CLI console OR in the browser console logs, probably the former as this is backend-stuff.

Also maybe try using UTC string instead?

let utcDateNow = new Date().toISOString();

console.log('The token expiry date is - ', token.expiresAt);
console.log('The current date is - ', utcDateNow);
 
if (token.expiresAt > utcDateNow) {
return token

}

You would need to save the "expires at" as Utc format as well obviously.

Copernicus answered 13/3, 2023 at 12:52 Comment(0)
L
-1

i think you need to return await refreshAccessToken(token), i did this in my code and it worked fine

Leonaleonanie answered 26/3, 2022 at 4:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.