Search code examples
next.jsprismanext-auth

NextAuth - Handle refresh token rotation (Reddit) with database


Reddit's access token has an expiration of 1 hour, but I want users that log in to my app to be able to post comments on Reddit for example. This means I need to refresh their access token once it has expired. Since I'm using a database (PlanetScale + Prisma) and not a JWT strategy, the documentation found here https://next-auth.js.org/tutorials/refresh-token-rotation is not useful to me (jwt callback is never called).

As far as I'm understanding it, it means it's not really possible to check the expiration in the session callback and refresh the token here without accessing the database each time?

What can I do if I want to refresh the access token in my database? Should I use a JWT strategy instead, even though I'm using a database?


Solution

  • To do refresh token rotation when using a database strategy you can do something like this:

    async function refreshAccessToken(session: Session) {
      if (!session.user?.id) {
        return;
      }
    
      const {
        id,
        refresh_token: refreshToken,
        expires_at: expiresAt,
      } = (await prisma.account.findFirst({
        where: { userId: session.user.id, provider: "reddit" },
      })) ?? {};
    
      if (!id || !refreshToken) {
        return;
      }
    
      // If expired refresh it
      if (expiresAt && Date.now() / 1000 > expiresAt) {
        const authorizationString = Buffer.from(
          `${process.env?.["REDDIT_CLIENT_ID"]}:${process.env?.["REDDIT_CLIENT_SECRET"]}`,
        ).toString("base64");
    
        const headers = {
          Authorization: `Basic ${authorizationString}`,
          "Content-Type": "application/x-www-form-urlencoded",
        };
    
        const urlSearchParams = new URLSearchParams();
        urlSearchParams.append("grant_type", "refresh_token");
        urlSearchParams.append("refresh_token", refreshToken);
        urlSearchParams.append("redirect_uri", `${process.env?.["NEXTAUTH_URL"]}/api/auth/callback/reddit`);
    
        const { data } = await axios.post<RedditResponse>("https://www.reddit.com/api/v1/access_token", urlSearchParams, {
          headers,
        });
    
        await prisma.account.update({
          where: { id },
          data: {
            access_token: data.access_token,
            expires_at: Math.floor(Date.now() / 1000) + data.expires_in,
            refresh_token: data.refresh_token,
            token_type: data.token_type,
            scope: data.scope,
          },
        });
      }
    }
    

    You can use this anywhere I guess. I don't know if it makes sense to use this in the session callback or not since it's probably a performance hit, so maybe just call it each time you actually need the access token for something? I'm not knowledgable about this to know what the best practice is in this regard...