Како аутентификовати и овластити корисника користећи ЈВТ у НодеЈС-у

Autentifikacija i autorizacija predstavljaju ključne elemente u oblasti računarske bezbednosti. Koristeći vaše akreditive, poput korisničkog imena i lozinke, dokazujete svoj identitet i potvrđujete status registrovanog korisnika, čime stičete određene privilegije.

Ovaj princip se takođe primenjuje prilikom prijavljivanja na različite online servise putem vaših Facebook ili Google naloga.

U ovom tekstu ćemo razviti NodeJS API sa JWT (JSON Web Token) autentifikacijom. Alati koje ćemo koristiti u ovom procesu su:

  • ExpressJS
  • MongoDB baza podataka
  • Mongoose
  • Dotenv
  • BcryptJS
  • Jsonwebtoken

Autentifikacija nasuprot Autorizaciji

Šta je autentifikacija?

Autentifikacija je postupak identifikacije korisnika putem pribavljanja akreditiva kao što su email adresa, lozinka i tokeni. Navedeni akreditivi se porede sa podacima registrovanog korisnika, koji se čuvaju u lokalnom sistemu računara ili nekoj bazi podataka. Ukoliko se akreditivi podudaraju sa podacima u bazi, proces autentifikacije je uspešan i korisniku se odobrava pristup resursima.

Šta je autorizacija?

Autorizacija nastupa nakon autentifikacije. Svaka autorizacija zahteva prethodnu autentifikaciju. To je proces kojim se korisnicima omogućava pristup određenim resursima unutar sistema ili web stranice. U ovom uputstvu ćemo ovlastiti prijavljene korisnike da pristupe korisničkim podacima. Ukoliko korisnik nije prijavljen, neće imati pristup tim informacijama.

Najbolji primeri autorizacije mogu se naći na platformama društvenih medija poput Facebooka i Twittera. Ne možete pristupiti sadržaju ovih platformi ukoliko nemate kreiran nalog.

Još jedan primer autorizacije je sadržaj koji se zasniva na pretplati. Vaša autentifikacija se može izvršiti prilikom prijavljivanja na web sajt, ali nećete biti ovlašćeni za pristup sadržaju dok se ne pretplatite.

Preduslov

Pre nego što nastavite, pretpostavlja se da imate osnovno znanje JavaScript-a i MongoDB-a, kao i da ste upoznati sa NodeJS-om.

Proverite da li su node i npm instalirani na vašem računaru. Da biste potvrdili instalaciju, otvorite komandnu liniju i unesite `node -v` i `npm -v`. Trebalo bi da dobijete sličan rezultat:

Verzije na vašem sistemu mogu se razlikovati od prikazane. NPM se automatski instalira sa node-om. Ukoliko ga niste preuzeli, možete ga naći na zvaničnoj NodeJS stranici.

Potreban vam je IDE (Integrisano Razvojno Okruženje) za pisanje koda. U ovom uputstvu, koristim VS Code editor. Možete koristiti i neki drugi editor po vašem izboru. Ako nemate instaliran IDE, možete ga preuzeti sa Visual Studio stranice. Preuzmite verziju koja odgovara vašem operativnom sistemu.

Podešavanje Projekta

Napravite folder pod nazivom `nodeapi` bilo gde na vašem lokalnom disku, a zatim ga otvorite pomoću VS Code-a. Unutar VS Code terminala inicijalizujte node paket menadžer komandom:

npm init -y

Uverite se da ste u `nodeapi` direktorijumu.

Navedena komanda će kreirati `package.json` datoteku koja će sadržati sve zavisnosti koje ćemo koristiti u ovom projektu.

Sada ćemo preuzeti sve pakete navedene na početku teksta. Unesite sledeću komandu u terminal:

npm install express dotenv jsonwebtoken mongoose bcryptjs

Nakon toga, dobićete datoteke i direktorijume slične prikazanom:

Kreiranje Servera i Povezivanje Baze Podataka

Sada napravite datoteku pod nazivom `index.js` i folder `config`. U okviru foldera `config` kreirajte dve datoteke, `conn.js` za povezivanje sa bazom podataka i `config.env` za definisanje promenljivih okruženja. Unesite sledeći kod u odgovarajuće datoteke:

index.js

const express = require('express');
  const dotenv = require('dotenv');
  
  //Konfigurisanje dotenv datoteka
  dotenv.config({path:'./config/config.env'}); 
  
  //Kreiranje aplikacije pomoću Express-a
  const app = express();
  
  //Koriscenje express.json za dobijanje JSON podataka
  app.use(express.json());
  
  
  
  //Pokretanje servera
  app.listen(process.env.PORT,()=>{
      console.log(`Server je pokrenut na portu ${process.env.PORT}`);
  })
  

Ako koristite dotenv, konfigurišite ga u `index.js` datoteci pre pozivanja drugih datoteka koje koriste promenljive okruženja.

conn.js

const mongoose = require('mongoose');
  
  mongoose.connect(process.env.URI, 
      { useNewUrlParser: true,
       useUnifiedTopology: true })
      .then((data) => {
          console.log(`Baza podataka je povezana sa ${data.connection.host}`)
  })
  

config.env

URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
  PORT = 5000

Ja koristim mongoDB Atlas URI, ali možete koristiti i localhost.

Kreiranje Modela i Ruta

Model je struktura podataka u vašoj MongoDB bazi podataka, koji se čuva kao JSON dokument. Za kreiranje modela, koristićemo Mongoose šemu.

Rutiranje se odnosi na način na koji aplikacija odgovara na zahteve klijenata. Koristićemo Express router funkciju za kreiranje ruta.

Metode rutiranja obično koriste dva argumenta. Prvi je ruta, a drugi je callback funkcija koja definiše šta će ruta uraditi prilikom zahteva klijenta.

Takođe, uzima i treći argument kao middleware funkciju, kada je to potrebno, kao u procesu autentifikacije. Pošto razvijamo API sa autentifikacijom, koristićemo i middleware funkciju za autorizaciju i autentifikaciju korisnika.

Sada ćemo kreirati dva foldera pod nazivom `routes` i `models`. Unutar foldera `routes` napravite datoteku `userRoute.js`, a unutar `models` direktorijuma napravite `userModel.js` datoteku. Nakon kreiranja ovih datoteka, unesite sledeći kod u odgovarajuće fajlove:

userModel.js

const mongoose = require('mongoose');
  
  //Kreiranje šeme pomoću mongoose-a
  const userSchema = new mongoose.Schema({
      name: {
          type:String,
          required:true,
          minLength:[4,'Ime mora imati najmanje 4 karaktera']
      },
      email:{
          type:String,
          required:true,
          unique:true,
      },
      password:{
          type:String,
          required:true,
          minLength:[8,'Lozinka mora imati najmanje 8 karaktera']
      },
      token:{
          type:String
      }
  })
  
  //Kreiranje modela
  const userModel = mongoose.model('user',userSchema);
  module.exports = userModel;

userRoute.js

const express = require('express');
  //Kreiranje express router-a
  const route = express.Router();
  //Uvoz userModel-a
  const userModel = require('../models/userModel');
  
  //Kreiranje rute za registraciju
  route.post('/register',(req,res)=>{
  
  })
  //Kreiranje rute za prijavu
  route.post('/login',(req,res)=>{
  
  })
  
  //Kreiranje rute za preuzimanje korisnickih podataka
  route.get('/user',(req,res)=>{
  
  })

Implementacija Funkcionalnosti Ruta i Kreiranje JWT Tokena

Šta je JWT?

JSON Web Token (JWT) je JavaScript biblioteka koja generiše i verifikuje tokene. To je otvoreni standard koji se koristi za razmenu informacija između dve strane, klijenta i servera. Koristićemo dve funkcije JWT-a. Prva funkcija je `sign`, koja se koristi za kreiranje novog tokena, a druga je `verify`, koja se koristi za validaciju tokena.

Šta je bcryptjs?

Bcryptjs je funkcija za heširanje koju su kreirali Niels Provos i David Mazières. Koristi heš algoritam za heširanje lozinki. Ima dve osnovne funkcije koje ćemo koristiti u ovom projektu. Prva funkcija bcryptjs je `hash` za generisanje heš vrednosti, a druga je `compare` funkcija, za poređenje lozinki.

Implementacija Funkcionalnosti Ruta

Callback funkcija u rutiranju uzima tri argumenta, `request`, `response` i `next` funkciju. `Next` argument je opcionalan; prosledite ga samo ako vam je potreban. Argumenti moraju biti navedeni ovim redosledom. Sada izmenite `userRoute.js`, `config.env` i `index.js` datoteke sledećim kodom:

userRoute.js

//Uvoz svih potrebnih datoteka i biblioteka
  const express = require('express');
  const bcrypt = require('bcryptjs');
  const jwt = require('jsonwebtoken');
  
  //Kreiranje express router-a
  const route = express.Router();
  //Uvoz userModel-a
  const userModel = require('../models/userModel');
  
  //Kreiranje rute za registraciju
  route.post("/register", async (req, res) => {
  
      try {
          const { name, email, password } = req.body;
          //Provera da li su sva polja popunjena
          if (!name || !email || !password) {
              return res.json({ message: 'Molimo vas unesite sve podatke' })
          }
  
          //Provera da li korisnik već postoji
          const userExist = await userModel.findOne({ email: req.body.email });
          if (userExist) {
              return res.json({ message: 'Korisnik već postoji sa datom email adresom' })
          }
          //Heširanje lozinke
          const salt = await bcrypt.genSalt(10);
          const hashPassword = await bcrypt.hash(req.body.password, salt);
          req.body.password = hashPassword;
          const user = new userModel(req.body);
          await user.save();
          const token = await jwt.sign({ id: user._id }, process.env.SECRET_KEY, {
              expiresIn: process.env.JWT_EXPIRE,
          });
          return res.cookie({ 'token': token }).json({ success: true, message: 'Korisnik je uspešno registrovan', data: user })
      } catch (error) {
          return res.json({ error: error });
      }
  
  })
  //Kreiranje rute za prijavu
  route.post('/login', async (req, res) => {
      try {
          const { email, password } = req.body;
          //Provera da li su sva polja popunjena
          if (!email || !password) {
              return res.json({ message: 'Molimo vas unesite sve podatke' })
          }
          //Provera da li korisnik postoji
          const userExist = await userModel.findOne({email:req.body.email});
          if(!userExist){
              return res.json({message:'Pogrešni podaci'});
          }
          //Provera podudaranja lozinke
          const isPasswordMatched = await bcrypt.compare(password,userExist.password);
          if(!isPasswordMatched){
              return res.json({message:'Pogrešna lozinka'});
          }
          const token = await jwt.sign({ id: userExist._id }, process.env.SECRET_KEY, {
              expiresIn: process.env.JWT_EXPIRE,
          });
          return res.cookie({"token":token}).json({success:true,message:'Uspešna prijava'})
      } catch (error) {
          return res.json({ error: error });
      }
  
  })
  
  //Kreiranje rute za preuzimanje korisnickih podataka
  route.get('/user', async (req, res) => {
      try {
          const user  = await userModel.find();
          if(!user){
              return res.json({message:'Nije pronađen nijedan korisnik'})
          }
          return res.json({user:user})
      } catch (error) {
          return res.json({ error: error });  
      }
  })
  
  module.exports = route;

Ako koristite `async` funkciju, koristite `try-catch` blok, inače će se pojaviti greška.

config.env

URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
  PORT = 5000
  SECRET_KEY = KGGK>HKHVHJVKBKJKJBKBKHKBMKHB
  JWT_EXPIRE = 2d

index.js

const express = require('express');
  const dotenv = require('dotenv');
  
  //Konfigurisanje dotenv datoteka
  dotenv.config({path:'./config/config.env'}); 
  require('./config/conn');
  //Kreiranje aplikacije pomoću Express-a
  const app = express();
  const route = require('./routes/userRoute');
  
  //Koriscenje express.json za dobijanje JSON podataka
  app.use(express.json());
  //Koriscenje ruta
  
  app.use('/api', route);
  
  //Pokretanje servera
  app.listen(process.env.PORT,()=>{
      console.log(`Server je pokrenut na portu ${process.env.PORT}`);
  })
  

Kreiranje Middleware-a za Autentifikaciju Korisnika

Šta je Middleware?

Middleware je funkcija koja ima pristup zahtevu, odgovoru i sledećoj funkciji u request-response ciklusu. `Next` funkcija se poziva kada se izvršenje funkcije završi. Kao što sam već spomenuo, koristite `next()` kada je potrebno izvršiti drugu callback funkciju ili middleware funkciju.

Sada napravite folder pod nazivom `Middleware`, unutar njega kreirajte `auth.js` datoteku i unesite sledeći kod.

auth.js

const userModel = require('../models/userModel');
  const jwt = require('jsonwebtoken');
  const isAuthenticated = async (req,res,next)=>{
      try {
          const {token} = req.cookies;
          if(!token){
              return next('Molimo vas, prijavite se da biste pristupili podacima');
          }
          const verify = await jwt.verify(token,process.env.SECRET_KEY);
          req.user = await userModel.findById(verify.id);
          next();
      } catch (error) {
         return next(error); 
      }
  }
  
  module.exports = isAuthenticated;

Sada instalirajte `cookie-parser` biblioteku kako biste konfigurisali `cookieParser` u vašoj aplikaciji. `CookieParser` vam pomaže da pristupite tokenu koji je sačuvan u kolačiću. Ukoliko nemate konfigurisan `cookieParser` u vašoj NodeJS aplikaciji, nećete moći da pristupite kolačićima iz zaglavlja `request` objekta. Sada unesite sledeću komandu u terminalu da biste preuzeli `cookie-parser`.

npm i cookie-parser

Sada kada je `cookie-parser` instaliran, konfigurišite vašu aplikaciju modifikovanjem `index.js` datoteke i dodajte middleware na „/user/“ rutu.

index.js datoteka

const cookieParser = require('cookie-parser');
  const express = require('express');
  const dotenv = require('dotenv');
  
  //Konfigurisanje dotenv datoteka
  dotenv.config({path:'./config/config.env'}); 
  require('./config/conn');
  //Kreiranje aplikacije pomoću Express-a
  const app = express();
  const route = require('./routes/userRoute');
  
  //Koriscenje express.json za dobijanje JSON podataka
  app.use(express.json());
  //Konfigurisanje cookie-parser-a
  app.use(cookieParser()); 
  
  //Koriscenje ruta
  app.use('/api', route);
  
  //Pokretanje servera
  app.listen(process.env.PORT,()=>{
      console.log(`Server je pokrenut na portu ${process.env.PORT}`);
  })
  

userRoute.js

//Uvoz svih potrebnih datoteka i biblioteka
  const express = require('express');
  const bcrypt = require('bcryptjs');
  const jwt = require('jsonwebtoken');
  const isAuthenticated = require('../middleware/auth');
  
  //Kreiranje express router-a
  const route = express.Router();
  //Uvoz userModel-a
  const userModel = require('../models/userModel');
  
  //Kreiranje rute za preuzimanje korisnickih podataka
  route.get('/user', isAuthenticated, async (req, res) => {
      try {
          const user = await userModel.find();
          if (!user) {
              return res.json({ message: 'Nije pronađen nijedan korisnik' })
          }
          return res.json({ user: user })
      } catch (error) {
          return res.json({ error: error });
      }
  })
  
  module.exports = route;

Ruta „/user“ je dostupna samo kada je korisnik prijavljen.

Provera API-ja na Postman-u

Pre nego što proverite API-je, potrebno je da izmenite `package.json` datoteku i dodate sledeće linije koda:

"scripts": {
      "test": "echo "Error: no test specified" && exit 1",
      "start": "node index.js",
      "dev": "nodemon index.js"
    },

Možete pokrenuti server unosom `npm start`, ali će se pokrenuti samo jednom. Da bi vaš server radio dok menjate datoteke, potreban vam je nodemon. Preuzmite ga unosom sledeće komande u terminalu:

npm install -g nodemon

`-g` zastavica će preuzeti nodemon globalno na vaš lokalni sistem. Nećete morati da ga preuzimate iznova za svaki novi projekat.

Da biste pokrenuli server, unesite `npm run dev` u terminal. Trebalo bi da dobijete sledeći rezultat:

Sada kada je kod završen i server radi ispravno, proverite da li sve radi u Postman-u.

Šta je POSTMAN?

Postman je softverski alat za dizajniranje, izgradnju, razvoj i testiranje API-ja.

Ukoliko niste preuzeli Postman, možete ga preuzeti sa zvaničnog sajta Postman-a.

Sada otvorite Postman i kreirajte kolekciju pod nazivom `nodeAPItest` i unutar nje kreirajte tri zahteva: registracija, prijava i korisnik. Trebalo bi da imate sledeće fajlove:

Kada pošaljete JSON podatke na `localhost:5000/api/register`, dobićete sledeći rezultat:

Pošto kreiramo i čuvamo token u kolačiću tokom registracije, možete dobiti korisničke podatke prilikom zahteva za rutu `localhost:5000/api/user`. Ostale zahteve možete testirati u Postman-u.

Ako želite kompletan kod, možete ga naći na mom github nalogu.

Zaključak

U ovom uputstvu smo naučili kako da implementiramo autentifikaciju u NodeJS API koristeći JWT tokene. Takođe smo omogućili autorizaciju korisnicima za pristup korisničkim podacima.

SREĆNO KODIRANJE!