Back

RESTful API การยืนยันตัวตน ด้วย JWT และกำหนดสิทธิ์ RBACBlur image

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)ADMINEDITORREADER
สร้าง Article
อ่าน Articles
แก้ไข Article
ลบ Article

2. การตั้งค่าโปรเจค#

2.1. เริ่มต้นโปรเจค#

สร้างโฟลเดอร์โปรเจคใหม่และเริ่มต้น npm:

mkdir jwt-rbac-demo
cd jwt-rbac-demo
npm init -y
bash

2.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/clientPrisma 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 ฐานข้อมูล jwt_rbac_demo สร้างด้วย phpMyAdmin

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);
javascript
node password.js
bash

เปิด Prisma Studio เพื่อเพิ่มผู้ใช้ admin:

npx prisma studio
bash

เพิ่มผู้ใช้ด้วย:

  • username: admin
  • password: <hashed_password_from_above>
  • role: ADMIN

เพื่อเพิ่มผู้ใช้ admin ใน Prisma Studio


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
bash

5. 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 พร้อมข้อมูลผู้ใช้ที่สร้าง

ตัวอย่างการทดสอบ API POST /register (ADMIN)

2. ลงทะเบียนผู้ใช้ EDITOR:

  • Method: POST
  • URL: http://localhost:4000/register
  • Headers:
    • Content-Type: application/json
  • Body:
{
  "username": "editor1",
  "password": "password123",
  "role": "EDITOR"
}
json
  • ผลลัพธ์ที่คาดหวัง: 200 OK

ตัวอย่างการทดสอบ API POST /register (EDITOR)

3. ลงทะเบียนผู้ใช้ READER:

  • Method: POST
  • URL: http://localhost:4000/register
  • Headers:
    • Content-Type: application/json
  • Body:
{
  "username": "reader1",
  "password": "reader123",
  "role": "READER"
}
json
  • ผลลัพธ์ที่คาดหวัง: 200 OK

ตัวอย่างการทดสอบ API POST /register (READER)

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

ตัวอย่างการทดสอบ API POST /login (ADMIN)

2. เข้าสู่ระบบ EDITOR:

  • Method: POST
  • URL: http://localhost:4000/login
  • Headers:
    • Content-Type: application/json
  • Body:
{
  "username": "editor1",
  "password": "password123"
}
json
  • ผลลัพธ์ที่คาดหวัง: 200 OK พร้อม access token

ตัวอย่างการทดสอบ API POST /login (EDITOR)

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:

  1. การดึง Header: มองหา Authorization: Bearer <token>
  2. การตรวจสอบ Token: ตรวจสอบลายเซ็นโดยใช้ secret key
  3. การตรวจสอบการหมดอายุ: ตรวจสอบให้แน่ใจว่า token ยังไม่หมดอายุ
  4. การแนบผู้ใช้: เพิ่มข้อมูลผู้ใช้ที่ถอดรหัสแล้วไปยัง req.user
  5. การควบคุมการไหล: เรียก 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:

  1. Middleware Factory: authorizeRoles() สร้าง middleware ที่กำหนดเอง
  2. การตรวจสอบผู้ใช้: ตรวจสอบให้แน่ใจว่า req.user มีอยู่ (จากการยืนยันตัวตน)
  3. การตรวจสอบ Role: เปรียบเทียบ role ของผู้ใช้กับ allowed roles
  4. การควบคุมการเข้าถึง: อนุญาตหรือปฏิเสธการเข้าถึงตาม 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 มีความสำคัญ:

  1. ใช้ authenticateToken ก่อน authorizeRoles เสมอ
  2. การยืนยันตัวตนตั้งค่า req.user ที่การอนุญาตต้องการ
  3. ทั้งสองต้องมาก่อน 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

การใส่ Token ใน Request

  • ผลลัพธ์ที่คาดหวัง: 201 Created พร้อมข้อมูล article ที่สร้าง

ตัวอย่างการทดสอบ API POST /articles (ADMIN)

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

ตัวอย่างการทดสอบ API POST /articles (EDITOR)

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”

ตัวอย่างการทดสอบ API POST /articles (READER)

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
}
json

7.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: คำแนะนำด้านความปลอดภัยอย่างเป็นทางการ
RESTful API การยืนยันตัวตน ด้วย JWT และกำหนดสิทธิ์ RBAC
ผู้เขียน กานต์ ยงศิริวิทย์ / Karn Yongsiriwit
เผยแพร่เมื่อ July 27, 2025
ลิขสิทธิ์ CC BY-NC-SA 4.0

กำลังโหลดความคิดเห็น...

ความคิดเห็น 0