Xây dựng Login gRPC API với Node.js và Express (Phần 2)

 



Wellcome back! Sau khi đã giới thiệu tổng quan về gRPC ở phần 1Bài viết này sẽ hướng dẫn bạn cách tạo một server và client gRPC bằng Node.js để xác thực người dùng bằng JSON Web Tokens sử dụng TypeScript, Node.js, PostgreSQL, Redis, Prisma và Docker-compose.

Series này sẽ bao gồm những gì
  • Cách tạo một  gRPC API server method để đăng ký và đăng nhập người dùng. 
  • Cách tạo một gRPC API server methid để xác thực người dùng bằng access  và refresh token. 
  • Cách xây dựng một gRPC client để tương tác với  API gRPC server. 
  • Cách xây dựng một API gRPC server để get thông tin đăng nhập của người dùng.


Điều kiện tiên quyết
Software
  • Kiến thức cơ bản về Node.js và PostgreSQL
  • Hiểu biết  về Prisma và cách sử dụng ORM và pgAdmin. 
  • Đã cài đặt Node.js và Docker
VS Code Extensions
  • DotENV – Nhận đánh dấu cú pháp cho bất kỳ tệp biến môi trường nào có phần mở rộng .env. 
  • Proto3 – Để làm nổi bật cú pháp, xác thực cú pháp, nhận xét dòng và khối, biên dịch, đoạn mã, hoàn thành mã và định dạng mã cho tất cả các tệp kết thúc bằng phần mở rộng .proto. 
Tiện ích mở rộng Proto3 của VS Code sử dụng Protocol buffer compiler được cài đặt ở OS, vì vậy bạn cần cài đặt Protocol buffer compiler.

Bạn truy cập trang web gRPC chính thức để cài đặt Protocol buffer compiler cần thiết cho hệ điều hành của mình.

Theo mặc định, Protocol buffer compiler tìm kiếm các tệp proto trong thư mục gốc và chúng ta cần cho nó biết vị trí của các tệp proto theo cách thủ công. 

Để làm điều đó, hãy mở trang setting trong VS Code và tìm kiếm Proto3. Tiếp theo, nhấp vào liên kết Edit  trong settings.json và thêm tùy chọn này vào cấu hình Protoc.
{
   "options": ["--proto_path=proto"]
}
Thiết lập PostgreSQL và Redis Server
Mở terminal lên và tạo project `node-grpc-prisma`
$ mkdir node-grpc-prisma
Trong thư root của dự án, tạo tệp docker-compose.yml và thêm đoạn mã sau để định cấu hình image Redis và PostgreSQL.

docker-compose.yml
version: '3'
services:
  postgres:
    image: postgres
    container_name: postgres-grpc
    ports:
      - '6500:5432'
    restart: always
    env_file:
      - ./.env
    volumes:
      - postgres-db:/var/lib/postgresql/data
  redis:
    image: redis:latest
    container_name: redis-grpc
    ports:
      - '7001:6379'
    volumes:
      - redis:/data
volumes:
  postgres-db:
  redis:
Tạo file .env để lưu thông tin cài đặt của postgres
.env
DATABASE_PORT=6500
POSTGRES_PASSWORD=password123
POSTGRES_USER=postgres
POSTGRES_DB=grpc-node-prisma
POSTGRES_HOST=postgres
POSTGRES_HOSTNAME=127.0.0.1
Tạo file .gitignore để không public các file nhạy cảm trước khi đẩy lên git
.gitignore
node_modules
.env
Bây giờ khởi động các container postgres và redis bằng lệnh sau
docker-compose up -d
Để stop các container postgres và redis bạn chạy lệnh sau
docker-compose down
Sau khi chạy thành công các bạn có thể thấy thông báo như sau



Tạo các gRPC Protocol Buffer Messages

Trước khi có thể bắt đầu sử dụng gRPC, chúng ta cần tạo các tệp Protocol Buffer để liệt kê các dịch vụ và phương thức RPC bằng protocol buffer language.

Một phương pháp hay nhất là giữ tất cả các tệp Protocol buffer trong một thư mục duy nhất để làm cho project của bạn gọn gàng hơn và cho phép bạn dễ dàng tạo các lớp stub hoặc interface server và client gRPC.
  1. Lớp stub gRPC: Lớp stub là một thành phần trung gian được tạo tự động bởi trình biên dịch Protocol buffer từ tệp proto. Trong gRPC, lớp stub trên máy khách được sử dụng để gọi các phương thức từ máy khách đến máy chủ. Stub đóng vai trò như một giao diện dễ sử dụng giữa mã máy khách và máy chủ gRPC. Nó ẩn đi các chi tiết phức tạp của việc gửi và nhận dữ liệu qua mạng, cho phép người lập trình dễ dàng tương tác với máy chủ gRPC một cách trừu tượng.
  2. Giao diện (Interface) gRPC: Giao diện hoặc interface là một tập hợp các phương thức mà máy chủ gRPC cung cấp và máy khách gRPC có thể gọi. Giao diện gRPC được xác định trong tệp proto bằng cách định nghĩa một service (dịch vụ) và các phương thức trong service đó. Giao diện định nghĩa các phương thức gRPC mà máy khách có thể sử dụng để gửi yêu cầu và nhận phản hồi từ máy chủ. Sau khi đã xác định giao diện trong tệp proto, trình biên dịch Protocol buffer sẽ tạo ra lớp stub tương ứng trên máy khách và máy chủ để triển khai các phương thức trong giao diện.

Bây giờ hãy tạo một thư mục "proto" trong thư mục gốc. Thư mục này sẽ chứa tất cả các tệp Protocol buffer.

Định nghĩa gRPC User Protocol Buffer Message

Để định nghĩa một Protocol buffer message, chúng ta chỉ định phiên bản của ngôn ngữ Protocol buffer ở đầu tệp proto, trong ví dụ của chúng ta là ngôn ngữ Protobuf phiên bản 3.

Tiếp theo, chúng ta xác định tên gói (package) bằng cách sử dụng từ khóa package được cung cấp bởi ngôn ngữ proto3.

Lợi ích của việc sử dụng các gói ProtoBuf
Nhóm các thông điệp Protobuf dưới các gói sẽ giúp chúng ta:
  • Tránh xung đột tên trong các đối tượng thông điệp Protobuf.
  • Dễ dàng mang đi được. Điều này đảm bảo rằng các giao diện TypeScript và các bí danh loại cũng được tạo ra và nhóm lại trong các thư mục.
  • Tải các tệp Protobuf trong Node.js dựa trên các gói chúng thuộc về.
Bây giờ để định nghĩa trường (cặp tên/giá trị) của một thông điệp giao thức buffer, chúng ta chỉ định loại trường, tiếp theo là tên trường và một định danh duy nhất sẽ được sử dụng bởi framework gRPC để dễ dàng xác định các trường trong định dạng nhị phân của thông điệp.

Các số trường nằm trong khoảng từ 1 đến 15 chỉ cần một byte để mã hóa, trong khi các số trong khoảng từ 16 đến 2047 cần hai byte để mã hóa.

Bạn có thể tìm hiểu thêm về cách các trường được mã hóa trên trang web Protocol Buffer Encoding.

Bây giờ chúng ta sẽ tạo một thông điệp Protobuf có thể tái sử dụng mà chúng ta có thể sử dụng trong các thông điệp Protobuf khác:

proto/user.proto
syntax = "proto3";

package auth;

import "google/protobuf/timestamp.proto";

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  string role = 4;
  string photo = 5;
  string provider = 6;
  google.protobuf.Timestamp created_at = 7;
  google.protobuf.Timestamp updated_at = 8;
}

message UserResponse { User user = 1; }

message GenericResponse {
  string status = 1;
  string message = 2;
}
Định nghĩa gRPC Protocol Buffer Message cho Login User
proto/rpc_signin_user.proto
syntax = "proto3";

package auth;

message SignInUserInput {
  string email = 1;
  string password = 2;
}

message SignInUserResponse {
  string status = 1;
  string access_token = 2;
  string refresh_token = 3;
}
Tạo RPC Services và Method
Bây giờ chúng ta đã có RPC messages đã được định nghĩa, tiếp theo chúng ta sẽ tạo các gRPC services và thêm các định nghĩa RPC.

Tạo một file proto/services.proto chứa tất cả các gRPC services và các ProtoBuf mesages

proto/services.proto
syntax = "proto3";

package auth;

import "user.proto";
import "rpc_signup_user.proto";
import "rpc_signin_user.proto";

service AuthService {
  rpc SignUpUser(SignUpUserInput) returns (SignUpUserResponse) {}
  rpc SignInUser(SignInUserInput) returns (SignInUserResponse) {}
  rpc RefreshToken(RefreshTokenInput) returns (RefreshTokenResponse) {}
  rpc GetMe(GetMeInput) returns (UserResponse) {}
}

message GetMeInput { string access_token = 1; }
message RefreshTokenInput { string refresh_token = 1; }
message RefreshTokenResponse {
  string access_token = 1;
  string refresh_token = 2;
}
Ở đoạn code trên, chúng ta đã tạo một AuthService có các methods RPC sau:
  • SignUpUser - Để đăng ký mới một user
  • SignInUser - Để đăng nhập người dùng đã đã đăng ký
  • RefreshToken - Để làm mới access token hết hạn
  • GetMe - Để truy xuất thông tin của người dùng hiện được xác thực
Sau đó, chúng ta sẽ sử dụng phương thức SignUpUser RPC để gửi mã xác minh đến địa chỉ email do người dùng cung cấp.

Bước xác thực này là bắt buộc để đảm bảo rằng chỉ những người dùng có địa chỉ email hợp lệ mới có thể đăng nhập vào ứng dụng gRPC của chúng ta.

Tạo gRPC Client và Server sử dụng typescript
Để bắt đầu, hãy khởi tạo một project Node.js TypeScript mới với lệnh sau:
yarn init -y && yarn add -D typescript && npx tsc --init 
Thay thế nội dung của file "tsconfig.json" theo nội dung sau:

tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "skipLibCheck": true
  }
}
Bây giờ hãy thêm các dependencies cần thiết để tạo file TypeScript từ các định nghĩa Protobuf.
yarn add @grpc/grpc-js @grpc/proto-loader 
  • @grpc/grpc-js - Một thư viện đa dạng tính năng để triển khai grpc cho Nodejs
  • @grpc/grpc-js- Thư viện này chứa các function tiện ích cho việc tải file .proto để sử dụng với gRPC 
Tạo một file proto-gen.sh và thêm script này để giúp chúng ta sinh ra các file TypeScript từ các file Protobuf.

proto-gen.sh
#!/bin/bash

rm -rf pb/
yarn proto-loader-gen-types --longs=String --enums=String --defaults --keepCase --oneofs --grpcLib=@grpc/grpc-js --outDir=pb/ proto/*.proto
Ngoài ra, bạn có thể copy các lệnh riêng lẻ và chạy trực tiếp trên terminal.

Khi mà script trong proto-gen.sh được thực thi, các gói @grpc/proto-loader sẽ sinh ra các file TypeScript từ các proto files và kết quả các file sinh ra sẽ được đặt vào thư mục pb

Bạn có thể mở thư mục pb sẽ thấy các file TypeScripts được sinh ra.

Tạo các JWT Private và Public Keys
Có nhiều cách để tạo ra JSON Web Token Private và publc keys, tuy nhiên trong series này tôi sẽ sử dụng 2 website sau để làm việc đó
  • Tạo cặp private và public keys bằng website này
  • Mã hóa cặp keys trên bằng website này 
Tiếp theo chúng ta sẽ thêm chúng vào file .env
File .env bây giờ sẽ trông giống như sau

.env
DATABASE_PORT=6500
POSTGRES_PASSWORD=password123
POSTGRES_USER=postgres
POSTGRES_DB=grpc-node-prisma
POSTGRES_HOST=postgres
POSTGRES_HOSTNAME=127.0.0.1

DATABASE_URL="postgresql://postgres:password123@localhost:6500/grpc-node-prisma?schema=public"

ACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT1FJQkFBSkFVTWhaQjNucFJ6OEdrc0tneVZJcEZHMkJqZldHdElMWGNLUVFGMHZGbVZvOVVFcDhyOEVmCnI5T204azVTaXgrSi8rbXc0d08xVUlGb25rQTJFWnl6THdJREFRQUJBa0FDcVViOWp3K1hVRVU0S244L2dweGwKMXVHd3VvandnMnJ6aEFRZnNGaFhIKzlyQ1NWTmxNaEk0UWh4bWt3bHI2Y0NhUnFMUGg0Vk5lN3hKRDloWU5XcApBaUVBbXJ4TENiQXJJWDIvYkFETU1JdXljWFZKMnAwUk91S3FQOVBVeTB6ZG0zc0NJUUNGcGs5VDJKQ3NUVEhWCjErMWFVbk9JOFE3eUdNK1VQVGt6a200emNHcE8zUUloQUloOEU3Z2M4ejVjVzQ5WmVNSk5SbjI3VmdTRnpKL2oKTlFhTnc4SDdML0duQWlCTS9lUFJEMzg0WXpnRVV1SGZHSVNLTFNSSS8xWUZ0Y2RRR0ZqM3RSam8yUUlnS2t6ZwpVWFkwWjJRR1dqblFTdzdJRThaSDZuTHcydFUrci9LR0NZRzVKN3M9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
ACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZzd0RRWUpLb1pJaHZjTkFRRUJCUUFEU2dBd1J3SkFVTWhaQjNucFJ6OEdrc0tneVZJcEZHMkJqZldHdElMWApjS1FRRjB2Rm1WbzlVRXA4cjhFZnI5T204azVTaXgrSi8rbXc0d08xVUlGb25rQTJFWnl6THdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t

REFRESH_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT1FJQkFBSkFZOHRTUEZXMTk3bWgwcitCWUdLVTA4OFRPcDkrT2FObVNWQ1lMMTFhb05ZeEY1TSs1d0NSCnNDTnAxVEdHNW5zb215NW9QRitLajFsOGhjbmtUSUU2SndJREFRQUJBa0FVN2dLc1ZzbVlVQjJKWnRMS2xVSmoKZmUycGdPUG5VTWJXSDRvYmZQZlIvWGNteTdONkQyVXVQcnJ0MkdQVUpnNVJ4SG5NbVFpaDJkNHUwY3pqRDhpcApBaUVBcDFNaUtvY1BEWDJDU0lGN3c5SzVGWHlqMjIzQXJQcVJoUzNtL1dkVzVlVUNJUUNZcmxyeXRJOFkvODIzCkQ1ZTFHVExnbDlTcXN1UWdvaGF4ZCtKaXludGZHd0lnQ2xlK0xlakpTbWt1cTNLdGhzNDR1SlpLdnA2TElXWWYKcHA3T3YyMHExdTBDSVFDSy9lYWpuZ1hLLzB3NXcwTWJSUVpRK1VkTDRqRFZHRm5LVTFYUEUzOStVd0lnSEdLWgpjcDd2K3VyeG5kU05GK25MVEpZRG9abkMrKytteXRMaCtSUmU4dVU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
REFRESH_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZzd0RRWUpLb1pJaHZjTkFRRUJCUUFEU2dBd1J3SkFZOHRTUEZXMTk3bWgwcitCWUdLVTA4OFRPcDkrT2FObQpTVkNZTDExYW9OWXhGNU0rNXdDUnNDTnAxVEdHNW5zb215NW9QRitLajFsOGhjbmtUSUU2SndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t
Tiếp theo, hãy tạo file server/config/default.ts để tải các biến môi trường.Ngoài ra file này sẽ chứa các biến môi trường của TypeScript types.
yarn add dotenv
server/config/default.ts
import path from 'path';
require('dotenv').config({ path: path.join(__dirname, '../../.env') });

const customConfig: {
  port: number;
  accessTokenExpiresIn: number;
  refreshTokenExpiresIn: number;
  dbUri: string;
  accessTokenPrivateKey: string;
  accessTokenPublicKey: string;
  refreshTokenPrivateKey: string;
  refreshTokenPublicKey: string;
  redisCacheExpiresIn: number;
} = {
  port: 8000,
  accessTokenExpiresIn: 15,
  refreshTokenExpiresIn: 60,
  redisCacheExpiresIn: 60,

  dbUri: process.env.DATABASE_URL as string,
  accessTokenPrivateKey: process.env.ACCESS_TOKEN_PRIVATE_KEY as string,
  accessTokenPublicKey: process.env.ACCESS_TOKEN_PUBLIC_KEY as string,
  refreshTokenPrivateKey: process.env.REFRESH_TOKEN_PRIVATE_KEY as string,
  refreshTokenPublicKey: process.env.REFRESH_TOKEN_PUBLIC_KEY as string,
};

export default customConfig;
Định nghĩa các function để Sign và Verify JWTs

Cài đặt thư viện và phụ thuộc để sử dụng jwt
yarn add jsonwebtoken && yarn add -D @types/jsonwebtoken 
Tạo 1 file server/utils/jwt.ts và thêm hai functions để sign và verify JWTs

server/utils/jwt.ts
import jwt, { SignOptions } from 'jsonwebtoken';
import customConfig from '../config/default';

export const signJwt = (
  payload: Object,
  key: 'accessTokenPrivateKey' | 'refreshTokenPrivateKey',
  options: SignOptions = {}
) => {
  const privateKey = Buffer.from(customConfig[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(customConfig[key], 'base64').toString(
      'ascii'
    );
    return jwt.verify(token, publicKey) as T;
  } catch (error) {
    console.log(error);
    return null;
  }
};
Xác định các Database Models với Prisma
Tại thời điểm tôi viết bài viết này Prisma hỗ trợ các databases sau:
  • PostgreSQL
  • MySQL
  • SQLite
  • SQL Server
  • MongoDB
  • CockroachDB

Tóm lại, bạn có thể dễ dàng điều chỉnh code trong hướng dẫn này để làm việc với bất kỳ cơ sở dữ liệu nào được Prisma hỗ trợ.

Cài đặt các phụ thuộc theo hướng dẫn sau:
yarn add -D prisma && yarn add @prisma/client
Prisma cung cấp một lệnh init mà chúng ta có thể sử dụng để khởi tạo dự án Prisma. Theo mặc định, lệnh init sử dụng cơ sở dữ liệu PostgreSQL, tuy nhiên, bạn có thể chuyển cờ --datasource-provider để thay đổi loại cơ sở dữ liệu.
npx prisma init --datasource-provider postgresql
Lệnh trên sẽ tạo một thư mục server/prisma có tệp schema.prisma.

File server/prisma/schema.prisma sẽ chứa các cài đặt để kết nối với cơ sở dữ liệu và các mô models thiết để tạo bảng SQL.

Giờ chúng ta sẽ tạo User model trong file server/prisma/schema.prisma như sau:
server/prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}


model User{
  @@map(name: "users")

  id String  @id @default(uuid())
  name String  @db.VarChar(255)
  email String @unique
  photo String? @default("default.png")
  verified Boolean? @default(false) 
  
  password String
  role RoleEnumType? @default(user)

  created_at DateTime @default(now())
  updated_at DateTime @updatedAt

  provider String?
}

enum RoleEnumType {
  user
  admin
}
Database Migration với Prisma
Trước khi có thể chạy lệnh migration Prisma, bạn cần đảm bảo PostgreSQL Docker Container đang chạy.

Bây giờ thêm những script sau vào file package.json
{
"scripts": {
        "db:migrate": "npx prisma migrate dev --name user-entity --create-only --schema ./server/prisma/schema.prisma && yarn prisma generate --schema ./server/prisma/schema.prisma",
    "db:push": "npx prisma db push --schema ./server/prisma/schema.prisma"
  },
}
  • --name – cờ này chỉ định tên của quá trình migration .
  • --schema – cờ này chỉ định đường dẫn đến tệp schema.prisma.
  • --create-only – lệnh này cho biết cho Prisma chỉ tạo tệp migration mà không áp dụng nó.
  • db:migrate – đoạn mã này sẽ thực thi các Prisma migrate và tạo các lệnh.Lệnh "migrate" sẽ tạo ra một tệp migrate mới, trong khi lệnh "generate" sẽ tạo ra các loại TypeScript dựa trên những model được định nghĩa trong tệp "schema.prisma".
  • db:push – đoạn mã này sẽ chạy lệnh db của Prisma để đẩy các thay đổi lên cơ sở dữ liệu và duy trì sự đồng bộ của cơ sở dữ liệu với schema của Prisma.
Nếu bạn sử dụng 1 công cụ để view database thì cũng có thể vào xem bảng mới do chúng ta vừa mới tạo nên, lưu ý: thông tin để truy cập database chúng ta lưu ở file .env


Kết nối PostgreSQL và Redis Servers
Kết nối PostgreSQL Docker Container

server/utils/prisma.ts
import { PrismaClient } from '@prisma/client';

declare global {
  var prisma: PrismaClient | undefined;
}

export const prisma = global.prisma || new PrismaClient();

if (process.env.NODE_ENV !== 'production') {
  global.prisma = prisma;
}

async function connectDB() {
  try {
    await prisma.$connect();
    console.log('? Database connected successfully');
  } catch (error) {
    console.log(error);
    process.exit(1);
  } finally {
    await prisma.$disconnect();
  }
}

export default connectDB;
Kết nối Redis Docker Container
yarn add redis
server/utils/connectRedis.ts
import { createClient } from 'redis';

const redisUrl = `redis://localhost:7001`;
const redisClient = createClient({
  url: redisUrl,
});

const connectRedis = async () => {
  try {
    await redisClient.connect();
    console.log('? Redis client connected...');
    redisClient.set(
      'tRPC',
      '??Welcome to tRPC with React.js, Express and Typescript!'
    );
  } catch (err: any) {
    console.log(err.message);
    process.exit(1);
  }
};

connectRedis();

redisClient.on('error', (err) => console.log(err));

export default redisClient;
Tạo các Prisma Services
server/services/user.service.ts
import { Prisma, User } from '@prisma/client';
import customConfig from '../config/default';
import redisClient from '../utils/connectRedis';
import { signJwt } from '../utils/jwt';
import { prisma } from '../utils/prisma';

export const createUser = async (input: Prisma.UserCreateInput) => {
  return (await prisma.user.create({
    data: input,
  })) as User;
};

export const findUser = async (
  where: Partial<Prisma.UserCreateInput>,
  select?: Prisma.UserSelect
) => {
  return (await prisma.user.findFirst({
    where,
    select,
  })) as User;
};

export const findUniqueUser = async (
  where: Prisma.UserWhereUniqueInput,
  select?: Prisma.UserSelect
) => {
  return (await prisma.user.findUnique({
    where,
    select,
  })) as User;
};

export const updateUser = async (
  where: Partial<Prisma.UserWhereUniqueInput>,
  data: Prisma.UserUpdateInput,
  select?: Prisma.UserSelect
) => {
  return (await prisma.user.update({ where, data, select })) as User;
};

export const signTokens = async (user: Prisma.UserCreateInput) => {
  // 1. Create Session
  redisClient.set(`${user.id}`, JSON.stringify(user), {
    EX: customConfig.redisCacheExpiresIn * 60,
  });

  // 2. Create Access and Refresh tokens
  const access_token = signJwt({ sub: user.id }, 'accessTokenPrivateKey', {
    expiresIn: `${customConfig.accessTokenExpiresIn}m`,
  });

  const refresh_token = signJwt({ sub: user.id }, 'refreshTokenPrivateKey', {
    expiresIn: `${customConfig.refreshTokenExpiresIn}m`,
  });

  return { access_token, refresh_token };
};
Loạt bài này khá là dài vì vậy tôi sẽ chia nhỏ ra, đến đây tôi xin tạm khép lại phần 2.Các bạn vui lòng truy cập phần 3 để theo dõi tiếp.

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