Реалізація автентифікації користувача в Express.js за допомогою JWT

GraphQL є популярною альтернативою традиційній архітектурі RESTful API, що пропонує гнучку та ефективну мову запитів і обробки даних для API. У зв’язку з його широким впровадженням стає все більш важливим надавати пріоритет безпеці GraphQL API, щоб захистити програми від несанкціонованого доступу та потенційних порушень даних.

Одним з ефективних підходів для захисту API GraphQL є впровадження веб-токенів JSON (JWT). JWT надають безпечний і ефективний метод для надання доступу до захищених ресурсів і виконання авторизованих дій, забезпечуючи безпечне спілкування між клієнтами та API.

Автентифікація та авторизація в GraphQL API

На відміну від REST API, GraphQL API зазвичай мають одну кінцеву точку, яка дозволяє клієнтам динамічно запитувати різні обсяги даних у своїх запитах. Незважаючи на те, що ця гнучкість є його сильною стороною, вона також підвищує ризик потенційних атак на безпеку, таких як уразливості несправного контролю доступу.

Щоб зменшити цей ризик, важливо запровадити надійні процеси автентифікації та авторизації, зокрема правильно визначити дозволи доступу. Таким чином ви гарантуєте, що лише авторизовані користувачі зможуть отримати доступ до захищених ресурсів, і, зрештою, зменшите ризик потенційних порушень безпеки та втрати даних.

Ви можете знайти код цього проекту в його GitHub сховище.

Налаштуйте сервер Express.js Apollo

Сервер Apollo це широко використовувана реалізація сервера GraphQL для GraphQL API. Ви можете використовувати його для легкого створення схем GraphQL, визначення резолверів і керування різними джерелами даних для ваших API.

Щоб налаштувати Express.js Apollo Server, створіть і відкрийте папку проекту:

 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 і resolvers, які вказують схему та операції, які може обробляти 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

В API GraphQL схема визначає структуру даних, які можна запитувати, а також описує доступні операції (запити та мутації), які можна виконувати для взаємодії з даними через API.

Щоб визначити схему, створіть нову папку в кореневому каталозі вашого проекту та назвіть її 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 запускає відповідні резолвери для обробки та повернення необхідних даних із різних джерел, таких як бази даних або API.

  Як вибрати правильний варіант

Щоб реалізувати автентифікацію та авторизацію за допомогою веб-токенів JSON (JWT), визначте резолвери для мутацій реєстрації та входу. Вони оброблятимуть процеси реєстрації та автентифікації користувачів. Потім створіть розв’язувач запитів на вибірку даних, який буде доступний лише автентифікованим і авторизованим користувачам.

Але спочатку визначте функції для створення та перевірки JWT. У файл resolvers.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, вам потрібно окреслити конкретні операції, якими він керуватиме, у цьому випадку це операції реєстрації та входу користувача. Спершу створіть об’єкт резольверів, який буде містити функції резолвера, а потім визначте такі операції зміни:

 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, а також поверне повідомлення про успішне виконання у відповідь.

  Як побачити, які пристрої використовують ваш обліковий запис Amazon Video

Тепер додайте розв’язувач запитів для отримання даних користувача. Щоб переконатися, що цей запит буде доступний лише для автентифікованих і авторизованих користувачів, додайте логіку авторизації, щоб обмежити доступ лише для користувачів із роллю адміністратора.

По суті, запит спочатку перевірить дійсність маркера, а потім роль користувача. Якщо перевірка авторизації пройшла успішно, запит розпізнавача продовжить отримання та повернення даних користувачів із бази даних.

   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 за допомогою пісочниці API Apollo Server у вашому браузері. Наприклад, ви можете використовувати мутацію реєстру, щоб додати нові дані користувача в базу даних, а потім мутацію входу для автентифікації користувача.

Нарешті, додайте маркер JWT до розділу заголовка авторизації та перейдіть до запиту даних користувача до бази даних.

Захист GraphQL API

Автентифікація та авторизація є ключовими компонентами для захисту GraphQL API. Тим не менш, важливо визнати, що їх самих по собі може бути недостатньо для забезпечення повної безпеки. Ви повинні застосувати додаткові заходи безпеки, такі як перевірка введених даних і шифрування конфіденційних даних.

Застосовуючи комплексний підхід до безпеки, ви можете захистити свої API від різних потенційних атак.