
กำลังโหลด...

กำลังโหลด...

Starter เตรียม validation และ error handling ไว้แล้ว ให้เติมเฉพาะ hash, query, compare และ sign เพื่อเห็นหน้าที่ของแต่ละส่วนชัดๆ
JWT (อ่านว่า “เจ-ดับเบิลยู-ที”) คือ Token ชนิดหนึ่ง หรือบัตรผ่านดิจิทัลที่ Server ออกให้หลัง Login สำเร็จ
จำประโยคนี้ก่อน: Token คือบัตรผ่านดิจิทัล; JWT คือ Token รูปแบบหนึ่งที่ Server เซ็นไว้เพื่อตรวจว่าไม่ถูกปลอมหรือแก้ไข
ให้นึกถึงบัตรข้อมือในงานโรงเรียน:
userId: 7verifyToken) ตรวจว่าบัตรออกโดย server จริงและยังไม่หมดอายุflowchart LR
A["1. POST /auth/login\nemail + password"] --> B["2. Server ตรวจด้วย\nbcrypt.compare"]
B -->|"ถูก"| C["3. jwt.sign\nสร้าง token ที่มี userId"]
C --> D["4. Client เก็บ token"]
D --> E["5. Request ถัดไปส่ง\nAuthorization: Bearer token"]
E --> F["6. verifyToken\nตรวจและได้ req.user.userId"]
HTTP แต่ละ Request เป็นอิสระจากกัน Server ไม่จำว่า “คนที่เพิ่ง Login คือใคร” โดยอัตโนมัติ
JWT จึงช่วยให้ Request ถัดไปบอก Server ได้ว่า “ฉันคือ user คนนี้” โดย ไม่ต้องส่ง password ซ้ำทุกครั้ง เช่น:
POST /auth/login → ได้ token
DELETE /books/7 → ส่ง token เพื่อพิสูจน์ว่าเป็นใคร
POST /books → ส่ง token เพื่อบันทึกว่า book เป็นของใคร
| JWT ไม่ใช่ | เพราะอะไร |
|---|---|
| password | ใช้แทน password หลัง Login แต่ห้ามใส่ password ลงใน token |
| การเข้ารหัสลับ | JWT payload อ่านได้ จึงใส่แค่ข้อมูลที่ไม่ลับ เช่น userId |
| สิทธิ์อัตโนมัติ | Token บอกตัวตน; SQL/route ยังต้องตรวจว่าเป็นเจ้าของข้อมูลหรือไม่ |
Secret สำหรับตรวจ JWT อยู่ใน
.envฝั่ง Server เท่านั้น ถ้า secret หลุด คนอื่นอาจสร้างบัตรปลอมได้
const { email, password } = req.body;
const passwordHash = await bcrypt.hash(password, 10);
const result = await pool.query(
`INSERT INTO users (email, password_hash)
VALUES ($1, $2)
RETURNING id, email`,
[email, passwordHash]
);
res.status(201).json(result.rows[0]);
result ไม่ใช่ user โดยตรง แต่เป็น object คำตอบจาก pool.query(...) โดย result.rows คือ array ของ rows ที่ PostgreSQL ส่งกลับมา
INSERT ... RETURNING id, email
↓
result.rows = [{ id: 7, email: "student@example.com" }]
↓
result.rows[0] = user ใหม่หนึ่งคน
↓
res.status(201).json(...) ส่ง user นี้กลับไป
เราเขียน RETURNING id, email แทน RETURNING * เพราะ Response ไม่ควรมี password_hash
| บรรทัด | ข้อมูลก่อน | ข้อมูลหลัง |
|---|---|---|
req.body |
password เดิม | ตัวแปร password |
bcrypt.hash |
password เดิม | passwordHash |
INSERT |
email + hash | row user ใหม่ |
| Response | row user | มี id/email แต่ไม่มี hash |
const result = await pool.query(
"SELECT * FROM users WHERE email = $1",
[email]
);
const user = result.rows[0];
const valid = user && await bcrypt.compare(password, user.password_hash);
if (!valid) {
return res.status(401).json({ message: "Invalid credentials" });
}
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.json({ token });
bcrypt.compare(...) ไม่ได้ถอด hash กลับ แต่คำนวณตรวจว่าตรงกับ password ที่รับมาหรือไม่user && ... ป้องกันกรณีหา email ไม่เจอ แล้วไปอ่าน password_hash ของ undefinedjwt.sign(...) ใส่เพียง userId เพราะ route ถัดไปต้องรู้ว่าใครกำลังขอข้อมูลPOST /auth/register
Content-Type: application/json
{ "email": "student@example.com", "password": "hello123" }
คาดหวัง 201 และ JSON มี id, email
POST /auth/login
Content-Type: application/json
{ "email": "student@example.com", "password": "hello123" }
คาดหวัง 200 และ JSON มี token
เปลี่ยน password เป็นค่าอื่น คาดหวัง 401 และข้อความ Invalid credentials
JWT_SECRET เป็น undefined → ใส่ใน .env และ restart NodeRETURNING id, email แทน RETURNING *user && ก่อน bcrypt.compare
ทำตามนี้ก่อนเติม TODO ใน index.js ของโปรเจกต์ books-api:
books-api แล้วติดตั้ง package สำหรับ hash password และสร้าง JWT Token:npm install bcrypt jsonwebtoken
index.js:import "dotenv/config"; // มีอยู่แล้ว
import express from "express"; // มีอยู่แล้ว
import bcrypt from "bcrypt"; // เพิ่ม
import jwt from "jsonwebtoken"; // เพิ่ม
import { pool } from "./db.js"; // มีอยู่แล้วจาก Chapter 3
.env ว่ามี JWT_SECRET ที่ใส่ไว้ตั้งแต่ Chapter 3 แล้ว จากนั้น restart server:JWT_SECRET=classroom-secret-change-later
TODO ในส่วน Register และ Login:// ภายใน app.post("/auth/register", ...)
const passwordHash = await bcrypt.TODO(password, 10);
const result = await pool.query(
`INSERT INTO users (email, password_hash)
VALUES ($1, $2) RETURNING id, email`,
[email, TODO]
);
// result คือคำตอบจาก PostgreSQL
// rows[0] คือ user ใหม่หนึ่งคนที่ INSERT สำเร็จ
res.status(201).json(result.rows[0]);
// ภายใน app.post("/auth/login", ...)
const result = await pool.query(
"SELECT * FROM users WHERE email = $1",
[email]
);
const user = result.rows[0];
const valid = user && await bcrypt.TODO(password, user.password_hash);
if (!valid) return res.status(401).json({ message: "Invalid credentials" });
const token = jwt.TODO(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.json({ token });
RETURNING id, email ทำให้ PostgreSQL ส่ง user ที่เพิ่งสร้างกลับมาใน result.rows
เราใช้ result.rows[0] ส่ง JSON Response กลับให้ client และตั้งใจ ไม่ส่ง password_hash กลับไป
