Implementarea autentificării utilizatorilor în Express.js folosind JWT

GraphQL este o alternativă populară la arhitectura tradițională API RESTful, oferind un limbaj flexibil și eficient de interogare și manipulare a datelor pentru API. Odată cu adoptarea sa în creștere, devine din ce în ce mai important să se prioritizeze securitatea API-urilor GraphQL pentru a proteja aplicațiile împotriva accesului neautorizat și a potențialelor încălcări ale datelor.

O abordare eficientă pentru securizarea API-urilor GraphQL este implementarea JSON Web Tokens (JWT). JWT-urile oferă o metodă sigură și eficientă pentru acordarea accesului la resursele protejate și efectuarea acțiunilor autorizate, asigurând o comunicare securizată între clienți și API-uri.

Autentificare și autorizare în API-urile GraphQL

Spre deosebire de API-urile REST, API-urile GraphQL au de obicei un singur punct final care permite clienților să solicite în mod dinamic cantități variate de date în interogările lor. Deși această flexibilitate este punctul său forte, crește și riscul unor potențiale atacuri de securitate, cum ar fi vulnerabilitățile de control al accesului întrerupte.

Pentru a reduce acest risc, este important să implementați procese solide de autentificare și autorizare, inclusiv definirea corectă a permisiunilor de acces. Procedând astfel, garantați că numai utilizatorii autorizați pot accesa resursele protejate și, în cele din urmă, reduceți riscul potențialelor încălcări de securitate și pierderi de date.

Puteți găsi codul acestui proiect în documentul său GitHub repertoriu.

Configurați un server Express.js Apollo

Apollo Server este o implementare de server GraphQL utilizată pe scară largă pentru API-urile GraphQL. Îl puteți folosi pentru a crea cu ușurință scheme GraphQL, pentru a defini soluții și pentru a gestiona diferite surse de date pentru API-urile dvs.

Pentru a configura un server Express.js Apollo, creați și deschideți un folder de proiect:

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

Apoi, rulați această comandă pentru a inițializa un nou proiect Node.js folosind npm, managerul de pachete Node:

 npm init --yes 

Acum, instalați aceste pachete.

 npm install apollo-server graphql mongoose jsonwebtokens dotenv 

În cele din urmă, creați un fișier server.js în directorul rădăcină și configurați-vă serverul cu acest cod:

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

Serverul GraphQL este configurat cu parametrii typeDefs și resolvers, specificând schema și operațiunile pe care API-ul le poate gestiona. Opțiunea context configurează obiectul req în contextul fiecărui rezolutor, ceea ce va permite serverului să acceseze detalii specifice cererii, cum ar fi valorile antetului.

  Cum să activați corectarea automată pentru matematică pe Microsoft Word

Creați o bază de date MongoDB

Pentru a stabili conexiunea la baza de date, creați mai întâi o bază de date MongoDB sau configurați un cluster pe MongoDB Atlas. Apoi, copiați șirul URI de conexiune la baza de date furnizat, creați un fișier .env și introduceți șirul de conexiune după cum urmează:

 MONGO_URI="<mongo_connection_uri>"

Definiți modelul de date

Definiți un model de date folosind Mongoose. Creați un fișier model/user.js nou și includeți următorul cod:

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

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

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

Definiți schema GraphQL

Într-un API GraphQL, schema definește structura datelor care pot fi interogate, precum și subliniază operațiunile disponibile (interogări și mutații) pe care le puteți efectua pentru a interacționa cu datele prin intermediul API-ului.

Pentru a defini o schemă, creați un folder nou în directorul rădăcină al proiectului și denumiți-l graphql. În acest folder, adăugați două fișiere: typeDefs.js și resolvers.js.

În fișierul typeDefs.js, includeți următorul cod:

 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;

Creați soluții pentru API-ul GraphQL

Funcțiile de rezolvare determină modul în care datele sunt preluate ca răspuns la interogările și mutațiile clientului, precum și alte câmpuri definite în schemă. Când un client trimite o interogare sau o mutație, serverul GraphQL declanșează rezolutorii corespunzători pentru a procesa și returna datele necesare din diverse surse, cum ar fi baze de date sau API-uri.

  De cât am nevoie în fiecare lună?

Pentru a implementa autentificarea și autorizarea folosind JSON Web Tokens (JWT), definiți soluții pentru mutațiile de înregistrare și de conectare. Acestea se vor ocupa de procesele de înregistrare și autentificare a utilizatorilor. Apoi, creați un solutor de interogare de preluare a datelor care va fi accesibil numai utilizatorilor autentificați și autorizați.

Dar mai întâi, definiți funcțiile pentru a genera și verifica JWT-urile. În fișierul resolvers.js, începeți prin a adăuga următoarele importuri.

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

Asigurați-vă că adăugați cheia secretă pe care o veți folosi pentru a semna jetoane web JSON în fișierul .env.

 SECRET_KEY = '<my_Secret_Key>'; 

Pentru a genera un jeton de autentificare, includeți următoarea funcție, care specifică și atribute unice pentru jetonul JWT, de exemplu, timpul de expirare. În plus, puteți încorpora și alte atribute, cum ar fi cele emise la timp, pe baza cerințelor specifice aplicației dvs.

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

  return token;
}

Acum, implementați logica de verificare a jetoanelor pentru a valida jetoanele JWT incluse în solicitările HTTP ulterioare.

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

Această funcție va prelua un jeton ca intrare, va verifica validitatea acestuia utilizând cheia secretă specificată și va returna jetonul decodat dacă este valid, altfel aruncă o eroare care indică un simbol invalid.

Definiți soluțiile API

Pentru a defini rezolutorii pentru API-ul GraphQL, trebuie să subliniați operațiunile specifice pe care le va gestiona, în acest caz, operațiunile de înregistrare și autentificare a utilizatorilor. Mai întâi, creați un obiect resolvers care va deține funcțiile de resolver, apoi definiți următoarele operații de mutație:

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

Mutația de registru gestionează procesul de înregistrare adăugând noile date de utilizator în baza de date. În timp ce mutația de conectare gestionează autentificarea utilizatorilor – la autentificarea cu succes, va genera un simbol JWT, precum și va returna un mesaj de succes în răspuns.

  Combinați mai multe imagini într-un fișier PDF cu Photoshop CS5

Acum, includeți soluția de interogare pentru preluarea datelor utilizatorului. Pentru a vă asigura că această interogare va fi accesibilă numai utilizatorilor autentificați și autorizați, includeți logica de autorizare pentru a restricționa accesul numai la utilizatorii cu rol de administrator.

În esență, interogarea va verifica mai întâi validitatea simbolului și apoi, rolul utilizatorului. Dacă verificarea de autorizare are succes, interogarea de rezolvare va continua să preia și să returneze datele utilizatorilor din baza de date.

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

În cele din urmă, porniți serverul de dezvoltare:

 node server.js 

Minunat! Acum, mergeți mai departe și testați funcționalitatea API-ului folosind sandbox-ul Apollo Server API din browser. De exemplu, puteți utiliza mutația de înregistrare pentru a adăuga noi date de utilizator în baza de date și apoi, mutația de conectare pentru a autentifica utilizatorul.

În cele din urmă, adăugați jetonul JWT la secțiunea antet de autorizare și continuați la interogarea bazei de date pentru datele utilizatorului.

Securizarea API-urilor GraphQL

Autentificarea și autorizarea sunt componente esențiale pentru securizarea API-urilor GraphQL. Cu toate acestea, este important să recunoaștem că ele singure pot să nu fie suficiente pentru a asigura securitatea globală. Ar trebui să implementați măsuri de securitate suplimentare, cum ar fi validarea intrărilor și criptarea datelor sensibile.

Prin adoptarea unei abordări cuprinzătoare de securitate, vă puteți proteja API-urile împotriva diferitelor atacuri potențiale.