Contents

วิธีรักษาความปลอดภัย GraphQL API: การใช้การรับรองความถูกต้องผู้ใช้ใน Express.js โดยใช้ JWT

GraphQL เป็นทางเลือกยอดนิยมแทนสถาปัตยกรรม RESTful API แบบดั้งเดิม โดยนำเสนอการสืบค้นข้อมูลและภาษาการจัดการที่ยืดหยุ่นและมีประสิทธิภาพสำหรับ API ด้วยการนำไปใช้ที่เพิ่มมากขึ้น การจัดลำดับความสำคัญด้านความปลอดภัยของ GraphQL API จึงมีความสำคัญมากขึ้น เพื่อปกป้องแอปพลิเคชันจากการเข้าถึงโดยไม่ได้รับอนุญาตและการละเมิดข้อมูลที่อาจเกิดขึ้น

กลยุทธ์หนึ่งที่ใช้ได้จริงในการปกป้อง GraphQL API เกี่ยวข้องกับการใช้ JSON Web Tokens (JWT) ซึ่งเสนอวิธีการที่มีประสิทธิภาพในการให้สิทธิ์การเข้าถึงทรัพยากรที่ถูกจำกัด และดำเนินการดำเนินการที่มีสิทธิพิเศษ ในขณะเดียวกันก็รักษาการสื่อสารที่ปลอดภัยระหว่างไคลเอนต์และ API

การรับรองความถูกต้องและการอนุญาตใน GraphQL API

ตรงกันข้ามกับ RESTful Application Programming Interfaces (API) การใช้งาน GraphQL API มักจะมีจุดสิ้นสุดเดียวที่ช่วยให้ลูกค้าสามารถขอข้อมูลในปริมาณและประเภทที่หลากหลายผ่านการสืบค้น ความสามารถในการปรับตัวนี้แสดงถึงทั้งข้อดีและแหล่งที่มาของความเสี่ยงด้านความปลอดภัย โดยเฉพาะอย่างยิ่งในส่วนที่เกี่ยวข้องกับกลไกการควบคุมการเข้าถึงที่ถูกบุกรุก

/th/images/altumcode-dc6pb2jdaqs-unsplash-1.jpg

เพื่อจัดการภัยคุกคามที่กล่าวมาข้างต้นได้อย่างมีประสิทธิภาพ จำเป็นอย่างยิ่งที่จะต้องสร้างโปรโตคอลการตรวจสอบสิทธิ์และการอนุญาตที่ครอบคลุม ซึ่งรวมถึงการกำหนดสิทธิพิเศษในการเข้าถึงอย่างพิถีพิถัน ด้วยมาตรการดังกล่าว เรารับรองว่าเฉพาะบุคคลที่ได้รับอนุญาตเท่านั้นจึงจะสามารถเข้าถึงทรัพย์สินที่มีความละเอียดอ่อนได้ ซึ่งจะช่วยลดความเสี่ยงของช่องโหว่ด้านความปลอดภัยและเหตุการณ์การรั่วไหลของข้อมูล

[แทรกลิงก์ที่เก็บ GitHub ที่นี่]

ตั้งค่าเซิร์ฟเวอร์ Express.js Apollo

Apollo Server คือการใช้งานเซิร์ฟเวอร์ GraphQL ที่ใช้กันอย่างแพร่หลายสำหรับ GraphQL API คุณสามารถใช้เพื่อสร้างสคีมา GraphQL กำหนดรีโซลเวอร์ และจัดการแหล่งข้อมูลต่างๆ สำหรับ API ของคุณได้อย่างง่ายดาย

ในการสร้าง Express.js Apollo Server จำเป็นต้องสร้างและเปิดไดเร็กทอรีโปรเจ็กต์

 mkdir graphql-API-jwt
cd graphql-API-jwt 

หากต้องการเริ่มต้นโปรเจ็กต์ Node.js ใหม่โดยใช้ Node Package Manager (npm) ให้ดำเนินการคำสั่งต่อไปนี้:

 npm init --yes 

ตอนนี้ให้ติดตั้งแพ็คเกจเหล่านี้

 npm install apollo-server graphql mongoose jsonwebtokens dotenv 

อันที่จริง จำเป็นอย่างยิ่งที่จะต้องสร้างไฟล์ server.js ในโฟลเดอร์หลักของโปรเจ็กต์ และกำหนดค่าเซิร์ฟเวอร์โดยใช้สคริปต์ที่ให้มาดังนี้:

 const { ApolloServer } = require('apollo-server');
const mongoose = require('mongoose');
require('dotenv').config();

const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");

const server = new ApolloServer({
     typeDefs,
     resolvers,
    context: ({ req }) => ({ req }),
 });

const MONGO_URI = process.env.MONGO_URI;

mongoose
  .connect(MONGO_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => {
    console.log("Connected to DB");
    return server.listen({ port: 5000 });
  })
  .then((res) => {
    console.log(`Server running at ${res.url}`);
  })
  .catch(err => {
    console.log(err.message);
  }); 

เซิร์ฟเวอร์ GraphQL ได้รับการกำหนดค่าโดยใช้การรวมกันของ typeDefs และ solvers ซึ่งกำหนดสคีมาและการดำเนินการที่ API สามารถจัดการได้ นอกจากนี้ การใช้ตัวเลือก บริบท ช่วยให้สามารถกำหนดค่าออบเจ็กต์ req เพื่อรวมบริบทเฉพาะที่เกี่ยวข้องกับรีโซลเวอร์แต่ละตัวได้ ซึ่งช่วยให้เซิร์ฟเวอร์สามารถเข้าถึงข้อมูลเฉพาะคำขอ รวมถึงค่าส่วนหัว เพื่ออำนวยความสะดวกในการเรียกข้อมูลที่มีประสิทธิภาพมากขึ้น

สร้างฐานข้อมูล MongoDB

สร้างฐานข้อมูลใหม่ภายในการติดตั้ง MongoDB ในเครื่องของคุณ หรือใช้โซลูชันบนคลาวด์ที่นำเสนอโดย MongoDB Atlas โดยการตั้งค่าคลัสเตอร์และรับข้อมูลการเชื่อมต่อที่จำเป็น เมื่อคุณได้รับสตริง URI การเชื่อมต่อที่เหมาะสมแล้ว คุณสามารถดำเนินการสร้างไฟล์ .env ใหม่ และป้อนรายละเอียดการเชื่อมต่อในรูปแบบที่ระบุได้

 MONGO_URI="<mongo_connection_uri>"

กำหนดโมเดลข้อมูล

แน่นอนว่า นี่คือตัวอย่างวิธีกำหนดโมเดลข้อมูลสำหรับผู้ใช้ในไฟล์ Mongoose schema ชื่อ models/user.js:javascriptconst mongoose=need(‘mongoose’);//Define the user schemaconst userSchema=new mongoose.Schema ({ชื่อ: { ประเภท: สตริง, จำเป็น: จริง }, อีเมล: { ประเภท: สตริง, ไม่ซ้ำกัน: จริง, จำเป็น: จริง }, รหัสผ่าน: { ประเภท: สตริง, จำเป็น: จริง },});//รวบรวมสคีมา ลงใน modelconst User=mongoose.model(‘User’, userSchema);module.exports=User;ในตัวอย่างนี้ ก่อนอื่นเราจะนำเข้าไลบรารี Mongoose ที่ด้านบนของไฟล์ จากนั้นเราสร้างออบเจ็กต์ Schema ใหม่

 const {model, Schema} = require('mongoose');

const userSchema = new Schema({
    name: String,
    password: String,
    role: String
});

module.exports = model('user', userSchema); 

กำหนดสคีมา GraphQL

สคีมาของ GraphQL API กำหนดการจัดเรียงข้อมูลที่สามารถเรียกคืนได้ นอกเหนือจากการระบุการดำเนินการที่เป็นไปได้ (การสอบถามและการเปลี่ยนแปลง) ซึ่งสามารถดำเนินการเพื่อสื่อสารกับข้อมูลผ่านอินเทอร์เฟซดังกล่าว

ในการสร้างสคีมาสำหรับโปรเจ็กต์ของคุณ คุณควรสร้างโฟลเดอร์ใหม่ภายในไดเร็กทอรีหลักของการดำเนินการของคุณก่อน ซึ่งจะเรียกว่า “graphql” โฟลเดอร์เฉพาะนี้จะทำหน้าที่เป็นวงล้อมซึ่งมีการจัดระเบียบส่วนประกอบทั้งหมดที่เกี่ยวข้องกับการดำเนินการ GraphQL ภายในขอบเขตของโฟลเดอร์ “graphql” จะต้องมีเอกสารสองฉบับที่แตกต่างกัน เอกสารหนึ่งชื่อ “typeDefs.js” และอีกเอกสารหนึ่งชื่อ “resolvers.js”

ในไฟล์ typeDefs.js ให้รวมโค้ดต่อไปนี้:

 const { gql } = require("apollo-server");

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    password: String!
    role: String!
  }
  input UserInput {
    name: String!
    password: String!
    role: String!
  }
  type TokenResult {
    message: String
    token: String
  }
  type Query {
     users: [User]
   }
  type Mutation {
    register(userInput: UserInput): User
    login(name: String!, password: String!, role: String!): TokenResult
  }
`;

module.exports = typeDefs; 

สร้างตัวแก้ไขสำหรับ GraphQL API

ฟังก์ชันตัวแก้ไขมีบทบาทสำคัญในการกำหนดวิธีการรับข้อมูลเพื่อตอบสนองคำขอข้อมูลและการแก้ไขของลูกค้า รวมถึงรายละเอียดเพิ่มเติมใดๆ ที่ระบุภายในสคีมา เมื่อได้รับคำขอจากไคลเอนต์ เซิร์ฟเวอร์ GraphQL จะเปิดใช้งานรีโซลเวอร์ที่เกี่ยวข้องเพื่อรับข้อมูลจากหลายแหล่ง รวมถึงฐานข้อมูลและบริการภายนอก และส่งคืนตามนั้น

หากต้องการเปิดใช้งานการตรวจสอบสิทธิ์และการควบคุมการเข้าถึงโดยใช้ JSON Web Token (JWT) ในเซิร์ฟเวอร์ GraphQL ของเรา อันดับแรกเราต้องสร้างตัวแก้ไขสำหรับทั้งการเปลี่ยนแปลง"การลงทะเบียน"และ"เข้าสู่ระบบ"ตัวแก้ไขเหล่านี้มีหน้าที่จัดการขั้นตอนการสร้างบัญชีและการตรวจสอบ ต่อจากนั้น เราควรพัฒนาตัวแก้ไขการสืบค้นการดึงข้อมูลที่สามารถเข้าถึงได้โดยผู้ใช้ที่ลงทะเบียนและตรวจสอบแล้วเท่านั้นที่ได้รับอนุญาตให้ดำเนินการดังกล่าว

ตัวเข้ารหัสและตัวถอดรหัส RSA JWE จากแพ็คเกจ’node-jose’รวมถึงโมดูล’crypto’ที่จัดทำโดย Node.js สิ่งนี้จะช่วยให้เราสามารถดำเนินการเข้ารหัสที่เกี่ยวข้องกับการสร้างและการตรวจสอบ JWT ได้อย่างราบรื่น การทำเช่นนี้ทำให้เรามั่นใจได้ถึงการสื่อสารที่ปลอดภัยระหว่างแอปพลิเคชันของเราและผู้ใช้ ในขณะเดียวกันก็รักษาความเรียบง่ายและความสะดวกในการใช้งานสำหรับทั้งนักพัฒนาและผู้ใช้ปลายทาง

 const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY; 

โปรดตรวจสอบให้แน่ใจว่าคุณได้รวมรหัสลับที่จำเป็นสำหรับการลงนาม JSON Web Tokens ไว้ในไฟล์.env ของคุณเพื่อใช้งานอย่างเหมาะสม

 SECRET_KEY = '<my_Secret_Key>'; 

เพื่อสร้างโทเค็นการตรวจสอบความถูกต้องที่ถูกต้อง จำเป็นต้องใช้ฟังก์ชันที่ให้มาและระบุคุณสมบัติที่กำหนดเอง รวมถึงเวลาหมดอายุ ภายในโทเค็น JWT นอกจากนี้ อาจรวมคุณลักษณะเพิ่มเติม เช่น เวลาที่ออกให้ไว้ตามความต้องการเฉพาะของใบสมัคร

 function generateToken(user) {
  const token = jwt.sign(
   { id: user.id, role: user.role },
   secretKey,
   { expiresIn: '1h', algorithm: 'HS256' }
 );

  return token;
} 

เพื่อให้มั่นใจถึงความถูกต้องของ JSON Web Tokens (JWT) ที่ใช้ในการแลกเปลี่ยน HTTP ในอนาคต จำเป็นต้องรวมกระบวนการตรวจสอบความถูกต้องสำหรับโทเค็นเหล่านี้ สิ่งนี้จะเกิดขึ้นได้โดยใช้กลไกการรับรองความถูกต้องที่จะตรวจสอบและยืนยันความถูกต้องตามกฎหมายของ JWT แต่ละรายการที่นำเสนอในระหว่างการโต้ตอบดังกล่าว

 function verifyToken(token) {
  if (!token) {
    throw new Error('Token not provided');
  }

  try {
    const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
    return decoded;
  } catch (err) {
    throw new Error('Invalid token');
  }
} 

ฟังก์ชันที่กำหนดยอมรับโทเค็นเป็นอินพุต โดยใช้คีย์ลับที่กำหนดเพื่อตรวจสอบความถูกต้อง หากโทเค็นพิสูจน์ได้ว่าถูกต้องตามกฎหมาย ฟังก์ชันจะส่งคืนโทเค็นที่ถอดรหัสแล้ว อย่างไรก็ตาม ในกรณีที่มีความคลาดเคลื่อน จะทำให้เกิดข้อยกเว้นซึ่งบ่งชี้ว่ามีโทเค็นที่ไม่ถูกต้อง

กำหนดตัวแก้ไข API

เพื่ออธิบายกระบวนการแก้ไขปัญหาสำหรับ GraphQL API ของเรา เราต้องระบุงานเฉพาะหรือการดำเนินการที่จะดำเนินการ เช่น การจัดการการลงทะเบียนผู้ใช้และกระบวนการเข้าสู่ระบบ ขั้นแรก ให้สร้างอ็อบเจ็กต์ที่มีฟังก์ชันตัวแก้ไขซึ่งทำหน้าที่ในการดำเนินงานที่กำหนดเหล่านี้ จากนั้น ให้ดำเนินการเปลี่ยนแปลงต่อไปนี้เพื่ออำนวยความสะดวกในการดำเนินการเหล่านี้:

 const resolvers = {
  Mutation: {
    register: async (_, { userInput: { name, password, role } }) => {
      if (!name || !password || !role) {
        throw new Error('Name password, and role required');
     }

      const newUser = new User({
        name: name,
        password: password,
        role: role,
      });

      try {
        const response = await newUser.save();

        return {
          id: response._id,
          ...response._doc,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Failed to create user');
      }
    },
    login: async (_, { name, password }) => {
      try {
         const user = await User.findOne({ name: name });

        if (!user) {
          throw new Error('User not found');
       }

        if (password !== user.password) {
          throw new Error('Incorrect password');
        }

         const token = generateToken(user);

         if (!token) {
          throw new Error('Failed to generate token');
        }

        return {
          message: 'Login successful',
          token: token,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Login failed');
      }
    }
  }, 

การเปลี่ยนแปลงรีจิสเตอร์มีหน้าที่รับผิดชอบในการจัดการกระบวนการลงทะเบียนผ่านการเพิ่มข้อมูลผู้ใช้ใหม่ลงในฐานข้อมูล ในทางกลับกัน การเปลี่ยนแปลงการเข้าสู่ระบบเกี่ยวข้องกับการเข้าสู่ระบบของผู้ใช้ และเมื่อการตรวจสอบความถูกต้องสำเร็จ จะสร้าง JSON Web Token (JWT) ในขณะเดียวกันก็ส่งคืนข้อความแสดงความสำเร็จในการตอบกลับด้วย

เพื่อจำกัดการเข้าถึงแบบสอบถามที่ดึงข้อมูลผู้ใช้เฉพาะสำหรับผู้ใช้ที่ได้รับการตรวจสอบสิทธิ์และมีสิทธิ์ซึ่งมีบทบาท"ผู้ดูแลระบบ"จำเป็นอย่างยิ่งที่จะต้องรวมตรรกะการอนุญาตภายในระบบ สิ่งนี้ทำให้มั่นใจได้ว่าบุคคลที่ไม่ได้รับอนุญาตจะไม่สามารถเข้าถึงข้อมูลที่ละเอียดอ่อนได้

โดยพื้นฐานแล้ว การสอบสวนจะเริ่มต้นตรวจสอบความถูกต้องของโทเค็น ตามด้วยการประเมินบทบาทของผู้ใช้ ในกรณีที่การตรวจสอบการอนุญาตพิสูจน์ได้ว่าเป็นผลดี คำร้องขอการแก้ปัญหาจะต้องดึงข้อมูลและให้ข้อมูลของผู้ใช้ปลายทางจากพื้นที่เก็บข้อมูล

   Query: {
    users: async (parent, args, context) => {
      try {
        const token = context.req.headers.authorization || '';
        const decodedToken = verifyToken(token);

        if (decodedToken.role !== 'Admin') {
          throw new ('Unauthorized. Only Admins can access this data.');
        }

        const users = await User.find({}, { name: 1, _id: 1, role:1 });
         return users;
      } catch (error) {
        console.error(error);
        throw new Error('Failed to fetch users');
      }
    },
  },
}; 

สุดท้ายให้เริ่มเซิร์ฟเวอร์การพัฒนา:

 node server.js 

ขอแนะนำอย่างยิ่งให้คุณตรวจสอบความสามารถในการทำงานของ GraphQL API ของเราโดยใช้แซนด์บ็อกซ์ Apollo Server API ภายในเว็บเบราว์เซอร์ของคุณ เพื่ออธิบายกระบวนการนี้ ให้พิจารณาใช้การเปลี่ยนแปลง"การลงทะเบียน"เพื่อป้อนข้อมูลผู้ใช้ใหม่ลงในฐานข้อมูล ตามด้วยการเปลี่ยนแปลง"การเข้าสู่ระบบ"เพื่อวัตถุประสงค์ในการตรวจสอบสิทธิ์

/th/images/login-mutation.jpg

สุดท้าย รวมโทเค็น JWT ไว้ในพารามิเตอร์ส่วนหัวการอนุญาต และดึงข้อมูลผู้ใช้จากฐานข้อมูลในภายหลังโดยดำเนินการค้นหา

/th/images/graphql-api-query.jpg

การรักษาความปลอดภัย GraphQL API

การรับรองความถูกต้องและการอนุญาตมีบทบาทสำคัญในการรับรองความปลอดภัยของ GraphQL API อย่างไรก็ตาม การใช้งานเพียงอย่างเดียวไม่ได้รับประกันการป้องกันที่สมบูรณ์ จำเป็นอย่างยิ่งที่จะต้องรวมมาตรการความปลอดภัยเพิ่มเติม เช่น การตรวจสอบอินพุตที่แข็งแกร่งและการเข้ารหัสข้อมูลที่ละเอียดอ่อน เพื่อให้เกิดความปลอดภัยอย่างละเอียด

การใช้กลยุทธ์ความปลอดภัยที่ครอบคลุมทั้งหมดถือเป็นสิ่งสำคัญในการปกป้อง Application Programming Interfaces (API) ของคุณจากภัยคุกคามประเภทต่างๆ ที่อาจเกิดขึ้น