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!