📆 Thời gian:
🕐 5 phút đọc
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.

[…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, accessTokenExpiry và refreshToken.
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:
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."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!
_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);Tham chiếu: https://dev.to/mabaranowski/nextjs-authentication-jwt-refresh-token-rotation-with-nextauthjs-5696
0
0
0
0
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