Ověřování a autorizace představují klíčové pilíře v oblasti počítačové bezpečnosti. K prokázání vaší identity a následnému získání specifických oprávnění využíváte přihlašovací údaje, typicky uživatelské jméno a heslo.
Tento princip platí i v situacích, kdy se přihlašujete k online službám prostřednictvím účtu Facebook nebo Google.
V tomto článku si ukážeme, jak vytvořit Node.js API s autentizací pomocí JWT (JSON Web Tokens). Budeme používat následující nástroje:
- Express.js
- Databázi MongoDB
- Mongoose
- Dotenv
- Bcrypt.js
- Jsonwebtoken
Autentizace vs. Autorizace
Co je autentizace?
Autentizace je proces, při kterém se uživatelé identifikují prostřednictvím přihlašovacích údajů, jako je e-mail, heslo nebo tokeny. Tyto údaje jsou porovnávány s údaji registrovaných uživatelů, uloženými v souboru místního počítačového systému nebo v databázích. Pokud se uvedené údaje shodují s údaji v databázi, ověřovací proces je úspěšný a uživateli je povolen přístup k daným zdrojům.
Co je autorizace?
Autorizace následuje po ověření. Každá autorizace vyžaduje předchozí proces autentizace. Jedná se o proces, který umožňuje uživatelům přístup ke zdrojům systémů nebo webových stránek. V tomto tutoriálu budeme autorizovat přihlášené uživatele k přístupu k uživatelským datům. Uživatel, který není přihlášen, nebude mít k těmto datům přístup.
Nejlepším příkladem autorizace jsou sociální sítě jako Facebook a Twitter. Bez uživatelského účtu nemáte přístup k jejich obsahu.
Dalším příkladem je obsah založený na předplatném. Autentizace proběhne přihlášením k webu, ale k samotnému obsahu získáte přístup až po zaplacení předplatného.
Požadavky
Než budete pokračovat, předpokládám, že máte základní znalosti JavaScriptu a MongoDB a dobré povědomí o Node.js.
Ujistěte se, že máte na svém počítači nainstalované Node.js a npm. Chcete-li ověřit instalaci, otevřete příkazový řádek a zadejte node -v
a npm -v
. Měly by se vám zobrazit výpisy verzí.
Vaše verze se mohou lišit od mých. NPM se instaluje automaticky s Node.js. Pokud Node.js nemáte, stáhněte si jej z Webu NodeJS.
Pro psaní kódu budete potřebovat IDE (integrované vývojové prostředí). V tomto tutoriálu používám VS Code, ale můžete použít i jiné. Pokud nemáte žádné IDE, můžete si VS Code stáhnout z Webu Visual Studio a vyberte verzi pro váš operační systém.
Nastavení projektu
Vytvořte složku s názvem „nodeapi“ kdekoliv na vašem počítači a poté ji otevřete v VS Code. Otevřete terminál v VS Code a inicializujte správce balíčků Node.js zadáním příkazu:
npm init -y
Ujistěte se, že jste ve složce „nodeapi“.
Tento příkaz vytvoří soubor „package.json“, který bude obsahovat všechny závislosti projektu.
Nyní si stáhněte všechny potřebné balíčky spuštěním následujícího příkazu v terminálu:
npm install express dotenv jsonwebtoken mongoose bcryptjs
Nyní byste měli mít soubory a složky, jak je znázorněno níže.
Vytvoření serveru a připojení databáze
Vytvořte soubor „index.js“ a složku „config“. Uvnitř složky „config“ vytvořte soubory „conn.js“ pro připojení k databázi a „config.env“ pro deklarování proměnných prostředí. Do těchto souborů zadejte následující kód:
index.js
const express = require('express');
const dotenv = require('dotenv');
//Konfigurace dotenv souborů dříve, než se použije jakákoliv jiná knihovna nebo soubor
dotenv.config({path:'./config/config.env'});
//Vytvoření aplikace z Express
const app = express();
//Použití express.json pro získávání json dat
app.use(express.json());
//Naslouchání serveru
app.listen(process.env.PORT,()=>{
console.log(`Server poslouchá na portu ${process.env.PORT}`);
})
Pokud používáte dotenv, nakonfigurujte jej v souboru „index.js“ před voláním jakýchkoliv souborů, které proměnné prostředí používají.
conn.js
const mongoose = require('mongoose');
mongoose.connect(process.env.URI,
{ useNewUrlParser: true,
useUnifiedTopology: true })
.then((data) => {
console.log(`Databáze připojena k ${data.connection.host}`)
})
config.env
URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
PORT = 5000
Používám MongoDB Atlas URI, ale můžete použít i localhost.
Vytváření modelů a tras
Model definuje strukturu vašich dat v databázi MongoDB a ukládá se jako dokument JSON. Pro vytvoření modelu použijeme schéma Mongoose.
Směrování (routing) definuje, jak aplikace reaguje na požadavky klienta. Pro vytváření tras použijeme funkci routeru Express.
Metody směrování obvykle akceptují dva argumenty. Prvním je trasa a druhým funkce zpětného volání, která definuje akci pro danou trasu při požadavku klienta.
Volitelně lze předat i třetí argument jako middleware, například pro ověřovací proces. Při vytváření autentizovaného API budeme používat middleware pro autorizaci a ověřování uživatelů.
Vytvořte složky „routes“ a „models“. Uvnitř „routes“ vytvořte soubor „userRoute.js“ a uvnitř „models“ „userModel.js“. Do těchto souborů vložte následující kód:
userModel.js
const mongoose = require('mongoose');
//Vytvoření schématu pomocí Mongoose
const userSchema = new mongoose.Schema({
name: {
type:String,
required:true,
minLength:[4,'Jméno musí mít minimálně 4 znaky']
},
email:{
type:String,
required:true,
unique:true,
},
password:{
type:String,
required:true,
minLength:[8,'Heslo musí mít minimálně 8 znaků']
},
token:{
type:String
}
})
//Vytvoření modelu
const userModel = mongoose.model('user',userSchema);
module.exports = userModel;
userRoute.js
const express = require('express');
//Vytvoření express routeru
const route = express.Router();
//Import modelu uživatele
const userModel = require('../models/userModel');
//Vytvoření trasy pro registraci
route.post('/register',(req,res)=>{
})
//Vytvoření trasy pro přihlášení
route.post('/login',(req,res)=>{
})
//Vytvoření trasy pro získání dat uživatele
route.get('/user',(req,res)=>{
})
Implementace funkčnosti tras a vytváření JWT tokenů
Co je JWT?
JSON Web Tokens (JWT) je knihovna v JavaScriptu pro vytváření a ověřování tokenů. Jedná se o otevřený standard pro sdílení informací mezi dvěma stranami – klientem a serverem. Budeme využívat dvě funkce JWT: „sign“ pro vytvoření nového tokenu a „verify“ pro jeho ověření.
Co je bcrypt.js?
bcrypt.js je hašovací funkce, kterou vytvořili Niels Provos a David Mazières. Používá hašovací algoritmus pro hashování hesla. Má dvě základní funkce, které budeme používat v tomto projektu. První je „hash“ pro generování haše a druhá je „compare“ pro porovnání hesel.
Implementujte funkčnost tras
Funkce zpětného volání ve směrování obvykle akceptuje tři argumenty – požadavek (request), odpověď (response) a „next“ funkci. Argument „next“ je volitelný a používá se pouze když je potřeba předat řízení dalším middleware funkcím. Tyto argumenty by měly být v pořadí request, response a next.
Nyní upravte soubory „userRoute.js“, „config.env“ a „index.js“ následujícím způsobem:
userRoute.js
//Import všech potřebných souborů a knihoven
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
//Vytvoření express routeru
const route = express.Router();
//Import modelu uživatele
const userModel = require('../models/userModel');
//Vytvoření trasy pro registraci
route.post("/register", async (req, res) => {
try {
const { name, email, password } = req.body;
//Kontrola prázdných vstupních dat
if (!name || !email || !password) {
return res.json({ message: 'Zadejte prosím všechny údaje' })
}
//Kontrola, zda uživatel již existuje
const userExist = await userModel.findOne({ email: req.body.email });
if (userExist) {
return res.json({ message: 'Uživatel s daným emailem již existuje' })
}
//Hashování hesla
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: 'Uživatel úspěšně registrován', data: user })
} catch (error) {
return res.json({ error: error });
}
})
//Vytvoření trasy pro přihlášení
route.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
//Kontrola prázdných vstupních dat
if (!email || !password) {
return res.json({ message: 'Zadejte prosím všechny údaje' })
}
//Kontrola, zda uživatel existuje
const userExist = await userModel.findOne({email:req.body.email});
if(!userExist){
return res.json({message:'Špatné přihlašovací údaje'})
}
//Kontrola správnosti hesla
const isPasswordMatched = await bcrypt.compare(password,userExist.password);
if(!isPasswordMatched){
return res.json({message:'Špatné přihlašovací heslo'});
}
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:'Úspěšně přihlášeno'})
} catch (error) {
return res.json({ error: error });
}
})
//Vytvoření trasy pro získání dat uživatele
route.get('/user', async (req, res) => {
try {
const user = await userModel.find();
if(!user){
return res.json({message:'Žádný uživatel nenalezen'})
}
return res.json({user:user})
} catch (error) {
return res.json({ error: error });
}
})
module.exports = route;
Při použití async funkcí je vhodné používat blok try-catch, aby se předešlo neošetřeným chybám promise rejection.
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');
//Konfigurace dotenv souborů dříve, než se použije jakákoliv jiná knihovna nebo soubor
dotenv.config({path:'./config/config.env'});
require('./config/conn');
//Vytvoření aplikace z Express
const app = express();
const route = require('./routes/userRoute');
//Použití express.json pro získávání json dat
app.use(express.json());
//Použití tras
app.use('/api', route);
//Naslouchání serveru
app.listen(process.env.PORT,()=>{
console.log(`Server poslouchá na portu ${process.env.PORT}`);
})
Vytvoření middlewaru pro ověření uživatele
Co je middleware?
Middleware je funkce, která má přístup k objektu požadavku, objektu odpovědi a funkci „next“ v cyklu request-response. Funkce „next“ se volá po dokončení provádění middleware funkce. Jak jsem zmínil výše, použijte next(), pokud potřebujete provést jinou funkci zpětného volání nebo middleware funkci.
Vytvořte složku „middleware“ a v ní soubor „auth.js“ s následujícím obsahem:
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('Pro přístup k datům se prosím přihlašte');
}
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;
Nainstalujte knihovnu „cookie-parser“, aby se dala nakonfigurovat v aplikaci. Cookie-parser vám pomůže získat přístup k tokenu uloženému v souboru cookie. Pokud nemáte v node.js aplikaci nakonfigurovaný cookie-parser, nebudete mít přístup k souborům cookie z hlavičky objektu požadavku. Nyní zadejte do terminálu a stáhněte si cookie-parser:
npm i cookie-parser
Po instalaci cookie-parser nakonfigurujte aplikaci úpravou „index.js“ a přidejte middleware do trasy „/user/“.
index.js
const cookieParser = require('cookie-parser');
const express = require('express');
const dotenv = require('dotenv');
//Konfigurace dotenv souborů dříve, než se použije jakákoliv jiná knihovna nebo soubor
dotenv.config({path:'./config/config.env'});
require('./config/conn');
//Vytvoření aplikace z Express
const app = express();
const route = require('./routes/userRoute');
//Použití express.json pro získávání json dat
app.use(express.json());
//Konfigurace cookie-parser
app.use(cookieParser());
//Použití tras
app.use('/api', route);
//Naslouchání serveru
app.listen(process.env.PORT,()=>{
console.log(`Server poslouchá na portu ${process.env.PORT}`);
})
userRoute.js
//Import všech potřebných souborů a knihoven
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const isAuthenticated = require('../middleware/auth');
//Vytvoření express routeru
const route = express.Router();
//Import modelu uživatele
const userModel = require('../models/userModel');
//Vytvoření trasy pro získání dat uživatele
route.get('/user', isAuthenticated, async (req, res) => {
try {
const user = await userModel.find();
if (!user) {
return res.json({ message: 'Žádný uživatel nebyl nalezen' })
}
return res.json({ user: user })
} catch (error) {
return res.json({ error: error });
}
})
module.exports = route;
Cesta „/user“ je přístupná pouze pokud je uživatel přihlášený.
Kontrola API v Postman
Před testováním API musíte upravit soubor „package.json“. Přidejte následující řádky:
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"start": "node index.js",
"dev": "nodemon index.js"
},
Server můžete spustit příkazem „npm start“, ale spustí se pouze jednou. Pro udržení serveru v chodu při změnách souborů budete potřebovat „nodemon“. Stáhněte si jej příkazem:
npm install -g nodemon
Příznak „-g“ stáhne nodemon globálně do vašeho lokálního systému. Nebudete ho muset stahovat pro každý nový projekt.
Pro spuštění serveru zadejte do terminálu „npm run dev“. Získáte následující výsledek:
Kód je hotový a server by měl běžet. Nyní se přesuneme k Postman pro otestování.
Co je Postman?
Postman je softwarový nástroj pro návrh, sestavení, vývoj a testování API.
Pokud nemáte Postman, stáhněte si jej z oficiálních stránek Postman.
Otevřete Postman a vytvořte kolekci „nodeAPItest“. V ní vytvořte tři požadavky: „register“, „login“ a „user“. Měli byste mít následující strukturu:
Pokud odešlete JSON data na „localhost:5000/api/register“, získáte následující výsledek:
Vzhledem k tomu, že vytváříme a ukládáme tokeny v cookies během registrace, můžete mít uživatelská data při požadavku na cestu „localhost:5000/api/user“. Ostatní požadavky můžete otestovat sami.
Kompletní kód najdete na mém GitHub účtu.
Závěr
V tomto tutoriálu jsme se naučili, jak implementovat autentizaci v Node.js API pomocí JWT tokenů. Také jsme povolili uživatelům přístup k uživatelským datům.
ŠŤASTNÉ KÓDOVÁNÍ!