Hướng dẫn bảo mật api nodejs

Chào các bạn, sau đây mình xin giới thiệu cho các bạn loạt bài viết về việc Sử dụng TypeScript để viết API bảo mật với Node.js và Express. Trong khuôn khổ 5 bài có thể sẽ không đầy đủ và sai sót nhiều. Mong các bạn comment góp ý. Mình xin cảm ơn.

Tổng quan các bài viết

  1. Giới thiệu và cài đặt ban đầu
  2. Tạo Modeling Data và Services
  3. Tạo Endpoints
  4. Bảo mật API
  5. Quản lý quyền

Mình sẽ ra lần lượt các bài.

Trong bài viết đầu tiên Giới thiệu và cài đặt ban đầu, mình sẽ hướng dẫn cách cài đặt TypeScript trong Node.js và Framework Express sử dụng Hot-Module Replacement của webpack.

Tôi sẽ lấy một ví dụ cụ thể về việc làm một trang web mà liên quan đến menu của nhà hàng. Qua đó sẽ tìm hiểu cách xây dựng API đầy đủ tính năng bằng Node.js, Express và TypeScript, cho phép client thực hiện các thao tác với data trên các resources (ở đây là menu của nhà hàng). Sử dụng TypeScript với Node.js cho phép bạn truy cập vào việc kiểm tra kiểu tĩnh tùy chọn cùng với công cụ mạnh mẽ cho các ứng dụng lớn và các tính năng ECMAScript mới nhất.

Ngoài ra, chúng ta sẽ tìm hiểu cách xác định data models, tạo data service và xây dựng các modular endpoints. Chúng ta cũng có thể sử dụng Auth0 để bảo mật API.

Chúng ta sẽ sử dụng rule trong business để hạn chế quyền truy cập API:

  • Bất cứ ai cũng có thể đọc các item trong menu.
  • Chỉ admin mới được phép CRUD các item trong menu đó.

Để đơn giản, tôi sẽ lưu data ngay trong RAM.

Nội dung

  1. Cài đặt project với Node.js và Express
  2. Cài đặt các dependeccy
  3. Khởi tạo TypeScript với Node.js
  4. Sử dụng biến môi trường env
  5. Tạo Node App với Express sử dụng TypeScript
  6. Set Up Hot-Module Replacement (HMR) for TypeScript with Webpack
1. Cài đặt project với Node.js và Express

Các bạn tạo thư mục mới tùy ý để chứa project. Mình đặt tên là typescript-secure-api và cd vào đó

1

2

3

mkdir typescript-secure-api

cd typescript-secure-api

 

Tiếp theo, khởi tạo một project Node.js mới với file package.json mặc định

1

2

npm init -y

 

2. Cài đặt các dependeccy

Chúng ta cần require một số các dependency để tạo server Express bảo mật với TypeScript.

1

2

npm i express dotenv cors helmet

 

  • Express: Cũng không cần trình bày nhiều. Một framework nhanh, gọn, nhẹ cho Node.js.
  • Dotenv: Dotenv là một module Zero-dependency, nó load các biến môi trường từ file .env vào process.env.
  • CORS: Một middleware Express. Các bạn có thể tham khảo ở đây.
  • Helmet: Một middleware Express, bảo mật các ứng dụng bằng cách setting các HTTP headers khác nhau, giúp giảm thiểu các attack vectors phổ biến.

Cài đặt typescript như các dependency

1

2

npm i -D typescript

 

Để sử dụng TypeScript hiệu quả, cần cài đặt định nghĩa kiểu cho các packages trên

1

2

npm i -D @types/node @types/express @types/dotenv @types/cors @types/helmet

 

Khi một package không có các loại build-in, bạn có thể cài đặt định nghĩa kiểu thông qua @types npm namespace – nơi lưu trữ các định nghĩa kiểu của TypeScript trong project DefiniteTyped. Khi các package được cài đặt, các types sẽ tự động được compiler của TypeScript include vào.

3. Khởi tạo TypeScript với Node.js

Để giúp compiler của TypeScript hiểu cấu trúc project, cần tạo file tsconfig.json trong folder project bằng câu lệnh sau

1

2

npx tsc --init

 

Đó là tất cả những gì bạn cần để định configure project này với các giá trị mặc định.

4. Sử dụng biến môi trường env

Thay vì sử dụng các hard-coded configuration variables trong các file, bạn có thể xác định tất cả các biến đó ở một file và import chúng vào các module nếu cần. File này thương có tên là .env:

1

2

touch .env

 

Trong file, mình định nghĩa PORT của server:

1

2

PORT=7000

 

Bằng cách sử dụng package dotenv, trong bất cứ module nào mình cũng có thể sử dụng biến local được định nghĩa trong file .env

Hướng dẫn bảo mật api nodejs
Hướng dẫn bảo mật api nodejs
Chú ý : Trong file .env có thể chứa các thông tin nhạy cảm VD như API keys,… Vì vậy nên thêm file này vào .gitignore.

5. Tạo Node App với Express sử dụng TypeScript

Để cho project có cấu trúc tốt, mình sẽ thêm một folder src

1

2

mkdir src

 

Trong folder src mình tạo một file tên là endtrypoint.ts

1

2

touch src/endtrypoint.ts

 

Dưới đây sẽ là nội dung của file endtrypoint.ts.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

/**

* Required External Modules

*/

 

/**

* App Variables

*/

 

/**

*  App Configuration

*/

 

/**

* Server Activation

*/

 

/**

* Webpack HMR Activation

*/

 

Tại mỗi phần comment, mình sẽ thêm nội dung như sau:
Phần Required External Modules, thêm các dependency đã cài đặt trước đó và load các biến môi trường trong file .env bằng cách sử dụng method package.json0

1

2

3

4

5

6

7

8

9

10

11

/**

* Required External Modules

*/

 

import * as dotenv from "dotenv";

import express from "express";

import cors from "cors";

import helmet from "helmet";

 

dotenv.config();

 

Phần package.json1, kiểm tra biến môi trường PORT đã được load vào process.env chưa. Nếu ok, sẽ truyền value của nó (dạng number) vào và tạo một instance của Express. Nếu không thì sẽ exit.

1

2

3

4

5

6

7

8

9

10

11

12

/**

* App Variables

*/

 

if (!process.env.PORT) {

   process.exit(1);

}

 

const PORT: number = parseInt(process.env.PORT as string, 10);

 

const app = express();

 

Phần package.json2, liên kết các function middleware trong packages mà đã import vào module entry point.

1

2

3

4

5

6

7

8

/**

*  App Configuration

*/

 

app.use(helmet());

app.use(cors());

app.use(express.json());

 

package.json3 là một collection của 14 middleware function nhỏ mà nó thiết đặt các HTTP Response Headers. Liên kết package.json4 không gồm tất cả các middleware function này, nhưng cung cấp các giá trị mặc định như DNS Prefetch Control, Frameguard, Hide Powered-By, HSTS, IE No Open, Don’t Sniff Mimetype và XSS Filter.

Chúng ta cho phép các CORS request bằng method package.json5. Sau đó, parse các request truyền vào dưới dạng JSON bằng method package.json6, cái sẽ xác định các package.json7 object với package.json8 object gồm các data đã được parse.
Phần package.json9, tạo Express server

1

2

3

4

5

6

7

8

/**

* Server Activation

*/

 

const server = app.listen(PORT, () => {

  console.log(`Listening on port ${PORT}`);

});

 

6. Set Up Hot-Module Replacement (HMR) for TypeScript with Webpack

Quá trình compile TypeScript có thể làm tăng thời gian khởi động. Tuy nhiên, nếu có bất cứ thay đổi nào trong sourcecode, bạn không cần recompile toàn bộ project. Chúng ta có thể thiết lập webpack Hot-Module Replacement (HMR) để giảm đáng kể thời gian khởi động lại khi thực hiện thay đổi.

Cài đặt Webpack Dependencies cho TypeScript

1

2

npm i -D ts-loader webpack webpack-cli webpack-node-externals

 

  • tsconfig.json0: Giúp cho việc tiền xử lý các file TypeScript để bundle ra các file Javascript.
  • tsconfig.json1: Một module bundle, có khả năng transforming, bundling, or packaging bất kỳ tresource hoặc asset nào.
  • tsconfig.json2: module cung cấp một bộ các lệnh để tăng tốc độ khi cài đặt một webpack project.
  • tsconfig.json3: dễ dàng exclude Node.js module từ webpack bundle.

Trong thư mục project tạo file tsconfig.json4

1

2

touch webpack.config.ts

 

Với nội dung dưới đây

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

const webpack = require("webpack");

const path = require("path");

const nodeExternals = require("webpack-node-externals");

 

module.exports = {

  entry: ["webpack/hot/poll?100", "./src/endtrypoint.ts"],

  watch: true,

  target: "node",

  externals: [

    nodeExternals({

      whitelist: ["webpack/hot/poll?100"]

    })

  ],

  module: {

    rules: [

      {

        test: /.tsx?$/,

        use: "ts-loader",

        exclude: /node_modules/

      }

    ]

  },

  mode: "development",

  resolve: {

    extensions: [".tsx", ".ts", ".js"]

  },

  plugins: [new webpack.HotModuleReplacementPlugin()],

  output: {

    path: path.join(__dirname, "dist"),

    filename: "endtrypoint.js"

  }

};

 

Có nhiều điều cần nói trong file config webpack này, nhưng tôi chỉ đề cập đến những task ở high-level cần hiểu:

  • Xác định file entry trong project: tsconfig.json5 .
  • Định nghĩa loader mà webpack cần để compile file source: tsconfig.json0 cho các file có đuôi là tsconfig.json7 và tsconfig.json8
  • Định nghĩa directory, nơi mà webpack sẽ lưu trữ các file source đã compiled: đó là directory tsconfig.json9

Enable Hot-Module Replacement trong ứng dụng Express

Để Enable Hot-Module Replacement (HMR) trong Express server, cần update lại file endtrypoint.ts.
Trong fileendtrypoint.ts, tại phần .env2

1

2

3

4

5

6

7

8

9

/**

* Webpack HMR Activation

*/

 

if (module.hot) {

   module.hot.accept();

   module.hot.dispose(() => server.close());

}

 

Bình tĩnh, hãy đọc tiếp phần dưới khi thấy lỗi báo đỏ .env3.

Ở đây, chúng ta nhận thấy rằng Hot-Module Replacement đã enable thông qua HotModuleReplocationPlugin cho module được xác định trong endtrypoint.ts. Nếu HMR đã enable, trong module sẽ có interface .env5, cái mà có thể sử dụng để thực hiện các thao tác trên module.

Sử dụng interface method accept() để accept các module và các dependency của nó cập nhật. Sau đó, interface method dispose() được thực hiện khi module cuối cùng được thay thế. Các hành động xóa hay thoát sẽ xóa mọi resources đã tạo, VD kết thúc ứng dụng Node.js và thoát tất cả các kết nối hiện có.

Do .env6 không được định nghĩa bất kì đâu trong file, nên TypeScript compiler sẽ quăng ra một error: Thuộc tính .env3 chưa được định nghĩa. Để fix, chúng ta sẽ sử dụng .env8 – một thư viện quản lý state của Angular

.env8 cung cấp một src0, cái mà được định nghĩa cho webpack hot module. Hãy update lại phần .env2 như sau

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

/**

* Webpack HMR Activation

*/

 

type ModuleId = string | number;

 

interface WebpackHotModule {

  hot?: {

    data: any;

    accept(

      dependencies: string[],

      callback?: (updatedDependencies: ModuleId[]) => void,

    ): void;

    accept(dependency: string, callback?: () => void): void;

    accept(errHandler?: (err: Error) => void): void;

    dispose(callback: (data: any) => void): void;

  };

}

 

declare const module: WebpackHotModule;

 

if (module.hot) {

  module.hot.accept();

  module.hot.dispose(() => server.close());

}

 

Run server Node-Express và TypeScript

Với việc cài đặt HMR, cần cài đặt thêm 2 script trong file package.json, đó là src3 và tsconfig.json1 (để ý tên file endtrypoint.js nhé)

1

2

3

4

5

6

"main": "endtrypoint.js",

"scripts": {

    "start": "node dist/endtrypoint",

    "webpack": "webpack --config webpack.config.ts"

  },

 

tsconfig.json1 script sẽ chạy và cấu hình tsconfig.json4và bundle ra file src7 trong directory tsconfig.json9.

Để chạy, chúng ta sẽ mở 2 cửa sổ terminal, một cái để gọi tới tsconfig.json1

1

2

npm run webpack

 

Một cái để gọi tới src3

1

2

npm start

 

Trong terminal chạy src3, hiển thị message “Listening on port 7000” -> OK.

Như vậy, mình đã xong phần một hướng dẫn Giới thiệu và cài đặt ban đầu cho loạt seri về việc Sử dụng TypeScript để viết API bảo mật với Node.js và Express.