Xác thực cơ bản với Golang

Tram Ho

Trong phần 2 này, chúng ta sẽ cùng tìm hiểu cách viết chức năng đăng ký, đăng nhập, authentication với json web token với Golang và cơ sở dữ liệu MongoDB. Toàn bộ mã nguồn của bài viết này các bạn có thể tham khảo tại đây

1. Khởi tạo thư mục

Câu lệnh go mod init [tên-module] sẽ tạo ra một file là go.mod, file này giúp quản lý các package của ứng dụng. Nó tương tự như việc thực hiện npm init bên Javascript và file go.mod tương tự như file package.json

2. Cấu trúc thư mục

Cấu trúc thư mục sẽ được tổ chức như sau

  • File main.go là file chúng ta thiết lập thông số kỹ thuật chung cho server, khi chạy ứng dụng thì sẽ chạy file này.
  • File go.mod quản lý cách package (như đã giới thiệu ở trên ).
  • .env lưu các biến môi trường của ứng dụng
    auth chứa file xử lý json web token
  • database chứa các file có nhiệm vụ thao tác với MongoDB (connect cơ sở dữ liệu ).
  • models định nghĩa là cấu trúc dữ liệu sẽ được lưu vào collection.
  • routes chứa các hàm xử lý request ứng với từng route
  • tests chứa các file unit test
  • utils chứa một số hàm xử lý cần dùng (parse JSON, …)

3. Coding Login and Register

main.go

  • Dòng đầu tiên trong thân hàm main sử dụng thư viện godotenv để ứng dụng có thể trích xuất các biến môi trường.
  • Phần sau chỉ đơn giản là kiểm tra lỗi.
  • Ứng dụng của chúng ta sẽ sử dụng Multiplexer http/router thay vì Multiplexer mặc định trong thư viện net/http.
  • 2 dòng tiếp theo defined 2 route dùng để đăng nhập, đăng ký. Hàm xử lý của các route tương ứng là LoginRegister trong package auth (folder routes)
  • Server sẽ listen ở port 8000

models/User.go: Định nghĩa cấu trúc dữ liệu trong db

  • struct User cơ bản gồm 3 trường username, emailpassword
  • hàm Hash truyền vào password dạng string, sau đó đưa vào hàm GenerateFromPassword của thư viện brcypt để băm. Dưới đây là prototyped của hàm GenerateFromPassword.

  • hàm CheckPasswordHash để so sánh mật khẩu do người dùng submit có trúng khớp với mật khẩu dạng băm ở trong cơ sở dữ liệu hay không ?

  • hàm Santize dùng để loại bỏ các dấu cách thừa, encode các ký tự đặc biệt của dữ liệu (tránh phần nào các lỗ hổng injection) trước khi lưu vào db .

database/connect.go : Kết nối đến cơ sở dữ liệu

Như trong bài trước, chúng ta kết nối với mongoDB ngay trong hàm main và thực hiện truy vấn. Tuy nhiên, với cách viết chia module như thế này thì sẽ không phù hợp vì các hàm ở package khác sẽ không sử dụng được đối tượng collection được trả về khi kết nối thành công. Lẽ đó, chúng ta sẽ viết hàm connect đến DB ở 1 package riêng và các hàm có thể gọi đến bất cứ lúc nào để thực hiện.

utils/json.go

Vì chúng ta đang viết server bằng Golang thuần nên khi return response cho client cần thực thao tác set status code cho header, chuyển data sang dạng json. Để đơn giản hóa vấn đề thì chúng ta sẽ viết hàm, đặt tên package là res. Như vậy lúc return response sẽ là res.JSON (trông hao hao Express.JS ? )

routes/auth.go: Đăng ký tài khoản

Ban đầu, chúng ta import một loạt package như trên. Ngoài các package local thì còn một số package của bên thứ 3 khác.

  • govalidator: Giúp validate dữ liệu nhận được từ client.
  • bson: Do dữ liệu trong MongoDB được lưu dưới dạng bson nên cần định dạng dữ liệu trước khi truy vấn.

  • Hàm Register truyền vào 3 đối số theo đúng prototype đã được quy định của http/router. Trong route này chúng ta không cần dùng params nên để dấu _ ở trước để bỏ qua.

  • Hơn 10 dòng đầu của hàm làm công việc trích xuất dữ liệu gửi từ client lên và validate bằng govalidator, nếu data gửi lên không hợp lệ thì trả về lỗi 400. Sau đó là làm sạch dữ liệu với hàm Santize.
  • collection := db.Connect(): Connect đến collection users
  • Tiếp theo cần check xem usernameemail đã tồn tại trong hệ thống chưa, nếu đã tồn tại thì trả về lỗi cho client. bson.M là kiểu dữ liệu bson dạng map (key-value). Chi tiết hơn các bạn có thể tham khảo bson go doc
  • Trước khi lưu user vào db thì password cần được băm với hàm Hash trong package models

Chạy app lên
Cái hay của golang thì khi bạn go run main.go thì nó sẽ tự động cài các package đã được import luôn. Như bên Node.JS thì cần chạy npm install trước khi chạy app.

Test bằng PostMan

  • Đăng ký thành công

Check trong database xem đã có bản ghi chưa ?

  • Đăng ký thất bại do trùng thông tin (trả về status 409)

  • Đăng ký thất bại do thiếu hoặc sai thông tin (trả về status 400)

auth/jwt.go: Tạo jsonwebtoken

Với chức năng đăng nhập, chúng ta sẽ sử dụng json web token, khi người dùng đăng nhập thành công, server sẽ trả về 1 token, client sẽ lưu token lại (vào localStorage chẳng hạn ) và gửi lên server cho những lần request sau nhằm định danh người dùng mà không cần phải đăng nhập lại.

Chúng ta sẽ dùng thư viện jwt-go để tạo json web token, token sẽ được mã hóa theo secret key lưu ở .env.

routes/auth.go: Đăng nhập

  • Phần lấy data từ form request và validate cũng tương tự như ở hàm Register chúng ta đã tìm hiểu ở trên.
  • Phần truy vấn cơ cở dữ liệu (FindOne) thì có khác thêm hàm Decode ở phía sau. Hàm Decode sẽ chuyển dữ liệu query về sang dạng map, giúp việc lấy giá trị băm của password ở phía sau dễ dàng hơn. Ví dụ biến result sau khi FindOne xong sẽ có dạng như thế này.

  • Tiếp theo là gọi hàm CheckPasswordHash để xem password gửi từ client khi băm ra có giống với password được lưu trong db không ?
  • Cuối cùng là gọi hàm jwt.Create để tạo token, chúng ta cũng không quên việc kiểm tra lỗi có thể xảy ra.

Test với Postman

  • Đăng nhập thành công
  • Sai mật khẩu
  • Username không tồn tại trong db

Tài liệu tham khảo

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo