Node.js + TypeScript + MongoDB: JWT Refresh Token



Wellcome back, hôm nay tôi xin trình bày về "JWT Refresh Token", bài này sẽ phần tiếp theo của series "JWT Authentication".Các bạn có thể xem thêm về loạt bài jwt authentication gồm 3 phần.


Lời nói đầu
Nói một cách đơn giản thì refresh token là token được gọi khi access token bị hết hạn, thay vì phải login lại thì chúng ta chỉ cần gọi api refreshToken để lấy lại accessToken mới.Khi gọi API refreshToken, bạn sẽ nhận được cả accessToken mới và refreshToken để tiếp tục truy cập các tài nguyên mà không cần đăng nhập lại. Quy trình này giúp cải thiện trải nghiệm người dùng và bảo mật hơn vì accessToken chỉ tồn tại trong một khoảng thời gian ngắn. Nếu accessToken bị lộ, người khác chỉ có thể truy cập được trong khoảng thời gian ngắn trước khi nó hết hạn và phải sử dụng refreshToken mới để tiếp tục truy cập.

JWT Refresh Token Implementation Flow
Sơ đồ dưới đây minh họa cách hoạt động của access token và refresh token

Để tôi trình bày từng bước trong quá trình sử dụng JWT Refresh Token như sau:
  • Đầu tiên, người dùng đăng nhập vào ứng dụng bằng Email và Mật khẩu của mình.
  • Máy chủ nhận yêu cầu đó và xác thực Email và Mật khẩu.
  • Nếu thông tin đăng nhập hợp lệ, máy chủ sẽ gửi refresh và access token dưới dạng cookie về trình duyệt của người dùng.
  • Sau đó, người dùng sẽ gửi một yêu cầu GET để truy cập một route bảo mật với các cookie được gửi trong tiêu đề yêu cầu.
  • Máy chủ sẽ xác thực access token và nếu token đã bị sửa đổi hoặc hết hạn, máy chủ sẽ gửi mã lỗi 401 đến người dùng.
  • Ứng dụng frontend sau đó nhận được lỗi không được ủy quyền và sử dụng các interceptors để làm mới access token.
  • Nghĩa là ứng dụng frontend sẽ thực hiện một yêu cầu GET đến /api/auth/refresh để lấy một access token mới dưới dạng cookie. Điều này xảy ra trong background.
  • Ứng dụng frontend sau đó sẽ thử lại yêu cầu sau khi nhận được access token mới.
Generate Public and Private Keys for the Token
Nếu bạn đã theo dõi bài trước JWT authentication thì cũng đã biết cách generate cặp key, tương tự như bài đó chúng ta cũng generate cặp key phục vụ việc refresh token.

Có nhiều cách để tạo cặp private key và public key tuy nhiên trong bài viết này tôi hướng dẫn sử dụng các website để tạo cặp key cho ngắn gọn.
  • Truy cập vào website để sinh ra cặp key RSA, chú ý chọn độ dài key là 4096, vì với thư viện jsonwebtoken có version > 9 thì bắt buộc độ dài của khóa phải lớn hơn 2048.
  • Mã hóa cặp key được sinh ra ở phía trên bằng định dạng base64, truy cập vào website để mã hóa
Sau khi có cặp key thì copy cặp key đó dán vào file .env
NODE_ENV=development
MONGODB_USERNAME=inovationthinking
MONGODB_PASSWORD=password123
MONGODB_DATABASE_NAME=jwtAuth

ACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBTlFLQStSV2ZQZFdHR25iYS9WRVo1TUs5cG1nMUlQay9paEE5dXF2Ny8rNVlzRjNUVURoCnFHZXN1bGJhdFFGdkNPaHVmSlNJQmFWT3RjbVZrTWZoWmRrQ0F3RUFBUUpBYkVlTkF6NnpaQzhBR3BhbGc4TmgKelBJdFNmaWFiWnd6dWVTcTh0L1RoRmQrUGhqN2IxTmphdjBMTjNGamhycjlzV3B2UjBBNW13OFpoSUFUNzZMUgpzUUloQU95Zmdhdy9BSTVoeGs3NmtWaVRRV0JNdjdBeERwdi9oSG1aUFdxclpyL1ZBaUVBNVdjalpmK0NaYlhTCnlpV3dUbEVENGVZQ3BSNk16Qk8wbFVhbExKdVRFL1VDSUhWTWZSUE9CNUNObDZqL1BaNFRJWTJEZm1MeGJyU1cKYmkxNWNhQzNaekFoQWlBNmUrVG1hQkdTWkp4c3ROY1I0RTJoRmNhdTJlOERTRExOcThrSWFsRkEwUUloQUlwUApUODFlWlNzYmVrNTlidGJPZ3J3bTJBdzJqUVk4TitJa3FMSTNySWFFCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
ACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTlFLQStSV2ZQZFdHR25iYS9WRVo1TUs5cG1nMUlQawovaWhBOXVxdjcvKzVZc0YzVFVEaHFHZXN1bGJhdFFGdkNPaHVmSlNJQmFWT3RjbVZrTWZoWmRrQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==

REFRESH_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBSnBBM08xOEZQRWtQR3lGZzUrS0xQUjJuSWFsQk1UeXo2bjJhdG1xQVNJZUFIMVBjeDRHCmZWV0pCWjRQUTBGTzlRYzBGYmxwMzB4UTl3WVpYSnBOVDdFQ0F3RUFBUUpBT1JwTDd1cGhRa2VjeXJ1K1Z5QXEKdGpEMmp1Mmx6MWJudzA2Q2phTmVtZ2NWMk9Fa25lbGplQTZOZGNGT3h6N0hRbTduRVVBbXJLV1JBM2htZ2hyNApRUUloQU96RmNGRmJuOUdoSzFrZ0RidWNqSFJYS2JEekcrQXBXbDlzTFVEZGJGMnBBaUVBcHNmWTZWdmJoTU5tCjlEcy9HRHNMZVhKaVVVWG9HNjUveldVQUJTRlpWc2tDSVFDcmFZMFUrWFpNdDVmQVlGcFExdGRBYXRIK0R5TEIKT0c3NjRrQW8wNlRlY1FJZ0gzb2ViVVNoOUxld2FhMzQ1WWpYVEkrVEVNWEIzZCtjVFZhZm4xaEE5VWtDSURNcApCMnVmMk85TDBENm1FbTBkSE5HZU5ITk9yMUhrRC9ZWjBWWFFESFgyCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
REFRESH_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSnBBM08xOEZQRWtQR3lGZzUrS0xQUjJuSWFsQk1UeQp6Nm4yYXRtcUFTSWVBSDFQY3g0R2ZWV0pCWjRQUTBGTzlRYzBGYmxwMzB4UTl3WVpYSnBOVDdFQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==
Update Environment Variables in default.ts
Update thêm trường refreshTokenExpiresIn, vào file config/default.ts
config/default.ts
export default {
  port: 8000,
  accessTokenExpiresIn: 15,
  refreshTokenExpiresIn: 59,
  origin: 'http://localhost:3000',
};
Cập nhật các biến môi trường trong Custom-Environment-Variables.ts
Trong tệp custom-environment-variables.ts, tôi đã thêm hai trường ("refreshTokenPrivateKey" và "refreshTokenPublicKey").

Cả hai thuộc tính này nên có các giá trị trỏ đến các biến chúng ta đã định nghĩa trong tệp .env.
Mô-đun cấu hình sẽ truy xuất các biến đó trong tệp .env khi chúng ta truy cập vào các thuộc tính tương ứng của chúng ("refreshTokenPrivateKey" và "refreshTokenPublicKey").

config/custom-environment-variables.ts
export default {
  dbName: 'MONGODB_USERNAME',
  dbPass: 'MONGODB_PASSWORD',
  accessTokenPrivateKey: 'ACCESS_TOKEN_PRIVATE_KEY',
  accessTokenPublicKey: 'ACCESS_TOKEN_PUBLIC_KEY',
  refreshTokenPrivateKey: 'REFRESH_TOKEN_PRIVATE_KEY',
  refreshTokenPublicKey: 'REFRESH_TOKEN_PUBLIC_KEY',
};
Cập nhật chức năng Sign và verify
Hãy cập nhật các hàm signJwt và verifyJwt.
Mục tiêu là sử dụng cùng một hàm signJwt để ký cả các access token và refresh token.
Tương tự, cùng một logic áp dụng cho hàm xác minh token verifyJwt.

src/utils/jwt.ts
import jwt, { SignOptions } from 'jsonwebtoken';
import config from 'config';

export const signJwt = (
  payload: Object,
  key: 'accessTokenPrivateKey' | 'refreshTokenPrivateKey',
  options: SignOptions = {}
) => {
  const privateKey = Buffer.from(config.get<string>(key), 'base64').toString(
    'ascii'
  );
  return jwt.sign(payload, privateKey, {
    ...(options && options),
    algorithm: 'RS256',
  });
};

export const verifyJwt = <T>(
  token: string,
  key: 'accessTokenPublicKey' | 'refreshTokenPublicKey'
): T | null => {
  try {
    const publicKey = Buffer.from(config.get<string>(key), 'base64').toString(
      'ascii'
    );
    return jwt.verify(token, publicKey) as T;
  } catch (error) {
    console.log(error);
    return null;
  }
};
Phân tích những gì tôi đã làm ở trên:
  • Tôi đã xuất cả hai hàm signJwt và verifyJwt từ tệp jwt.ts.
  • Dựa trên giá trị của khóa được truyền vào hàm signJwt, biến tương ứng sẽ được lấy từ tệp .env bởi mô-đun cấu hình (config module).
  • Sau đó, tôi sử dụng phương thức Buffer.from() của Node.js để giải mã các private key đã được mã hóa thành chuỗi ASCII.
  • Tiếp theo, tôi sử dụng thuật toán RS256 để ký các access token hoặc refresh token và trả về chúng từ hàm signJwt.
  • Trong hàm verifyJwt, tôi đã giải mã các public key và sử dụng phương thức jwt.verify() để xác minh các refresh token và access token.
  • Sau đó, tôi đã làm cho hàm verifyJwt trả về một giá trị generic hoặc null.
Update hàm Sign Token
Hàm signToken có trách nhiệm tạo một phiên người dùng trong cơ sở dữ liệu Redis và cũng ký cả access token và refresh token.
src/services/user.service.ts
// Sign Token
export const signToken = async (user: DocumentType<User>) => {
  // Sign the access token
  const access_token = signJwt({ sub: user._id }, 'accessTokenPrivateKey', {
    expiresIn: `${config.get<number>('accessTokenExpiresIn')}m`,
  });

  // Sign the refresh token
  const refresh_token = signJwt({ sub: user._id }, 'refreshTokenPrivateKey', {
    expiresIn: `${config.get<number>('refreshTokenExpiresIn')}m`,
  });

  // Create a Session
  redisClient.set(user._id, JSON.stringify(user), {
    EX: 60 * 60,
  });

  // Return access token
  return { access_token, refresh_token };
};
Dưới đây là những gì tôi đã làm:
  • Tôi đã export hàm signToken từ tệp user.service.ts.
  • Trong hàm signToken, tôi trước tiên đã ký cả access token và refresh token.
  • Tôi cũng đã cung cấp cho hàm  signJwt thời gian hết hạn của token tương ứng trong phút và sử dụng id của người dùng làm dữ liệu payload.
  • Tiếp theo, tôi đã sử dụng phương thức redisClient.set() để lưu thông tin người dùng vào Redis với id của người dùng làm khóa (key).
  • Cuối cùng, tôi đã xuất access token và refresh token trong một đối tượng.
Update Login Controller

src/controllers/auth.controller.ts
// Cookie options
const accessTokenCookieOptions: CookieOptions = {
  expires: new Date(
    Date.now() + config.get<number>('accessTokenExpiresIn') * 60 * 1000
  ),
  maxAge: config.get<number>('accessTokenExpiresIn') * 60 * 1000,
  httpOnly: true,
  sameSite: 'lax',
};

const refreshTokenCookieOptions: CookieOptions = {
  expires: new Date(
    Date.now() + config.get<number>('refreshTokenExpiresIn') * 60 * 1000
  ),
  maxAge: config.get<number>('refreshTokenExpiresIn') * 60 * 1000,
  httpOnly: true,
  sameSite: 'lax',
};

// Only set secure to true in production
if (process.env.NODE_ENV === 'production')
  accessTokenCookieOptions.secure = true;

export const loginHandler = async (
  req: Request<{}, {}, LoginUserInput>,
  res: Response,
  next: NextFunction
) => {
  try {
    // Get the user from the collection
    const user = await findUser({ email: req.body.email });

    // Check if user exist and password is correct
    if (
      !user ||
      !(await user.comparePasswords(user.password, req.body.password))
    ) {
      return next(new AppError('Invalid email or password', 401));
    }

    // Create the Access and refresh Tokens
    const { access_token, refresh_token } = await signToken(user);

    // Send Access Token in Cookie
    res.cookie('access_token', access_token, accessTokenCookieOptions);
    res.cookie('refresh_token', refresh_token, refreshTokenCookieOptions);
    res.cookie('logged_in', true, {
      ...accessTokenCookieOptions,
      httpOnly: false,
    });

    // Send Access Token
    res.status(200).json({
      status: 'success',
      access_token,
    });
  } catch (err: any) {
    next(err);
  }
};
Bây giờ chúng ta sẽ đi vào phần logic:
  • Trước tiên, tôi đã kiểm tra xem người dùng có email được cung cấp có tồn tại trong cơ sở dữ liệu hay không.
  • Tiếp theo, tôi băm mật khẩu được cung cấp và so sánh nó với mật khẩu đã được băm trong cơ sở dữ liệu.
  • Tiếp theo, nếu không có lỗi nào xuất hiện trong các kiểm tra trên, tôi ký cả access và refresh token.
  • Sau đó, tôi thêm cả access token và refresh token vào đối tượng res.cookie để gửi đến trình duyệt của người dùng.
  • Cuối cùng, tôi gửi một phản hồi JSON chứa access token. Bước cuối cùng này là cần thiết nếu bạn muốn sao chép và dán access token vào Authorization header (Bearer token). Trong production application, hãy đảm bảo bạn gửi cả access token  và refresh token dưới dạng cookie HTTPonly.Để giải thích kỹ đoạn này tôi sẽ viết một bài riêng để trình bày về CookieOptions.
Tạo Controller để Refresh Access Token
Đây là nơi mà logic làm mới access token mới sẽ xảy ra.

Logic về việc làm mới access token phụ thuộc vào dự án của bạn. Trong bài viết này, người dùng có thể làm mới access token sau mỗi 15 phút trong phiên làm việc hợp lệ.

Tóm lại, người dùng có thể sử dụng refresh token bao nhiêu lần cũng được trong phiên làm việc hợp lệ. Người dùng sẽ tự động được chuyển hướng đến trang đăng nhập khi anh ta cố gắng làm mới access token sau khi phiên làm việc đã hết hạn.

Bây giờ, hãy tạo trình xử lý refresh token và dán các đoạn mã dưới đây vào đó.

src/controllers/auth.controller.ts
const logout = (res: Response) => {
  res.cookie('access_token', '', { maxAge: 1 });
  res.cookie('refresh_token', '', { maxAge: 1 });
  res.cookie('logged_in', '', {
    maxAge: 1,
  });
};

export const refreshAccessTokenHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    // Get the refresh token from cookie
    const refresh_token = req.cookies.refresh_token as string;

    // Validate the Refresh token
    const decoded = verifyJwt<{ sub: string }>(
      refresh_token,
      'refreshTokenPublicKey'
    );
    const message = 'Could not refresh access token';
    if (!decoded) {
      return next(new AppError(message, 403));
    }

    // Check if the user has a valid session
    const session = await redisClient.get(decoded.sub);
    if (!session) {
      return next(new AppError(message, 403));
    }

    // Check if the user exist
    const user = await findUserById(JSON.parse(session)._id);

    if (!user) {
      return next(new AppError(message, 403));
    }

    // Sign new access token
    const access_token = signJwt({ sub: user._id }, 'accessTokenPrivateKey', {
      expiresIn: `${config.get<number>('accessTokenExpiresIn')}m`,
    });

    // Send the access token as cookie
    res.cookie('access_token', access_token, accessTokenCookieOptions);
    res.cookie('logged_in', true, {
      ...accessTokenCookieOptions,
      httpOnly: false,
    });

    // Send response
    res.status(200).json({
      status: 'success',
      access_token,
    });
  } catch (err: any) {
    next(err);
  }
};
Dưới đây là những gì tôi đã thực hiện ở trên:
  • Đầu tiên, tôi lấy refresh token từ đối tượng req.cookies.
  • Tiếp theo, tôi xác thực refresh token để đảm bảo nó không bị chỉnh sửa.
  • Sau đó, tôi sử dụng id người dùng mà chúng tôi lưu trữ trong JsonwebToken để kiểm tra xem người dùng có phiên làm việc hợp lệ trong cơ sở dữ liệu Redis hay không. Trong dự án nhỏ này, tôi làm cho thông tin người dùng trong cơ sở dữ liệu Redis hết hạn sau 60 phút.
  • Sau đó, tôi kiểm tra xem người dùng vẫn còn tồn tại trong cơ sở dữ liệu của chúng tôi hay không.
  • Nếu không có lỗi nào từ các kiểm tra trên, tôi sẽ ký một access token mới và gửi nó dưới dạng cookie đến trình duyệt của người dùng.
  • Cuối cùng, tôi gửi access token trong phần thân phản hồi. Bước này chỉ cần thiết để kiểm thử JWT API trong chế độ phát triển. Luôn luôn đảm bảo bạn gửi các token dưới dạng cookie HTTPOnly để giảm nguy cơ bị hack.
Cách để hết hạn các cookie Access và Refresh token JWT
Để hết hạn các cookie Access và Refresh token trong trình duyệt của người dùng, bạn cần gửi các access và refresh token mới dưới dạng cookie đến trình duyệt của người dùng, nhưng các cookie này phải có giá trị là chuỗi rỗng và cũng có giá trị maxAge là 1.

Giá trị maxAge được tính bằng mili giây, vì vậy việc đặt maxAge là 1 sẽ làm cho các cookie hết hạn ngay lập tức khi trình duyệt nhận chúng.
src/controllers/auth.controller.ts
const logout = (res: Response) => {
  res.cookie('access_token', '', { maxAge: 1 });
  res.cookie('refresh_token', '', { maxAge: 1 });
  res.cookie('logged_in', '', {
    maxAge: 1,
  });
};
Tạo một Controller để Đăng xuất Người dùng
Bây giờ, hãy định nghĩa controller cho phép người dùng dễ dàng đăng xuất khỏi ứng dụng.

Khi người dùng thực hiện một yêu cầu GET đến endpoint /api/auth/logout, chúng ta sẽ trước tiên lấy thông tin người dùng từ res.locals.user.

Lưu ý: người dùng chỉ có thể đăng xuất nếu anh ta có access token hợp lệ.

Tiếp theo, hãy xóa phiên làm việc của người dùng khỏi cơ sở dữ liệu Redis.

Cuối cùng, hãy gửi kết quả lại cho client

src/controllers/auth.controller.ts
export const logoutHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
   const user = res.locals.user;
    await redisClient.del(user._id);
    logout(res);
    return res.status(200).json({ status: 'success' });
  } catch (err: any) {
    next(err);
  }
};
Cập nhật Authentication Routes
src/routes/user.route.ts
import express from 'express';
import {
  getAllUsersHandler,
  getMeHandler,
} from '../controllers/user.controller';
import { deserializeUser } from '../middleware/deserializeUser';
import { requireUser } from '../middleware/requireUser';
import { restrictTo } from '../middleware/restrictTo';

const router = express.Router();

router.use(deserializeUser, requireUser);

// Admin Get Users route
router.get('/', restrictTo('admin'), getAllUsersHandler);

// Get my info route
router.get('/me', getMeHandler);

export default router;
src/routes/auth.route.ts
import express from 'express';
import {
  loginHandler,
  logoutHandler,
  refreshAccessTokenHandler,
  registerHandler,
} from '../controllers/auth.controller';
import { deserializeUser } from '../middleware/deserializeUser';
import { requireUser } from '../middleware/requireUser';
import { validate } from '../middleware/validate';
import { createUserSchema, loginUserSchema } from '../schema/user.schema';

const router = express.Router();

// Register user route
router.post('/register', validate(createUserSchema), registerHandler);

// Login user route
router.post('/login', validate(loginUserSchema), loginHandler);

// Refresh access toke route
router.get('/refresh', refreshAccessTokenHandler);

router.use(deserializeUser, requireUser);

// Logout User
router.get('/logout', logoutHandler);

export default router;
Tập tin auth.route.ts chứa các route để:

  • Đăng nhập người dùng
  • Đăng ký người dùng
  • Làm mới access token
  • Đăng xuất người dùng
Update app.ts
Cuối cùng, cập nhật các route trong tệp app.ts để có tiền tố /api.
src/app.ts
require('dotenv').config();
import express, { NextFunction, Request, Response } from 'express';
import morgan from 'morgan';
import config from 'config';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import connectDB from './utils/connectDB';
import userRouter from './routes/user.route';
import authRouter from './routes/auth.route';

const app = express();

// Middleware

// 1. Body Parser
app.use(express.json({ limit: '10kb' }));

// 2. Cookie Parser
app.use(cookieParser());

// 3. Cors
app.use(
  cors({
    origin: config.get<string>('origin'),
    credentials: true,
  })
);

// 4. Logger
if (process.env.NODE_ENV === 'development') app.use(morgan('dev'));

// 5. Routes
app.use('/api/users', userRouter);
app.use('/api/auth', authRouter);

// Testing
app.get(
  '/api/healthChecker',
  (req: Request, res: Response, next: NextFunction) => {
    res.status(200).json({
      status: 'success',
      message: 'Welcome to CodevoWeb????',
    });
  }
);

// UnKnown Routes
app.all('*', (req: Request, res: Response, next: NextFunction) => {
  const err = new Error(`Route ${req.originalUrl} not found`) as any;
  err.statusCode = 404;
  next(err);
});

// Global Error Handler
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
  err.status = err.status || 'error';
  err.statusCode = err.statusCode || 500;

  res.status(err.statusCode).json({
    status: err.status,
    message: err.message,
  });
});

const port = config.get<number>('port');
app.listen(port, () => {
  console.log(`Server started on port: ${port}`);
  // ? call the connectDB function here
  connectDB();
});

Testing các API mới.

Bảng liệt kê các api endpoint của chúng ta

RESOURCEHTTP METHODROUTEDESCRIPTION
usersGET/api/usersreturns all the users and their information
usersGET/api/users/mereturn the logged-in user’s information
usersPOST/api/auth/registerCreate a new user
usersPOST/api/auth/loginLogs the user in
usersGET/api/auth/refreshrefreshes the access token
usersGET/api/auth/logoutlogs the user out

Các API mới được thêm vào đó là refresh và logout
Thực hiện yêu cầu đăng nhập tới endpoint /api/auth/login bằng thông tin đăng nhập email và mật khẩu của người dùng.

Giả sử thông tin đăng nhập hợp lệ, bạn sẽ thấy các cookie mã thông báo truy cập và làm mới trong tab cookie phản hồi của Postman.




Vẫn ở tab api đó sử dụng tiếp api `/api/auth/refresh` chúng ta sẽ lấy được accessToken mới như hình dưới đây.Chú ý là tôi sẽ không sử dụng body để truyền bất cứ token nào mà các token đều lưu ở cookie.Tôi sẽ sử dụng refreshToken để tạo mới accessToken.



Sau khi gọi api `refresh` access và refresh token mới được sinh ra và được save vào cookie

Tổng kết

Qua bài viết này các bạn phần nào cũng hiểu được cách refreshToken, tuy nhiên đây để hoàn thiện chức năng refresh token cho thật bảo mật cũng sẽ là một chủ đề khá dài, hẹn các bạn vào một dịp khác tôi sẽ trình bày thêm về vấn đề này.
Link download sourcecode

Nhận xét

Bài đăng phổ biến từ blog này

Cài đặt SSL cho website sử dụng certbot

Xây dựng một hệ thống comment real-time hoặc chat đơn giản sử dụng Pusher

CÁC BÀI TẬP SQL CƠ BẢN - PART 1

Xây dựng một hệ thống tracking hành vi người dùng (phần 1)

Xây dựng một hệ thống tracking hành vi người dùng (phần 2)

Enterprise architecture trên 1 tờ A4

Web caching (P2)

Bàn về async/await trong vòng lặp javascript

Web caching (P1)

Cài đặt môi trường để code website Rails