สารจากนักเขียน
ทุกคนคิดว่า เมื่อเราต้องการสร้าง Web Application ขึ้นมาเพื่อให้บริการข้อมูลแก่บุคคลทั่วไป อะไรคือสิ่งแรกที่เราควรต้องทำ แน่นอนว่าคำตอบก็คือ ระบบ Authentication นั่นเอง
แล้วระบบ Authentication มันคืออะไรกันล่ะ
ทุกคนคงจะเคย Login เวลาที่ต้องการเข้าใช้งาน Website หรือ Application กันมาบ้างใช่มั้ย ระบบการยืนยันตัวตนที่จะคอยบอก Website หรือ Application เหล่านั้นว่าเราคือใครนั่นแหละคือสิ่งที่เรียกว่า ระบบ Authentication
ซึ่งความยากของการทำระบบ Authentication คือ
การที่ Web Application นั้นมีรูปแบบการสื่อสารที่ไม่รู้จักจดจำอะไรเลย ทำให้ทุก Request ที่ User ส่งไปยังระบบก็จะต้องถูกตรวจสอบสิทธิ์ใหม่ทุกครั้ง อาการแบบนี้ของมันถูกเรียกว่า Stateless กล่าวคือ หลังจากที่ User ได้ Login เข้ามาในระบบเรียบร้อยแล้ว พอ User จะทำกิจกรรมต่อไป ระบบก็จำไม่ได้แล้วว่า User คนนี้เพิ่งจะ Login เข้ามา
ถ้าอย่างนั้น เราจะทำยังไงกันดีล่ะ
เราก็แค่ต้องส่งข้อมูลไปบอกระบบในทุก ๆ การ Request เพื่อให้ระบบไม่ลืมว่า คนที่ส่ง Request นี้มาคือ User คนนี้นะ ที่เพิ่ง Login เข้ามา ซึ่งข้อมูลที่เราจะส่งไปบอกระบบนั้น เราเรียกอีกอย่างนึงว่า Token โดยในบทความนี้เราจะพาทุกคนไปทำความรู้จักกับ Standard Token ตัวนึงที่ชื่อว่า JWT หรือ JSON Web Tokens
ทำไมต้องเป็น JWT (JSON Web Tokens)
JWT ย่อมาจาก JSON Web Tokens เป็น Standard Token ที่สร้างจากข้อมูลของ User แต่ละคน ที่ถูกเก็บอยู่ในรูปแบบ JSON
โดยจากภาพจะเห็นได้ว่าหน้าตาของ JWT คือข้อมูลของ User แต่ละคนที่ถูกเข้ารหัสเป็น Token มีทั้งหมด 3 ส่วนด้วยกัน ซึ่งแต่ละส่วนจะถูกคั่นด้วยสัญลักษณ์เครื่องหมายจุด (.) ประกอบไปด้วย
- Header (สีแดง) เป็นข้อมูล Type ของ Token และข้อมูล Algorithm ที่ใช้ในการเข้ารหัส-ถอดรหัส เก็บอยู่ในรูปแบบ JSON ดังตัวอย่าง
{
"alg": "HS256",
"typ": "JWT"
}
จากในตัวอย่างข้อมูล JSON ของ Header จะเห็นว่า Token นี้เป็นประเภท JWT และใช้ Algorithm HS256 ในการเข้ารหัส-ถอดรหัส
- Payload (สีม่วง) เป็นข้อมูลที่ใช้ในการยืนยันตัวตนของ User และข้อมูลที่เอาไว้กำหนดพฤติกรรมของ Token เก็บอยู่ในรูปแบบ JSON ดังตัวอย่าง
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
จากในตัวอย่างข้อมูล JSON ของ Payload จะเห็นว่า Token นี้เก็บข้อมูล Id และ Name ของ User ไว้ และยังเก็บข้อมูลเวลาสร้าง Token ไว้ด้วย
- Signature (สีฟ้า) เป็นเหมือน Digital Signed ที่มีไว้เช็คความน่าเชื่อถือของ Token โดยเกิดจากการเอา Payload ที่ถูกเข้ารหัสด้วย Algorithm ตามที่ระบุใน Header มารวมเข้ากับ Secret Key ที่เราตั้งขึ้นมา ดังตัวอย่าง
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
จากในตัวอย่างข้อมูลของ Signature จะเห็นว่า Secret Key ที่ User ตั้งขึ้นคือ your-256-bit-secret นั่นเอง
โดยทุกคนสามารถลองเข้าไปเล่น ไปศึกษา และทำความรู้จักกับ JWT (JSON Web Tokens)ให้ดียิ่งขึ้นผ่านเว็ป https://jwt.io/ กันได้ เริ่มจากเราลองนำ Token นี้ไปวางในช่องของ Encoded แล้วมาดูผลลัพธ์ทางด้านขวากัน
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjaGFyYWN0ZXIwMSIsIm5hbWUiOiJMdW5hIEZyZXlhIiwiaWF0IjoxNjk2OTA1MDI1fQ.82YKRXah5sINkAYFEBQB1Py9ttrUB7uC7DtVoXbfkik
จากในตัวอย่าง Token เมื่อใส่ลงไปในช่อง Encoded แล้ว ผลลัพธ์ของข้อมูลที่ถูกถอดรหัสก็จะถูกแสดงอยู่ในฝั่งของ Decoded ทั้ง 3 ส่วน แต่จากรูปคุณจะเห็นแจ้งเตือน Error ที่ขึ้นว่า Invalid Signature แล้วมันเกิดจากอะไรกันล่ะ ถ้าทุกคนลองไปดูที่ช่อง VERIFY SIGNATURE (สีฟ้า) ก็จะเห็นว่าข้อมูล Secret Key นั้นไม่ได้ถูกเปลี่ยนตามข้อมูลอื่น ๆ ไปด้วย ทำให้เกิด Error เนื่องจากข้อมูล Secret Key ในช่อง VERIFY SIGNATURE กับข้อมูล Secret Key ในรหัส ไม่ตรงกันนั่นเอง
ทุกคนคงจะเห็นกันแล้วว่า JWT นี่สามารถเอารหัสมาถอดความได้ง่าย ๆ เลย แล้วแบบนี้เราควรจะใส่ข้อมูลอะไรลงไปใน Payload กันดีล่ะ คำตอบก็คือ เราควรเก็บข้อมูลที่น้อยที่สุดที่สามารถใช้ระบุตัวตนของเราได้ โดยไม่ควรเก็บข้อมูลที่เป็นความลับ หรือ Password โดยตรง
วิธีใช้งาน JWT (JSON Web Tokens) ในการ Authentication
ซึ่งในขั้นตอนต่อไปเราจะมาลองเขียน Code ทำระบบ Authentication ของ ระบบจัดการสินค้า ด้วย Passport.js และ JWT อย่างง่ายกัน
- ก่อนเริ่มให้ทุกคนสร้าง Web Application ระบบจัดการสินค้า ด้วย Node.js และ Express อย่างง่ายขึ้นมากันก่อน ดูรูปแบบคร่าว ๆ ได้จาก Code ในไฟล์ index.js ข้างล่างนี้เลย
const express = require("express");
const app = express();
const port = 5000;
let products = [
{ id: 1, name: "Laptop", category: "Electronics", price: 1000, stock: 5 },
{ id: 2, name: "Phone", category: "Electronics", price: 500, stock: 10 },
];
app.get("/products", (req, res) => {
res.status(200).json(products);
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
ถ้าทุกคนสร้าง Web Application ระบบจัดการสินค้า เสร็จแล้วงั้นเราก็ไปเริ่มทำระบบ Authentication กันได้เลย
- เริ่มจากการติดตั้ง Dependencies ที่จำเป็นต้องใช้ ด้วยคำสั่ง
npm install body-parser jwt-simple passport passport-jwt –save
- ต่อมาทำระบบ Login ผ่าน API ที่จะสร้าง JWT ให้ User ใช้ทำกิจกรรมต่อไปในระบบ โดยเพิ่ม Code นี้ลงไปในไฟล์ index.js
const bodyParser = require("body-parser");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const loginUsename = (req, res, next) => {
if (req.body.username === "admin" && req.body.password === "1234") {
next();
} else {
res.send("Error: username or password is incorrect");
}
};
app.post("/login", loginUsename, (req, res) => {
res.send("User " + req.body.username + " login success");
});
- จากนั้นให้เราลองเรียกใช้ API นี้ผ่าน Postman เพื่อ Test ระบบ Login เมื่อ User กรอก username หรือ password ไม่ถูกต้อง
เมื่อ User กรอก username และ password ถูกต้อง
- ต่อไปเราจะแก้ไข Code ในคำสั่ง app.post(“/login”) เพื่อให้ระบบ Login ของเราส่ง JWT ที่สร้างเสร็จแล้วกลับไปยัง User โดยในส่วนของข้อมูล Payload เราจะใส่เป็น
payload = { sub: ข้อมูล username, iat: ข้อมูลเวลาที่สร้าง Token นี้ }
จะได้เป็น Code ที่ปรับแก้แล้วในไฟล์ index.js ดังนี้
const jwt = require("jwt-simple");
const SECRET = "USER_SECRET_KEY";
app.post("/login", loginUsename, (req, res) => {
const payload = {
sub: req.body.username,
iat: new Date().getTime(),
};
res.send(jwt.encode(payload, SECRET));
});
- ทำให้เมื่อเราเรียกใช้ API นี้ผ่าน Postman อีกครั้ง ระบบก็จะส่ง JWT กลับมาให้เรา
- ซึ่งในการยืนยันตัวตนนั้น เราจะใช้ Passport.js เข้ามาเป็นตัวกลาง โดยเริ่มจากการสร้างกระบวนการยืนยันตัวตนด้วย JWT ผ่าน Code เหล่านี้
const ExtractJwt = require("passport-jwt").ExtractJwt;
const JwtStrategy = require("passport-jwt").Strategy;
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader("authorization"),
secretOrKey: SECRET,
};
const jwtAuth = new JwtStrategy(jwtOptions, (payload, done) => {
if (payload.sub === "admin") done(null, true);
else done(null, false);
});
- จากนั้นนำเอากระบวนการยืนยันตัวตนด้วย JWT ที่สร้างไว้ไปเชื่อมกับ Passport
const passport = require("passport");
passport.use(jwtAuth);
- สุดท้ายเรียกใช้ Passport ในรูปแบบ Middleware ผ่านคำสั่ง app.get(“/products”) ที่จะแสดงสินค้าทั้งหมดที่มีอยู่ในระบบจัดการสินค้า
const requireJWTAuth = passport.authenticate("jwt", { session: false });
app.get("/products", requireJWTAuth, (req, res) => {
res.status(200).json(products);
});
- เพียงแค่นี้ ระบบ Authentication ของ ระบบจัดการสินค้า ก็เป็นอันเสร็จเรียบร้อย โดยทุกคนสามารถลอง Test ได้ ผ่าน Postman ได้เป็นผลลัพธ์ดังนี้
เมื่อ user ส่งคำร้องขอ GET ข้อมูลจากระบบโดยไม่ได้แนบ token ไปด้วย ระบบจึงส่ง Unauthorized กลับมา
เมื่อ user ส่งคำร้องขอ GET ข้อมูลจากระบบโดยแนบ token ไปด้วย ระบบจึงส่ง ข้อมูลสินค้าทั้งหมดในระบบ กลับมา
- ทุกคนสามารถดูสรุป Code ทั้งหมดในการทำระบบ Authentication ของ ระบบจัดการสินค้า อย่างง่าย ได้ที่ข้างล่างนี้เลย
const express = require("express");
const bodyParser = require("body-parser");
const jwt = require("jwt-simple");
const passport = require("passport");
const app = express();
const port = 5000;
const ExtractJwt = require("passport-jwt").ExtractJwt;
const JwtStrategy = require("passport-jwt").Strategy;
const SECRET = "USER_SECRET_KEY";
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader("authorization"),
secretOrKey: SECRET,
};
const jwtAuth = new JwtStrategy(jwtOptions, (payload, done) => {
if (payload.sub === "admin") done(null, true);
else done(null, false);
});
passport.use(jwtAuth);
const requireJWTAuth = passport.authenticate("jwt", { session: false });
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const loginUsename = (req, res, next) => {
if (req.body.username === "admin" && req.body.password === "1234") {
next();
} else {
res.send("Error: username or password is incorrect");
}
};
let products = [
{ id: 1, name: "Laptop", category: "Electronics", price: 1000, stock: 5 },
{ id: 2, name: "Phone", category: "Electronics", price: 500, stock: 10 },
];
app.post("/login", loginUsename, (req, res) => {
const payload = {
sub: req.body.username,
iat: new Date().getTime(),
};
res.send(jwt.encode(payload, SECRET));
});
app.get("/products", requireJWTAuth, (req, res) => {
res.status(200).json(products);
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
สรุป
จากที่ได้กล่าวมาในข้างต้นทุกคนคงจะได้รู้กันแล้วว่า การใช้งาน JWT (JSON Web Tokens) ในการทำระบบ Authentication นั้นไม่ใช่เรื่องยากเลย อีกทั้งยังจะช่วยเพิ่มความปลอดภัยให้กับระบบของเราในการตรวจสอบสิทธิ์การเข้าถึงข้อมูลของ User แต่ละคน ผ่านการเข้ารหัสจากข้อมูลที่ใช้ยืนยันตัวตนในรูปแบบ JSON ที่เชื่อถือได้ และมีความปลอดภัยในระดับนึงอีกด้วย
ข้อมูลอ้างอิง
- Introduction to JSON Web Tokens สืบค้นเมื่อ 9 ตุลาคม 2566 จาก: https://jwt.io/introduction
- JSON Web Token มาตรฐานใหม่ ในการทำ Authentication สืบค้นเมื่อ 9 ตุลาคม 2566 จาก: https://medium.com/rootusercc/json-web-token-มาตรฐานใหม่-ในการทำ-authentication-b0760dd9acd1
- ทำ Stateless Authentication บน Express ด้วย Passport.js + JWT สืบค้นเมื่อ 9 ตุลาคม 2566 จาก: https://medium.com/@kennwuttisasiwat/ทำ-authentication-บน-express-ด้วย-passport-js-jwt-34fb1169a410
- ทำความรู้จักกับ JWT (Json Web Token) สืบค้นเมื่อ 9 ตุลาคม 2566 จาก: https://www.jittagornp.me/blog/what-is-jwt/