Le Thinh Blog
Home
Source
Blog

Next.js Authentication - JWT Refresh Token với NextAuth.js

📆 Thời gian:

🕐 5 phút đọc

#nextjs
#lập trình
Next.js Authentication - JWT Refresh Token với NextAuth.js

Giới thiệu

Nếu bạn làm việc với xác thực người dùng thì khái niệm jwt (jsonwebtoken) sẽ không còn xa lạ với các bạn. Tuy nhiên việc để thời gian hết hạn của token quá dài sẽ ảnh hưởng đến bảo mật hệ thống. Do đó, trước khi token hết hạn, chúng ta phải refresh token! Dưới đây là phần trình bày cho refresh token với NextAuth.

Triển khai

Phía Server Side

[…nextauth].js

import axios from "axios";
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

async function refreshAccessToken(tokenObject) {
  try {
    // Get a new set of tokens with a refreshToken
    const tokenResponse = await axios.post(
      `${process.env.ENDPOINT_SERVER}/api/v1/users/refresh-token`,
      {
        token: tokenObject.refreshToken,
      }
    );
    return {
      ...tokenObject,
      accessToken: tokenResponse.data.accessToken,
      accessTokenExpiry: tokenResponse.data.accessTokenExpiry,
      refreshToken: tokenResponse.data.refreshToken,
    };
  } catch (error) {
    return {
      ...tokenObject,
      error: "RefreshAccessTokenError",
    };
  }
}

export default NextAuth({
  session: {
    strategy: "jwt",
    maxAge: 30000 * 24 * 60 * 60,
  },
  providers: [
    CredentialsProvider({
      id: "login",
      name: "login",
      async authorize(credentials, req) {
        try {
          const { account, password } = credentials;
          console.log(account, password);
          const user = await axios.post(
            `${process.env.ENDPOINT_SERVER}/api/v1/users/login`,
            {
              account,
              password,
            }
          );
          if (user.data.accessToken) {
            return user.data;
          }

          return null;
        } catch (err) {
          console.log(err);
          if (err.response.data.message) {
            throw new Error(err.response.data.message);
          }
          throw new Error(err);
        }
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        // This will only be executed at login. Each next invocation will skip this part.
        token.accessToken = user.accessToken;
        token.accessTokenExpiry = user.accessTokenExpiry;
        token.refreshToken = user.refreshToken;
      }
      // If accessTokenExpiry is 24 hours, we have to refresh token before 24 hours pass.
      const shouldRefreshTime = Math.round(
        token.accessTokenExpiry - 60 * 60 * 1000 - Date.now()
      );

      // If the token is still valid, just return it.
      if (shouldRefreshTime > 0) {
        return Promise.resolve(token);
      }
      // If the call arrives after 23 hours have passed, we allow to refresh the token.
      token = refreshAccessToken(token);
      return Promise.resolve(token);
    },
    async session({ session, token }) {
      // Here we pass accessToken to the client to be used in authentication with your API
      session.accessToken = token.accessToken;
      session.accessTokenExpiry = token.accessTokenExpiry;
      session.error = token.error;
      return Promise.resolve(session);
    },
  },
});

`${process.env.ENDPOINT_SERVER}/api/v1/users/login` Khi login thành công, server sẽ trả về accessToken, accessTokenExpiryrefreshToken

Trong đó:

  • accessToken ta nên để thời gian ngắn, ở đây tôi mặc định là 24 giờ.
  • refreshToken ta nên để thời gian dài hơn, ở đây tôi mặc định là 30 ngày, dùng để get new accessToken.
  • accessTokenExpiry thời gian hết hạn của accessToken

 

async function refreshAccessToken(tokenObject) {
  try {
    // Get a new set of tokens with a refreshToken
    const tokenResponse = await axios.post(
      `${process.env.ENDPOINT_SERVER}/api/v1/users/refresh-token`,
      {
        token: tokenObject.refreshToken,
      }
    );
    return {
      ...tokenObject,
      accessToken: tokenResponse.data.accessToken,
      accessTokenExpiry: tokenResponse.data.accessTokenExpiry,
      refreshToken: tokenResponse.data.refreshToken,
    };
  } catch (error) {
    return {
      ...tokenObject,
      error: "RefreshAccessTokenError",
    };
  }
}

`${process.env.ENDPOINT_SERVER}/api/v1/users/refresh-token` đường dẫn này để refresh token, nhận đầu vào là refreshToken hiện tại. 

Sau đó tiến hành kiểm tra logic:

  • Nếu thành công: server sẽ tiến hành generate  accessToken mới và tiến hành tạo refreshToken mới, accessTokenExpiry, sau đó gửi trả về. Sau khi nhận được response từ hàm refreshAccessToken, đoạn  async session({ session, token }) { sẽ được thực thi.
  • Nếu thất bại, sẽ kích hoạt error "RefreshAccessTokenError".
  async jwt({ token, user }) {
      if (user) {
        // This will only be executed at login. Each next invocation will skip this part.
        token.accessToken = user.accessToken;
        token.accessTokenExpiry = user.accessTokenExpiry;
        token.refreshToken = user.refreshToken;
      }
      // If accessTokenExpiry is 24 hours, we have to refresh token before 24 hours pass.
      const shouldRefreshTime = Math.round(
        token.accessTokenExpiry - 60 * 60 * 1000 - Date.now()
      );

      // If the token is still valid, just return it.
      if (shouldRefreshTime > 0) {
        return Promise.resolve(token);
      }
      // If the call arrives after 23 hours have passed, we allow to refresh the token.
      token = refreshAccessToken(token);
      return Promise.resolve(token);
    },

Ở đây nếu check accessTokenExpiry vẫn còn đang trong thời hạn 23 giờ sau thì token cũ vẫn sẽ được giữ nguyên, người lại, sau 23 giờ thì ta có thể refresh token!

Phía Client Side

_app.js

import { SessionProvider } from 'next-auth/react';
import { useState } from 'react';
import RefreshTokenHandler from '../components/refreshTokenHandler';

function MyApp({ Component, pageProps }) {
    const [interval, setInterval] = useState(0);

    return (
        <SessionProvider session={pageProps.session} refetchInterval={interval}>
            <Component {...pageProps} />
            <RefreshTokenHandler setInterval={setInterval} />
        </SessionProvider>
    )
}

export default MyApp;

RefreshTokenHandler.js

import { useSession } from "next-auth/react";
import { useEffect } from "react";

const RefreshTokenHandler = (props) => {
    const { data: session } = useSession();

    useEffect(() => {
        if(!!session) {
            // We did set the token to be ready to refresh after 23 hours, here we set interval of 23 hours 30 minutes.
            const timeRemaining = Math.round((((session.accessTokenExpiry - 30 * 60 * 1000) - Date.now()) / 1000));
            props.setInterval(timeRemaining > 0 ? timeRemaining : 0);
        }
    }, [session]);

    return null;
}

export default RefreshTokenHandler;

Mỗi khi refresh page, <RefreshTokenHandler /> sẽ được gọi, sau đó sẽ tính toán khoảng thời gian còn lại thì SessionProvider sẽ refetch bằng refetchInterval. Ở đây tôi tôi muốn đếm tới lúc 23 giờ 30 phút sau khi có accessToken thì sẽ refresh token mới.

useAuth.js
 

import { signOut, useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

export default function useAuth(shouldRedirect) {
    const { data: session } = useSession();
    const router = useRouter();
    const [isAuthenticated, setIsAuthenticated] = useState(false);

    useEffect(() => {
        if (session?.error === "RefreshAccessTokenError") {
            signOut({ callbackUrl: '/login', redirect: shouldRedirect });
        }

        if (session === null) {
            if (router.route !== '/login') {
                router.replace('/login');
            }
            setIsAuthenticated(false);
        } else if (session !== undefined) {
            if (router.route === '/login') {
                router.replace('/');
            }
            setIsAuthenticated(true);
        }
    }, [session]);

    return isAuthenticated;
}

Chúng ta tạo thêm một Hook dùng để bắt lỗi và đặt bất cứ đâu bạn muốn check nếu người dùng đã xác thực, chưa xác thực hoặc lỗi refresh token.

const isAuthenticated = useAuth(true);

Kết

Tham chiếu: https://dev.to/mabaranowski/nextjs-authentication-jwt-refresh-token-rotation-with-nextauthjs-5696

0

0

0

0

Contents

MYSELF

Lê Văn Thịnh
Lê Văn Thịnh
Tôi chỉ là một người đam mê lập trình web, chia sẻ những gì mình tích luỹ được, góp phần cộng đồng lập trình người Việt.
Theo dõi tôi

Relationship Blogs

Tags

refresh token nextjs, jwt refresh, nextauth, how to refresh token with nextauth, cách làm mới token, làm mới token, làm mới jwt, làm mới token nextjs