

1. ภาพรวม#
บทความนี้จะแสดงวิธีการสร้าง RESTful API โดยให้มี การยืนยันตัวตนและการอนุญาต (Authentication and Authorization) ด้วย JWT (JSON Web Tokens) พร้อมกับ Role-Based Access Control (RBAC) โดยระบบประกอบด้วย:
- JWT Authentication (Access token)
- สิทธิ์ตาม Role: ADMIN, EDITOR, READER
- Prisma ORM กับฐานข้อมูล MySQL
- Articles API พร้อม CRUD operations และสิทธิ์เฉพาะ Role
ตารางสิทธิ์ (Permission Matrix)#
การกระทำ (Action) | ADMIN | EDITOR | READER |
---|---|---|---|
สร้าง Article | ✅ | ✅ | ❌ |
อ่าน Articles | ✅ | ✅ | ✅ |
แก้ไข Article | ✅ | ✅ | ❌ |
ลบ Article | ✅ | ❌ | ❌ |
2. การตั้งค่าโปรเจค#
2.1. เริ่มต้นโปรเจค#
สร้างโฟลเดอร์โปรเจคใหม่และเริ่มต้น npm:
mkdir jwt-rbac-demo
cd jwt-rbac-demo
npm init -y
bash2.2. ติดตั้ง Dependencies#
ติดตั้งPackageที่จำเป็น:
npm install express jsonwebtoken bcryptjs cors body-parser
npm install @prisma/client
npm install -D prisma
bashคำอธิบาย Package ที่ติดตั้ง:
Package | วัตถุประสงค์ |
---|---|
express | เฟรมเวิร์ก API |
jsonwebtoken | สร้าง/ตรวจสอบ JWT tokens |
bcryptjs | เข้ารหัสรหัสผ่านอย่างปลอดภัย |
cors | เปิดใช้งาน cross-origin requests |
body-parser | แยกวิเคราะห์ JSON request bodies |
@prisma/client | Prisma client สำหรับ DB queries |
prisma (dev) | Prisma CLI สำหรับ migrations และ schema setup |
3. การตั้งค่าฐานข้อมูล Prisma#
3.1. เริ่มต้น Prisma#
เริ่มต้น Prisma ในโปรเจค:
npx prisma init
bashคำสั่งนี้จะสร้าง:
prisma/schema.prisma
– Database schema.env
– Database credentials และ secrets
3.2. กำหนดค่า Database Schema#
ใน prisma/schema.prisma
:
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String
role Role @default(READER)
articles Article[]
}
model Article {
id Int @id @default(autoincrement())
title String
content String
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
}
enum Role {
ADMIN
EDITOR
READER
}
prismaคุณสมบัติหลัก:
role
: Enum ที่มี 3 ระดับการเข้าถึงarticles
: ความสัมพันธ์แบบ one-to-many (User → Articles)@unique
: ชื่อผู้ใช้ต้องไม่ซ้ำ
3.3. การกำหนดค่า Environment#
อัปเดตไฟล์ .env
:
DATABASE_URL="mysql://root:@localhost:3306/jwt_rbac_demo"
JWT_ACCESS_TOKEN_SECRET="supersecretaccesstoken"
plaintextตรวจสอบให้แน่ใจว่ามีฐานข้อมูล MySQL ชื่อ jwt_rbac_demo
3.4. รัน Database Migration#
สร้างและใช้ database schema:
npx prisma migrate dev --name init
npx prisma generate
bashตรวจสอบตารางที่ถูกสร้างขึ้นในฐานข้อมูล jwt_rbac_demo
3.5. Seed ผู้ใช้ Admin เริ่มต้น#
สร้าง hash รหัสผ่านสำหรับผู้ใช้ admin:
// password.js
const bcrypt = require('bcryptjs');
const hashedPassword = bcrypt.hashSync("admin", 10);
console.log(hashedPassword);
javascriptnode password.js
bashเปิด Prisma Studio เพื่อเพิ่มผู้ใช้ admin:
npx prisma studio
bashเพิ่มผู้ใช้ด้วย:
- username:
admin
- password:
<hashed_password_from_above>
- role:
ADMIN
4. การพัฒนา API#
4.1. การตั้งค่า Server#
สร้าง index.js
พร้อมการตั้งค่า Express พื้นฐาน:
// นำเข้า modules ที่จำเป็น
const express = require("express"); // Web framework สำหรับ Node.js
const jwt = require("jsonwebtoken"); // ไลบรารีสำหรับสร้างและตรวจสอบ JWT tokens
const bcrypt = require("bcryptjs"); // ไลบรารีสำหรับเข้ารหัสรหัสผ่าน
const bodyParser = require("body-parser"); // Middleware เพื่อแยกวิเคราะห์ JSON request bodies
const cors = require("cors"); // Middleware เพื่อเปิดใช้งาน Cross-Origin Resource Sharing
const { PrismaClient } = require("@prisma/client"); // Prisma ORM client
// เริ่มต้น Prisma client สำหรับการดำเนินการฐานข้อมูล
const prisma = new PrismaClient();
// เริ่มต้น Express application
const app = express();
// การตั้งค่า Middleware
app.use(bodyParser.json()); // แยกวิเคราะห์ JSON payloads ที่เข้ามา
app.use(cors()); // เปิดใช้งาน CORS สำหรับทุก routes
// JWT secrets จาก environment variables
// ค่าเหล่านี้ควรเป็น string ที่แข็งแกร่งและสุ่มใน production
const ACCESS_TOKEN_SECRET = process.env.JWT_ACCESS_TOKEN_SECRET;
// เริ่มต้น server
const PORT = 4000;
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
console.log(`Prisma Studio: npx prisma studio`);
});
javascriptจุดสำคัญที่อธิบาย:
- Prisma Client: จัดการการดำเนินการฐานข้อมูลทั้งหมดด้วย type safety
- JWT Secrets: ใช้สำหรับ sign และ verify tokens - รักษาความปลอดภัยไว้!
- การจัดเก็บในหน่วยความจำ: ง่ายสำหรับ demos แต่ใช้การจัดเก็บแบบถาวรใน production
- ลำดับ Middleware: Body parser และ CORS ถูกใช้ก่อน routes
เริ่มต้น server:
# สร้าง Prisma client (รันหลังจากเปลี่ยน schema)
npx prisma generate
# เริ่มต้น server พร้อม environment variables ที่โหลดแล้ว
node --env-file=.env index.js
bash5. Authentication Endpoints#
5.1. การลงทะเบียนผู้ใช้ (User Registration)#
// POST /register - สร้างบัญชีผู้ใช้ใหม่
app.post("/register", async (req, res) => {
// ดึงข้อมูลผู้ใช้จาก request body
const { username, password, role } = req.body;
// เข้ารหัสรหัสผ่านด้วย bcrypt (salt rounds = 10)
// rounds ที่สูงขึ้น = ปลอดภัยมากขึ้นแต่ช้าลง
const hashedPassword = await bcrypt.hash(password, 10);
try {
// สร้างผู้ใช้ใหม่ในฐานข้อมูลโดยใช้ Prisma
const user = await prisma.user.create({
data: {
username, // ชื่อผู้ใช้ที่ไม่ซ้ำ
password: hashedPassword, // รหัสผ่านที่เข้ารหัสแล้ว (ไม่เก็บข้อความธรรมดา!)
role // Role ของผู้ใช้: ADMIN, EDITOR, หรือ READER
},
});
// ส่งคืน response สำเร็จ (ไม่รวมข้อมูลที่ละเอียดอ่อนเช่นรหัสผ่าน)
res.json({
message: "User registration successful",
user: {
id: user.id,
username: user.username,
role: user.role
}
});
} catch (err) {
// จัดการข้อผิดพลาดของฐานข้อมูล (เช่น ชื่อผู้ใช้ซ้ำ)
res.status(400).json({
message: "User registration failed",
error: err.message
});
}
});
javascriptหมายเหตุความปลอดภัย:
- การเข้ารหัสรหัสผ่าน: ไม่เก็บรหัสผ่านที่เป็นข้อความธรรมดา (plaintext)
- Salt Rounds: 10 rounds ให้ความสมดุลที่ดีระหว่างความปลอดภัยและประสิทธิภาพ
- การจัดการข้อผิดพลาด: จับข้อผิดพลาดการละเมิดข้อจำกัดของฐานข้อมูล (ชื่อผู้ใช้ซ้ำ)
- การกรอง Response: ไม่ส่งคืนรหัสผ่านใน API responses
ทดสอบด้วย Postman:
1. ลงทะเบียนผู้ใช้ ADMIN:
- Method:
POST
- URL:
http://localhost:4000/register
- Headers:
Content-Type: application/json
- Body:
{
"username": "admin2",
"password": "admin123",
"role": "ADMIN"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อมข้อมูลผู้ใช้ที่สร้าง
2. ลงทะเบียนผู้ใช้ EDITOR:
- Method:
POST
- URL:
http://localhost:4000/register
- Headers:
Content-Type: application/json
- Body:
{
"username": "editor1",
"password": "password123",
"role": "EDITOR"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
3. ลงทะเบียนผู้ใช้ READER:
- Method:
POST
- URL:
http://localhost:4000/register
- Headers:
Content-Type: application/json
- Body:
{
"username": "reader1",
"password": "reader123",
"role": "READER"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
5.2. การเข้าสู่ระบบผู้ใช้ (User Login)#
// POST /login - เข้าสู่ระบบและรับ JWT tokens
app.post("/login", async (req, res) => {
try {
// ดึงข้อมูลผู้ใช้จาก request body
const { username, password } = req.body;
// ตรวจสอบ input
if (!username || !password) {
return res.status(400).json({
message: "Username and password are required"
});
}
// ค้นหาผู้ใช้ในฐานข้อมูลด้วย username
const user = await prisma.user.findUnique({
where: { username }
});
// ตรวจสอบว่าผู้ใช้มีอยู่และรหัสผ่านถูกต้อง
if (!user || !await bcrypt.compare(password, user.password)) {
return res.status(401).json({
message: "Invalid username or password"
});
}
// สร้าง JWT payload (ข้อมูลที่จะเก็บใน token)
const tokenPayload = {
id: user.id,
username: user.username,
role: user.role
};
// สร้าง access token (อายุ 15 นาที)
const accessToken = jwt.sign(
tokenPayload,
ACCESS_TOKEN_SECRET,
{ expiresIn: "15m" }
);
// ส่งคืน response สำเร็จพร้อม tokens
res.json({
message: "Login successful",
accessToken,
user: {
id: user.id,
username: user.username,
role: user.role
}
});
} catch (err) {
console.error('Login error:', err);
res.status(500).json({
message: "Login failed",
error: err.message
});
}
});
javascriptคุณสมบัติหลักของ Login Endpoint:
- การตรวจสอบข้อมูล: ตรวจสอบว่ามีทั้ง username และ password
- การค้นหาผู้ใช้: ใช้ Prisma เพื่อค้นหาผู้ใช้ด้วย username ที่ไม่ซ้ำ
- การตรวจสอบรหัสผ่าน: ใช้ bcrypt.compare() เพื่อตรวจสอบรหัสผ่านที่เข้ารหัส
- การสร้าง JWT: สร้าง access token พร้อมข้อมูลผู้ใช้และอายุการใช้งาน
- การจัดการข้อผิดพลาด: จัดการกับข้อผิดพลาดต่างๆ อย่างเหมาะสม
Token Payload ประกอบด้วย:
- id: User ID สำหรับอ้างอิง
- username: ชื่อผู้ใช้
- role: บทบาทของผู้ใช้ (ADMIN, EDITOR, READER)
ความปลอดภัย:
- ไม่ส่งคืนรหัสผ่านใน response
- ใช้ข้อความข้อผิดพลาดทั่วไปเพื่อป้องกันการรั่วไหลของข้อมูล
- Token มีอายุสั้น (15 นาที) เพื่อลดความเสี่ยง
ทดสอบด้วย Postman:
1. เข้าสู่ระบบ ADMIN:
- Method:
POST
- URL:
http://localhost:4000/login
- Headers:
Content-Type: application/json
- Body:
{
"username": "admin",
"password": "admin"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อม access token
2. เข้าสู่ระบบ EDITOR:
- Method:
POST
- URL:
http://localhost:4000/login
- Headers:
Content-Type: application/json
- Body:
{
"username": "editor1",
"password": "password123"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อม access token
3. เข้าสู่ระบบ READER:
- Method:
POST
- URL:
http://localhost:4000/login
- Headers:
Content-Type: application/json
- Body:
{
"username": "reader1",
"password": "reader123"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อม access token
6. Authentication & Authorization Middleware#
Middleware functions เป็นกระดูกสันหลังของความปลอดภัย Express.js พวกมันทำงานก่อน route handlers และสามารถ:
- ยืนยันตัวตน (Authenticate): ตรวจสอบว่าผู้ใช้คือใคร
- อนุญาตตามสิทธิ์ (Authorize): ตรวจสอบว่าผู้ใช้สามารถทำอะไรได้บ้าง
- แปลง (Transform): แก้ไขข้อมูล request/response
- บันทึก (Log): ติดตามการเข้าถึงและการกระทำ
ระบบ RBAC นี้ใช้ middleware functions สองตัวหลักที่ทำงานร่วมกันเพื่อให้การควบคุมการเข้าถึงที่ปลอดภัย
6.1. Token Authentication Middleware#
/**
* Middleware เพื่อยืนยันตัวตน JWT tokens
* ดึงและตรวจสอบ Bearer token จาก Authorization header
* แนบข้อมูลผู้ใช้ที่ถอดรหัสแล้วไปยัง req.user เพื่อใช้ใน middleware/routes ต่อไป
*/
function authenticateToken(req, res, next) {
// รับ Authorization header (รูปแบบ: "Bearer <token>")
const authHeader = req.headers["authorization"];
// ดึง token จากรูปแบบ "Bearer <token>"
// authHeader?.split(" ")[1] รับส่วน token หลัง "Bearer "
const token = authHeader && authHeader.split(" ")[1];
// ตรวจสอบว่า token มีอยู่
if (!token) {
return res.status(401).json({
message: "Access denied. No token provided.",
hint: "Include 'Authorization: Bearer <token>' header"
});
}
// ตรวจสอบลายเซ็นและการหมดอายุของ token
jwt.verify(token, ACCESS_TOKEN_SECRET, (err, user) => {
if (err) {
// Token ไม่ถูกต้องหรือหมดอายุ
const message = err.name === 'TokenExpiredError'
? "Token has expired. Please refresh your token."
: "Invalid token. Please log in again.";
return res.status(403).json({
message,
error: err.name
});
}
// Token ถูกต้อง - แนบข้อมูลผู้ใช้ไปยัง request object
// สิ่งนี้ทำให้ข้อมูลผู้ใช้พร้อมใช้งานใน middleware/routes ต่อไป
req.user = user;
// ดำเนินการต่อไปยัง middleware/route handler ถัดไป
next();
});
}
javascriptวิธีการทำงานของ Authentication Middleware:
- การดึง Header: มองหา
Authorization: Bearer <token>
- การตรวจสอบ Token: ตรวจสอบลายเซ็นโดยใช้ secret key
- การตรวจสอบการหมดอายุ: ตรวจสอบให้แน่ใจว่า token ยังไม่หมดอายุ
- การแนบผู้ใช้: เพิ่มข้อมูลผู้ใช้ที่ถอดรหัสแล้วไปยัง
req.user
- การควบคุมการไหล: เรียก
next()
เพื่อดำเนินการต่อหรือส่งคืนข้อผิดพลาด
6.2. Role-Based Authorization Middleware#
/**
* Middleware factory เพื่อตรวจสอบ roles ที่อนุญาต
* ส่งคืน middleware function ที่ตรวจสอบ role ของผู้ใช้เทียบกับ allowed roles
* ต้องใช้หลัง authenticateToken middleware เสมอเนื่องจากต้องการ req.user
*
* @param {...string} allowedRoles - Roles ที่อนุญาตให้เข้าถึง (เช่น 'ADMIN', 'EDITOR')
* @returns {Function} Express middleware function
*/
function authorizeRoles(...allowedRoles) {
return (req, res, next) => {
// ตรวจสอบว่าผู้ใช้ถูกยืนยันตัวตนแล้ว (จาก authenticateToken middleware)
if (!req.user) {
return res.status(401).json({
message: "Authentication required. Please log in first."
});
}
// ตรวจสอบว่า role ของผู้ใช้อยู่ใน allowed roles
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({
message: "Access forbidden. Insufficient permissions.",
required: allowedRoles,
userRole: req.user.role
});
}
// Role ของผู้ใช้ได้รับอนุญาต - ดำเนินการต่อ
next();
};
}
javascriptกระบวนการ Role Authorization:
- Middleware Factory:
authorizeRoles()
สร้าง middleware ที่กำหนดเอง - การตรวจสอบผู้ใช้: ตรวจสอบให้แน่ใจว่า
req.user
มีอยู่ (จากการยืนยันตัวตน) - การตรวจสอบ Role: เปรียบเทียบ role ของผู้ใช้กับ allowed roles
- การควบคุมการเข้าถึง: อนุญาตหรือปฏิเสธการเข้าถึงตาม role
รูปแบบการใช้งาน:
// Role เดียว
app.get('/admin', authenticateToken, authorizeRoles('ADMIN'), handler);
// หลาย roles
app.post('/articles', authenticateToken, authorizeRoles('ADMIN', 'EDITOR'), handler);
// ผู้ใช้ที่ยืนยันตัวตนแล้วทุกคน (ไม่ต้องตรวจสอบ role)
app.get('/profile', authenticateToken, handler);
javascriptตัวอย่าง Middleware Chain:
// ตัวอย่าง 1: Route เฉพาะ admin
app.post("/admin-only",
authenticateToken, // ก่อน: ตรวจสอบ JWT token
authorizeRoles("ADMIN"), // สอง: ตรวจสอบ role
(req, res) => {
res.json({
message: "Access granted to Admin",
user: req.user.username // ข้อมูลผู้ใช้พร้อมใช้งานจาก token
});
}
);
// ตัวอย่าง 2: Route หลาย role (ผู้สร้างเนื้อหา)
app.post("/create-content",
authenticateToken,
authorizeRoles("ADMIN", "EDITOR"),
(req, res) => {
res.json({ message: "Permission granted to create content" });
}
);
// ตัวอย่าง 3: Route สาธารณะ (ไม่ต้องใช้ middleware)
app.get("/public", (req, res) => {
res.json({ message: "This is public information" });
});
// ตัวอย่าง 4: ยืนยันตัวตนแต่ role ใดก็ได้
app.get("/profile",
authenticateToken, // ต้องยืนยันตัวตนเท่านั้น
(req, res) => {
res.json({
profile: {
id: req.user.id,
username: req.user.username,
role: req.user.role
}
});
}
);
javascriptลำดับ Middleware มีความสำคัญ:
- ใช้
authenticateToken
ก่อนauthorizeRoles
เสมอ - การยืนยันตัวตนตั้งค่า
req.user
ที่การอนุญาตต้องการ - ทั้งสองต้องมาก่อน route handler ของคุณ
7. Articles API พร้อม RBAC#
ในส่วนนี้จะสร้าง CRUD (Create, Read, Update, Delete) API สำหรับ articles โดยแสดงให้เห็นว่าสิทธิ์ตาม role ทำงานอย่างไรในทางปฏิบัติ แต่ละ endpoint จะมีความต้องการสิทธิ์ที่แตกต่างกันตามโมเดลความปลอดภัย
7.1. สร้าง Article (ADMIN, EDITOR)#
// POST /articles - สร้าง article ใหม่
// สิทธิ์: ADMIN และ EDITOR สามารถสร้าง articles ได้
app.post("/articles",
authenticateToken, // ตรวจสอบ JWT token
authorizeRoles("ADMIN", "EDITOR"), // ตรวจสอบ role ของผู้ใช้
async (req, res) => {
try {
// ดึงข้อมูล article จาก request body
const { title, content } = req.body;
// สร้าง article ในฐานข้อมูล
// authorId มาจากผู้ใช้ที่ยืนยันตัวตนแล้ว (req.user.id)
const article = await prisma.article.create({
data: {
title,
content,
authorId: req.user.id // เชื่อม article กับผู้ใช้ที่ยืนยันตัวตนแล้ว
},
// รวมข้อมูลผู้เขียนใน response
include: {
author: {
select: {
id: true,
username: true,
role: true
// ไม่รวมรหัสผ่านเพื่อความปลอดภัย
}
}
}
});
// ส่งคืน article ที่สร้างแล้วพร้อมข้อมูลผู้เขียน
res.status(201).json({
message: "Article created successfully",
article
});
} catch (err) {
console.error('Create article error:', err);
res.status(500).json({
message: "Failed to create article",
error: err.message
});
}
}
);
javascriptคุณสมบัติหลัก:
- การตรวจสอบ Input: ตรวจสอบฟิลด์ที่จำเป็น
- การกำหนดผู้เขียน: เชื่อมโยงกับผู้ใช้ที่ยืนยันตัวตนแล้วโดยอัตโนมัติ
- การเพิ่มเติม Response: รวมรายละเอียดผู้เขียน
- การจัดการข้อผิดพลาด: จับข้อผิดพลาดของฐานข้อมูลอย่างงดงาม
ทดสอบด้วย Postman:
1. สร้าง Article โดย ADMIN:
- Method:
POST
- URL:
http://localhost:4000/articles
- Headers:
Authorization: Bearer {{accessToken_admin}}
Content-Type: application/json
- Body:
{
"title": "Test Article from Admin",
"content": "This is article content created by Admin"
}
json- การใส่ Token ใน Request เลือก Auth > Bearer Token
- ผลลัพธ์ที่คาดหวัง:
201 Created
พร้อมข้อมูล article ที่สร้าง
2. สร้าง Article โดย EDITOR:
- Method:
POST
- URL:
http://localhost:4000/articles
- Headers:
Authorization: Bearer {{accessToken_editor}}
Content-Type: application/json
- Body:
{
"title": "Article from Editor",
"content": "Editor can create articles"
}
json- ผลลัพธ์ที่คาดหวัง:
201 Created
3. สร้าง Article READER EDITOR (ควรล้มเหลว):
- Method:
POST
- URL:
http://localhost:4000/articles
- Headers:
Authorization: Bearer {{accessToken_reader}}
Content-Type: application/json
- Body:
{
"title": "Attempted creation by Reader",
"content": "This should fail"
}
json- ผลลัพธ์ที่คาดหวัง:
403 Forbidden
- “Access forbidden. Insufficient permissions”
7.2. อ่าน Articles (ทุก ROLES)#
// GET /articles - ดึงข้อมูล articles ทั้งหมด
// สิทธิ์: ผู้ใช้ที่ยืนยันตัวตนแล้วทุกคนสามารถอ่าน articles ได้
app.get("/articles",
authenticateToken, // ต้องยืนยันตัวตนเท่านั้น ไม่จำกัด role
async (req, res) => {
try {
// ดึง articles ทั้งหมดจากฐานข้อมูล
const articles = await prisma.article.findMany({
include: {
author: {
select: {
id: true,
username: true,
role: true
// ไม่รวมรหัสผ่านเพื่อความปลอดภัย
}
}
}
});
// ส่งคืน articles ทั้งหมด
res.json({
articles,
total: articles.length
});
} catch (err) {
console.error('Fetch articles error:', err);
res.status(500).json({
message: "Failed to fetch articles",
error: err.message
});
}
}
);
javascriptคุณสมบัติ:
- การดึงข้อมูลง่าย: ดึง articles ทั้งหมดจากฐานข้อมูล
- รวมข้อมูลผู้เขียน: แสดงรายละเอียดผู้เขียนแต่ละ article
- ความปลอดภัย: ไม่แสดงรหัสผ่านในข้อมูลผู้เขียน
ทดสอบด้วย Postman:
1. ทดสอบด้วย ADMIN:
- Method:
GET
- URL:
http://localhost:4000/articles
- Headers:
Authorization: Bearer {{accessToken_admin}}
- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อมรายการ articles ทั้งหมด
2. ทดสอบด้วย EDITOR:
- Method:
GET
- URL:
http://localhost:4000/articles
- Headers:
Authorization: Bearer {{accessToken_editor}}
- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อมรายการ articles ทั้งหมด
3. ทดสอบด้วย READER:
- Method:
GET
- URL:
http://localhost:4000/articles
- Headers:
Authorization: Bearer {{accessToken_reader}}
- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อมรายการ articles ทั้งหมด
4. ทดสอบโดยไม่มี Token (ควรล้มเหลว):
- Method:
GET
- URL:
http://localhost:4000/articles
- Headers: ไม่มี Authorization header
- ผลลัพธ์ที่คาดหวัง:
401 Unauthorized
- “Access denied. No token provided.”
ตัวอย่าง Response สำเร็จ:
{
"articles": [
{
"id": 1,
"title": "Test Article from Admin",
"content": "This is article content created by Admin",
"authorId": 1,
"createdAt": "2025-07-27T10:00:00.000Z",
"author": {
"id": 1,
"username": "admin",
"role": "ADMIN"
}
}
],
"total": 1
}
json7.3. แก้ไข Article (ADMIN, EDITOR)#
// PUT /articles/:id - แก้ไข article ที่มีอยู่
// สิทธิ์: ADMIN และ EDITOR สามารถแก้ไข articles ได้
app.put("/articles/:id",
authenticateToken,
authorizeRoles("ADMIN", "EDITOR"),
async (req, res) => {
try {
// ดึง article ID จาก URL parameters
const { id } = req.params;
const { title, content } = req.body;
// เตรียมข้อมูลการอัปเดต (รวมเฉพาะฟิลด์ที่ระบุ)
const updateData = {};
if (title !== undefined) updateData.title = title;
if (content !== undefined) updateData.content = content;
// อัปเดต article ในฐานข้อมูล
const updatedArticle = await prisma.article.update({
where: { id: parseInt(id) },
data: updateData,
include: {
author: {
select: {
id: true,
username: true,
role: true
}
}
}
});
res.json({
message: "Article updated successfully",
article: updatedArticle
});
} catch (err) {
console.error('Update article error:', err);
res.status(500).json({
message: "Failed to update article",
error: err.message
});
}
}
);
javascriptตรรกะการอนุญาต:
- ADMIN: สามารถอัปเดต article ใดก็ได้
- EDITOR: สามารถอัปเดต article ใดก็ได้
- READER: ไม่สามารถอัปเดต articles ได้ (ถูกบล็อกโดย middleware)
คุณสมบัติเพิ่มเติม:
- การอัปเดตบางส่วน: อัปเดตเฉพาะฟิลด์ที่ระบุ
ทดสอบด้วย Postman:
1. ADMIN แก้ไข Article:
- Method:
PUT
- URL:
http://localhost:4000/articles/1
- Headers:
Authorization: Bearer {{accessToken_admin}}
Content-Type: application/json
- Body:
{
"title": "Article edited by Admin",
"content": "Content updated by Admin"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
พร้อมข้อมูล article ที่อัปเดต
2. EDITOR แก้ไข Article:
- Method:
PUT
- URL:
http://localhost:4000/articles/2
- Headers:
Authorization: Bearer {{accessToken_editor}}
Content-Type: application/json
- Body:
{
"title": "Updated article title",
"content": "Updated article content"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
3. READER พยายามแก้ไข (ควรล้มเหลว):
- Method:
PUT
- URL:
http://localhost:4000/articles/1
- Headers:
Authorization: Bearer {{accessToken_reader}}
Content-Type: application/json
- Body:
{
"title": "Reader attempting to edit"
}
json- ผลลัพธ์ที่คาดหวัง:
403 Forbidden
- “Access forbidden. Insufficient permissions”
4. การอัปเดตบางส่วน (เฉพาะ title):
- Method:
PUT
- URL:
http://localhost:4000/articles/1
- Headers:
Authorization: Bearer {{accessToken_admin}}
Content-Type: application/json
- Body:
{
"title": "Only changing the title"
}
json- ผลลัพธ์ที่คาดหวัง:
200 OK
- อัปเดตเฉพาะ title, content คงเดิม
7.4. ลบ Article (เฉพาะ ADMIN)#
// DELETE /articles/:id - ลบ article
// สิทธิ์: เฉพาะ ADMIN เท่านั้นที่สามารถลบ articles ได้
app.delete("/articles/:id",
authenticateToken,
authorizeRoles("ADMIN"),
async (req, res) => {
try {
const { id } = req.params;
await prisma.article.delete({ where: { id: parseInt(id) } });
res.json({ message: "Article deleted successfully" });
} catch (err) {
res.status(500).json({ message: "Failed to delete article" });
}
}
);
javascriptทำไมเฉพาะ ADMIN สำหรับการลบ?
- การป้องกันข้อมูล: ป้องกันการสูญเสียเนื้อหาโดยไม่ตั้งใจ
- การควบคุมการแก้ไข: รักษาการดูแลเนื้อหา
ทดสอบด้วย Postman:
1. ADMIN ลบ Article:
- Method:
DELETE
- URL:
http://localhost:4000/articles/1
- Headers:
Authorization: Bearer {{accessToken_admin}}
- ผลลัพธ์ที่คาดหวัง:
200 OK
2. EDITOR พยายามลบ (ควรล้มเหลว):
- Method:
DELETE
- URL:
http://localhost:4000/articles/2
- Headers:
Authorization: Bearer {{accessToken_editor}}
- ผลลัพธ์ที่คาดหวัง:
403 Forbidden
- “Access forbidden. Insufficient permissions”
3. READER พยายามลบ (ควรล้มเหลว):
- Method:
DELETE
- URL:
http://localhost:4000/articles/1
- Headers:
Authorization: Bearer {{accessToken_reader}}
- ผลลัพธ์ที่คาดหวัง:
403 Forbidden
- “Access forbidden. Insufficient permissions”
8. สรุปและขั้นตอนต่อไป#
บทความที่ครอบคลุมนี้ครอบคลุม:
- ✅ ระบบ JWT Authentication ระบบการยืนยันตัวตนสำหรับ RESTFul API โดยใช้ JWT
- ✅ Role-Based Access Control ที่มีระบบสิทธิ์ 3 ชั้น (ADMIN, EDITOR, READER)
- ✅ การรวม Prisma ORM กับ MySQL สำหรับการดำเนินการฐานข้อมูลที่ปลอดภัยตาม type
- ✅ การจัดการรหัสผ่านที่ปลอดภัย โดยใช้ bcryptjs กับการเข้ารหัสที่เหมาะสม
- ✅ RESTful API ที่สมบูรณ์ พร้อม CRUD operations และสิทธิ์เฉพาะ role
- ✅ Middleware ที่ครอบคลุม สำหรับการยืนยันตัวตนและการอนุญาต
- ✅ กลยุทธ์การทดสอบ พร้อมตัวอย่าง Postman collection
ประโยชน์ของสถาปัตยกรรม#
- ความสามารถในการขยาย: การออกแบบ JWT แบบ stateless รองรับการขยายแนวนอน
- ความปลอดภัย: ความปลอดภัยหลายชั้นพร้อมการจัดการ token ที่เหมาะสม
- ความยืดหยุ่น: ง่ายต่อการขยายด้วย roles และสิทธิ์ใหม่
- ความสามารถในการบำรุงรักษา: การแยกข้อกังวลอย่างชัดเจนด้วย middleware
- ความปลอดภัยตาม Type: Prisma ให้การตรวจสอบ type ในเวลา compile
แหล่งการเรียนรู้#
- JWT.io ↗: Interactive JWT debugger และเอกสาร
- Prisma Docs ↗: เอกสาร ORM ที่ครอบคลุม
- OWASP ↗: แนวทางปฏิบัติที่ดีด้านความปลอดภัยและแนวทาง
- Express.js Security ↗: คำแนะนำด้านความปลอดภัยอย่างเป็นทางการ