Implementace ověření uživatele v Express.js pomocí JWT

GraphQL je oblíbená alternativa k tradiční architektuře RESTful API, která nabízí flexibilní a efektivní jazyk pro dotazy na data a manipulaci s API. S jeho rostoucím přijetím je stále důležitější upřednostňovat zabezpečení GraphQL API, aby byly aplikace chráněny před neoprávněným přístupem a potenciálním únikem dat.

Jedním z účinných přístupů k zabezpečení rozhraní GraphQL API je implementace webových tokenů JSON (JWT). JWT poskytují bezpečnou a efektivní metodu pro udělování přístupu k chráněným zdrojům a provádění autorizovaných akcí, zajišťující bezpečnou komunikaci mezi klienty a API.

Autentizace a autorizace v GraphQL API

Na rozdíl od REST API mají GraphQL API obvykle jeden koncový bod, který klientům umožňuje dynamicky vyžadovat různá množství dat ve svých dotazech. I když je tato flexibilita její silnou stránkou, zvyšuje také riziko potenciálních bezpečnostních útoků, jako jsou zranitelnosti prolomené kontroly přístupu.

Ke zmírnění tohoto rizika je důležité implementovat robustní procesy ověřování a autorizace, včetně správného definování přístupových oprávnění. Tím zaručíte, že pouze oprávnění uživatelé budou mít přístup k chráněným zdrojům, a v konečném důsledku snížíte riziko potenciálního narušení bezpečnosti a ztráty dat.

Kód tohoto projektu najdete v něm GitHub úložiště.

Nastavte Express.js Apollo Server

Server Apollo je široce používaná implementace serveru GraphQL pro rozhraní GraphQL API. Můžete jej použít ke snadnému vytváření schémat GraphQL, definování resolverů a správě různých zdrojů dat pro vaše API.

Chcete-li nastavit Express.js Apollo Server, vytvořte a otevřete složku projektu:

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

Dále spusťte tento příkaz pro inicializaci nového projektu Node.js pomocí npm, správce balíčků Node:

 npm init --yes 

Nyní nainstalujte tyto balíčky.

 npm install apollo-server graphql mongoose jsonwebtokens dotenv 

Nakonec vytvořte soubor server.js v kořenovém adresáři a nastavte server pomocí tohoto kódu:

 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);
  });

Server GraphQL je nastaven s parametry typeDefs a resolvers, které určují schéma a operace, které rozhraní API dokáže zpracovat. Volba kontextu konfiguruje objekt req do kontextu každého resolveru, což serveru umožní přístup k podrobnostem specifickým pro požadavek, jako jsou hodnoty záhlaví.

  Jak nastavit Quassel Core na serveru Ubuntu

Vytvořte databázi MongoDB

Chcete-li navázat připojení k databázi, nejprve vytvořte databázi MongoDB nebo nastavte cluster v Atlasu MongoDB. Poté zkopírujte poskytnutý řetězec URI připojení k databázi, vytvořte soubor .env a zadejte připojovací řetězec takto:

 MONGO_URI="<mongo_connection_uri>"

Definujte datový model

Definujte datový model pomocí Mongoose. Vytvořte nový soubor models/user.js a vložte následující kód:

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

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

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

Definujte schéma GraphQL

V GraphQL API schéma definuje strukturu dat, na která lze dotazovat, a také nastiňuje dostupné operace (dotazy a mutace), které můžete provádět pro interakci s daty prostřednictvím rozhraní API.

Chcete-li definovat schéma, vytvořte novou složku v kořenovém adresáři vašeho projektu a pojmenujte ji graphql. Do této složky přidejte dva soubory: typeDefs.js a resolvers.js.

Do souboru typeDefs.js zahrňte následující kód:

 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;

Vytvořte překladače pro GraphQL API

Funkce překladače určují, jak jsou data načítána v reakci na klientské dotazy a mutace, stejně jako další pole definovaná ve schématu. Když klient odešle dotaz nebo mutaci, server GraphQL spustí odpovídající překladače, aby zpracovaly a vrátily požadovaná data z různých zdrojů, jako jsou databáze nebo rozhraní API.

  Jak se odhlásit z automatických textových zpráv

Chcete-li implementovat ověřování a autorizaci pomocí webových tokenů JSON (JWT), definujte resolvery pro mutace registru a přihlášení. Ty se budou starat o procesy registrace a ověřování uživatelů. Poté vytvořte překladač dotazů načítání dat, který bude přístupný pouze ověřeným a autorizovaným uživatelům.

Nejprve však definujte funkce pro generování a ověřování JWT. V souboru resolvers.js začněte přidáním následujících importů.

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

Ujistěte se, že jste do souboru .env přidali tajný klíč, který budete používat k podepisování webových tokenů JSON.

 SECRET_KEY = '<my_Secret_Key>'; 

Chcete-li vygenerovat ověřovací token, zahrňte následující funkci, která také určuje jedinečné atributy pro token JWT, např. dobu platnosti. Kromě toho můžete začlenit další atributy, například vydané v čase, na základě vašich specifických požadavků aplikace.

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

  return token;
}

Nyní implementujte logiku ověření tokenu k ověření tokenů JWT zahrnutých v následných požadavcích 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');
  }
}

Tato funkce vezme token jako vstup, ověří jeho platnost pomocí zadaného tajného klíče a vrátí dekódovaný token, pokud je platný, jinak vyvolá chybu označující neplatný token.

Definujte rozhraní API

Chcete-li definovat resolvery pro GraphQL API, musíte nastínit konkrétní operace, které bude spravovat, v tomto případě operace registrace uživatele a přihlášení. Nejprve vytvořte objekt resolveru, který bude obsahovat funkce resolveru, a poté definujte následující operace mutace:

 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');
      }
    }
  },

Registrační mutace zpracovává registrační proces přidáním nových uživatelských dat do databáze. Zatímco přihlašovací mutace spravuje přihlášení uživatelů – při úspěšné autentizaci vygeneruje token JWT a v odpovědi vrátí zprávu o úspěchu.

  Jak naplánovat odeslání e-mailu v Gmailu

Nyní zahrňte překladač dotazů pro načítání uživatelských dat. Chcete-li zajistit, že tento dotaz bude přístupný pouze ověřeným a autorizovaným uživatelům, zahrňte autorizační logiku k omezení přístupu pouze na uživatele s rolí správce.

Dotaz v podstatě nejprve zkontroluje platnost tokenu a poté uživatelskou roli. Pokud je kontrola autorizace úspěšná, dotaz překladače bude pokračovat v načítání a vracení dat uživatelů z databáze.

   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');
      }
    },
  },
};

Nakonec spusťte vývojový server:

 node server.js 

Skvělý! Nyní pokračujte a otestujte funkčnost API pomocí karantény Apollo Server API ve vašem prohlížeči. Můžete například použít mutaci registru k přidání nových uživatelských dat do databáze a poté mutaci přihlášení k ověření uživatele.

Nakonec přidejte token JWT do sekce záhlaví autorizace a pokračujte v dotazování databáze na uživatelská data.

Zabezpečení rozhraní GraphQL API

Autentizace a autorizace jsou klíčové komponenty pro zabezpečení GraphQL API. Je však důležité si uvědomit, že samy o sobě nemusí být dostatečné k zajištění komplexní bezpečnosti. Měli byste implementovat další bezpečnostní opatření, jako je ověřování vstupu a šifrování citlivých dat.

Přijetím komplexního bezpečnostního přístupu můžete ochránit svá rozhraní API před různými potenciálními útoky.