Имплементација аутентификације корисника у Екпресс.јс помоћу ЈВТ-а

GraphQL se ističe kao popularna alternativa tradicionalnoj RESTful API arhitekturi, pružajući fleksibilan i efikasan pristup za upite i manipulaciju podacima u API-jevima. Kako se njegova upotreba širi, postaje ključno osigurati sigurnost GraphQL API-ja kako bi se aplikacije zaštitile od neovlašćenog pristupa i potencijalnih kompromitacija podataka.

Jedan od efikasnih metoda za obezbeđivanje GraphQL API-ja je primena JSON Web Token-a (JWT). JWT-ovi nude siguran i efikasan način za autorizaciju pristupa zaštićenim resursima i izvođenje autorizovanih radnji, čime se obezbeđuje bezbedna komunikacija između klijenata i API-ja.

Autentifikacija i autorizacija u GraphQL API-jima

Za razliku od REST API-ja, GraphQL API-ji često imaju jednu krajnju tačku koja omogućava klijentima da dinamički zahtevaju različite količine podataka putem svojih upita. Iako je ova fleksibilnost prednost, ona takođe povećava rizik od potencijalnih bezbednosnih napada, kao što su ranjivosti u kontroli pristupa.

Kako bi se umanjio ovaj rizik, neophodno je implementirati snažne procese autentifikacije i autorizacije, uključujući jasno definisanje dozvola za pristup. Ovim se osigurava da samo ovlašćeni korisnici mogu pristupiti zaštićenim resursima, smanjujući tako rizik od potencijalnih bezbednosnih propusta i gubitka podataka.

Kod ovog projekta možete pronaći u GitHub repozitorijumu.

Podešavanje Express.js Apollo servera

Apollo Server je široko korišćena implementacija GraphQL servera za GraphQL API-je. Omogućava vam da jednostavno kreirate GraphQL šeme, definišete rezolvere i upravljate raznim izvorima podataka za vaše API-je.

Da biste podesili Express.js Apollo server, kreirajte i otvorite folder projekta:

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

Zatim, pokrenite ovu komandu kako biste inicijalizovali novi Node.js projekat koristeći npm, Node package manager:

 npm init --yes 

Sada instalirajte ove pakete:

 npm install apollo-server graphql mongoose jsonwebtoken dotenv 

Na kraju, kreirajte `server.js` fajl u osnovnom direktorijumu i podesite server sa ovim kodom:

 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("Povezano sa DB");
    return server.listen({ port: 5000 });
  })
  .then((res) => {
    console.log(`Server radi na ${res.url}`);
  })
  .catch(err => {
    console.log(err.message);
  });

GraphQL server je podešen sa parametrima `typeDefs` i `resolvers`, koji specificiraju šemu i operacije koje API može da izvrši. Opcija `context` konfiguriše `req` objekat u kontekstu svakog rezolvera, omogućavajući serveru pristup informacijama specifičnim za zahtev, poput vrednosti zaglavlja.

Kreiranje MongoDB baze podataka

Da biste uspostavili vezu sa bazom podataka, prvo kreirajte MongoDB bazu podataka ili postavite klaster na MongoDB Atlas. Nakon toga, kopirajte specificirani URI niz za povezivanje sa bazom podataka, kreirajte `.env` fajl i unesite niz za povezivanje na sledeći način:

 MONGO_URI="<mongo_connection_uri>"

Definisanje modela podataka

Definišite model podataka koristeći Mongoose. Kreirajte novi fajl `models/user.js` i uključite sledeći kod:

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

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

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

Definisanje GraphQL šeme

U GraphQL API-ju, šema definiše strukturu podataka koja se može zatražiti putem upita, kao i specificiranje dostupnih operacija (upiti i mutacije) koje možete izvršiti za interakciju sa podacima putem API-ja.

Da biste definisali šemu, kreirajte novi folder u osnovnom direktorijumu vašeg projekta i nazovite ga `graphql`. Unutar ovog foldera, dodajte dva fajla: `typeDefs.js` i `resolvers.js`.

U fajlu `typeDefs.js` uključite sledeći kod:

 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;

Kreiranje rezolvera za GraphQL API

Funkcije rezolvera definišu kako se podaci preuzimaju kao odgovor na upite i mutacije klijenata, kao i druga polja definisana u šemi. Kada klijent pošalje upit ili mutaciju, GraphQL server pokreće odgovarajuće rezolvere da obrade i vrate potrebne podatke iz različitih izvora, kao što su baze podataka ili API-ji.

Da biste implementirali autentifikaciju i autorizaciju pomoću JSON Web Token-a (JWT), definišite rezolvere za mutacije registracije i prijavljivanja. Oni će upravljati procesima registracije i autentifikacije korisnika. Zatim, kreirajte rezolver upita za preuzimanje podataka, koji će biti dostupan samo autentifikovanim i autorizovanim korisnicima.

Ali prvo, definišite funkcije za generisanje i verifikaciju JWT-ova. U fajlu `resolvers.js`, započnite dodavanjem sledećih uvoza:

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

Obavezno dodajte tajni ključ koji ćete koristiti za potpisivanje JSON Web Token-a u `.env` fajl.

 SECRET_KEY = '<my_Secret_Key>'; 

Da biste generisali token za autentifikaciju, uključite sledeću funkciju, koja takođe specificira jedinstvene atribute za JWT token, npr. vreme isteka. Pored toga, možete ugraditi i druge atribute, kao što je vreme izdavanja, na osnovu vaših specifičnih zahteva aplikacije.

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

  return token;
}

Sada implementirajte logiku verifikacije tokena da biste potvrdili JWT tokene uključene u naredne HTTP zahteve.

 function verifyToken(token) {
  if (!token) {
    throw new Error('Token nije obezbeđen');
  }

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

Ova funkcija će uzeti token kao ulaz, verifikovati njegovu validnost pomoću navedenog tajnog ključa i vratiti dekodirani token ako je važeći, u suprotnom će izbaciti grešku koja ukazuje na nevažeći token.

Definisanje API rezolvera

Da biste definisali rezolvere za GraphQL API, potrebno je specificirati operacije kojima će upravljati, u ovom slučaju, registraciju korisnika i operacije prijavljivanja. Prvo, kreirajte objekat rezolvera koji će sadržati funkcije rezolvera, a zatim definišite sledeće operacije mutacije:

 const resolvers = {
  Mutation: {
    register: async (_, { userInput: { name, password, role } }) => {
      if (!name || !password || !role) {
        throw new Error('Ime, lozinka i uloga su obavezni');
     }

      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('Neuspelo kreiranje korisnika');
      }
    },
    login: async (_, { name, password }) => {
      try {
        const user = await User.findOne({ name: name });

        if (!user) {
          throw new Error('Korisnik nije pronađen');
       }

        if (password !== user.password) {
          throw new Error('Netačna lozinka');
        }

        const token = generateToken(user);

        if (!token) {
          throw new Error('Neuspelo generisanje tokena');
        }

        return {
          message: 'Uspešno prijavljivanje',
          token: token,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Neuspelo prijavljivanje');
      }
    }
  },

Mutacija registracije upravlja procesom registracije dodavanjem novih korisničkih podataka u bazu podataka. Dok mutacija prijavljivanja upravlja prijavljivanjem korisnika – nakon uspešne autentifikacije, ona će generisati JWT token, kao i vratiti poruku o uspehu u odgovoru.

Sada uključite rezolver upita za preuzimanje korisničkih podataka. Da biste osigurali da će ovaj upit biti dostupan samo proverenim i autorizovanim korisnicima, uključite logiku autorizacije kako biste ograničili pristup samo korisnicima sa ulogom administratora.

U suštini, upit će prvo proveriti validnost tokena, a zatim i korisničku ulogu. Ako je provera autorizacije uspešna, upit rezolvera će nastaviti da preuzima i vraća podatke korisnika iz baze podataka.

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

        if (decodedToken.role !== 'Admin') {
          throw new ('Neovlašćeno. Samo administratori mogu pristupiti ovim podacima.');
        }

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

Na kraju, pokrenite razvojni server:

 node server.js 

Odlično! Sada, nastavite i testirajte funkcionalnost API-ja koristeći Apollo Server API sandbox u vašem pretraživaču. Na primer, možete koristiti mutaciju registracije da dodate nove korisničke podatke u bazu podataka, a zatim mutaciju za prijavu da biste potvrdili autentičnost korisnika.

Na kraju, dodajte JWT token u odeljak zaglavlja autorizacije i nastavite da zahtevate korisničke podatke u bazi podataka.

Obezbeđivanje GraphQL API-ja

Autentifikacija i autorizacija su ključne komponente za obezbeđivanje GraphQL API-ja. Ipak, važno je prepoznati da one same po sebi možda nisu dovoljne da osiguraju sveobuhvatnu bezbednost. Trebalo bi da primenite dodatne mere bezbednosti, kao što su validacija ulaza i šifrovanje osetljivih podataka.

Usvajanjem sveobuhvatnog bezbednosnog pristupa, možete zaštititi svoje API-je od različitih potencijalnih napada.