KaronatoR
KaronatoR

Reputation: 2653

Next auth refresh token strategy

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
    }

Upvotes: 7

Views: 19087

Answers (5)

bodich
bodich

Reputation: 2225

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

Upvotes: 5

zackrw
zackrw

Reputation: 31

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
}

Upvotes: 1

Scottish Smile
Scottish Smile

Reputation: 1141

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.

Upvotes: 0

Khang Do
Khang Do

Reputation: 29

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

Upvotes: -1

ndom91
ndom91

Reputation: 837

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

Upvotes: 0

Related Questions