دریابید که چگونه JWT ها راه حل ساده ای را برای مسئله دشوار احراز هویت ارائه می دهند.
GraphQL یک جایگزین محبوب برای معماری سنتی RESTful API است که یک زبان جستجو و دستکاری داده انعطاف پذیر و کارآمد برای API ها ارائه می دهد. با پذیرش رو به رشد آن، اولویت بندی امنیت API های GraphQL برای محافظت از برنامه ها در برابر دسترسی غیرمجاز و نقض احتمالی داده ها اهمیت فزاینده ای پیدا می کند.
یکی از روش های موثر برای ایمن سازی API های GraphQL، پیاده سازی JSON Web Tokens (JWTs) است. JWT ها روشی ایمن و کارآمد برای اعطای دسترسی به منابع محافظت شده و انجام اقدامات مجاز ارائه می دهند و از برقراری ارتباط امن بین کلاینت ها و API ها اطمینان حاصل می کنند.
احراز هویت و مجوز در API های GraphQL
برخلاف REST APIها، APIهای GraphQL معمولاً دارای یک نقطه پایانی واحد هستند که به مشتریان اجازه میدهد به صورت پویا مقادیر متفاوتی از دادهها را در پرس و جوهای خود درخواست کنند. در حالی که این انعطافپذیری نقطه قوت آن است، خطر حملات امنیتی بالقوه مانند آسیبپذیریهای کنترل دسترسی شکسته را نیز افزایش میدهد.
برای کاهش این خطر، اجرای فرآیندهای احراز هویت و مجوز قوی، از جمله تعریف صحیح مجوزهای دسترسی، مهم است. با انجام این کار، تضمین می کنید که فقط کاربران مجاز می توانند به منابع محافظت شده دسترسی داشته باشند و در نهایت، خطر نقض احتمالی امنیت و از دست دادن داده ها را کاهش دهید.
می توانید کد این پروژه را در مخزن GitHub آن پیدا کنید.
سرور Express.js Apollo را راه اندازی کنید
Apollo Server یک پیاده سازی سرور GraphQL برای API های GraphQL است. میتوانید از آن برای ایجاد آسان طرحوارههای GraphQL، تعریف حلکنندهها و مدیریت منابع داده مختلف برای APIهای خود استفاده کنید.
برای راه اندازی یک سرور Express.js Apollo، یک پوشه پروژه ایجاد و باز کنید:
mkdir graphql-API-jwt
cd graphql-API-jwt
سپس، این دستور را برای مقداردهی اولیه یک پروژه Node.js با استفاده از npm، مدیر بسته Node اجرا کنید:
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 میتواند انجام دهد را مشخص میکند. گزینه context شی req را در زمینه هر حل کننده پیکربندی می کند، که به سرور اجازه می دهد به جزئیات درخواستی خاص مانند مقادیر سرصفحه دسترسی پیدا کند.
یک پایگاه داده MongoDB ایجاد کنید
برای برقراری ارتباط پایگاه داده، ابتدا یک پایگاه داده MongoDB ایجاد کنید یا یک کلاستر در MongoDB Atlas راه اندازی کنید. سپس رشته URI اتصال پایگاه داده ارائه شده را کپی کنید، یک فایل .env ایجاد کنید و رشته اتصال را به صورت زیر وارد کنید:
MONGO_URI="<mongo_connection_uri>"
مدل داده را تعریف کنید
یک مدل داده را با استفاده از Mongoose تعریف کنید. یک فایل models/user.js جدید ایجاد کنید و کد زیر را وارد کنید:
const {model, Schema} = require('mongoose');
const userSchema = new Schema({
name: String,
password: String,
role: String
});
module.exports = model('user', userSchema);
طرح واره GraphQL را تعریف کنید
در یک GraphQL API، طرح ساختار دادههایی را که میتوان پرسوجو کرد و همچنین عملیاتهای موجود (پرسوجوها و جهشها) را که میتوانید برای تعامل با دادهها از طریق API انجام دهید، مشخص میکند.
برای تعریف طرحواره، یک پوشه جدید در پوشه اصلی پروژه خود ایجاد کنید و نام آن را graphql بگذارید. داخل این پوشه دو فایل typeDefs.js و solvers.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;
Resolvers برای GraphQL API ایجاد کنید
توابع Resolver نحوه بازیابی داده ها در پاسخ به پرس و جوها و جهش های مشتری و همچنین سایر فیلدهای تعریف شده در طرح را تعیین می کند. هنگامی که یک کلاینت یک پرس و جو یا جهش ارسال می کند، سرور GraphQL حل کننده های مربوطه را فعال می کند تا داده های مورد نیاز را از منابع مختلف، مانند پایگاه های داده یا API ها پردازش و برگردانند.
برای اجرای احراز هویت و مجوز با استفاده از JSON Web Tokens (JWTs)، جهشهای ثبت و ورود را حلکننده تعریف کنید. اینها فرآیندهای ثبت نام و احراز هویت کاربر را اداره می کنند. سپس، یک حلکننده پرس و جو واکشی داده ایجاد کنید که فقط برای کاربران تأیید شده و مجاز قابل دسترسی باشد.
اما ابتدا، توابع تولید و تأیید JWT ها را تعریف کنید. در فایل solvers.js، با افزودن واردات زیر شروع کنید.
const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;
مطمئن شوید که کلید مخفی را که برای امضای توکن های وب JSON استفاده می کنید به فایل .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;
}
اکنون، منطق تأیید توکن را برای تأیید اعتبار توکنهای JWT موجود در درخواستهای HTTP بعدی پیادهسازی کنید.
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 Resolvers را تعریف کنید
برای تعریف حلکنندهها برای GraphQL API، باید عملیات خاصی را که مدیریت میکند، در این مورد، عملیات ثبت نام و ورود کاربر مشخص کنید. ابتدا یک شی Resolers ایجاد کنید که توابع Resolver را نگه دارد، سپس عملیات جهش زیر را تعریف کنید:
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');
}
}
},
جهش ثبت، فرآیند ثبت نام را با افزودن داده های کاربر جدید به پایگاه داده انجام می دهد. در حالی که جهش ورود، ورود کاربران را مدیریت میکند – در صورت احراز هویت موفقیتآمیز، یک توکن 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
عالی! اکنون، پیش بروید و عملکرد API را با استفاده از سندباکس Apollo Server API در مرورگر خود آزمایش کنید. به عنوان مثال، میتوانید از جهش ثبت برای اضافه کردن دادههای کاربر جدید در پایگاه داده استفاده کنید و سپس از جهش ورود برای احراز هویت کاربر استفاده کنید.
در نهایت، رمز JWT را به بخش هدر مجوز اضافه کنید و به جستجو در پایگاه داده برای اطلاعات کاربر ادامه دهید.
ایمن سازی API های GraphQL
احراز هویت و مجوز اجزای حیاتی برای ایمن سازی API های GraphQL هستند. با این وجود، مهم است که بدانیم آنها به تنهایی ممکن است برای تضمین امنیت جامع کافی نباشند. شما باید اقدامات امنیتی اضافی مانند اعتبار سنجی ورودی و رمزگذاری داده های حساس را اجرا کنید.
با اتخاذ یک رویکرد امنیتی جامع، می توانید از API های خود در برابر حملات احتمالی مختلف محافظت کنید.