Grunnleggende om sikkerhet: Autentisering og autorisasjon
I hjertet av datasikkerhet finner vi konseptene autentisering og autorisasjon. Autentisering handler om å verifisere identiteten din, som regel ved hjelp av brukernavn og passord. Dette beviser at du er den du utgir deg for å være og gir deg tilgang til systemet som en registrert bruker. Deretter kan autorisasjon gi deg ytterligere tilgangsrettigheter.
Dette prinsippet gjelder også når du logger deg inn på nettjenester ved hjelp av kontoer fra Facebook eller Google.
Denne artikkelen tar for seg hvordan man kan bygge en Node.js API med JWT (JSON Web Tokens) for autentisering. Vi vil bruke følgende verktøy:
- Express.js
- MongoDB database
- Mongoose
- Dotenv
- Bcrypt.js
- Jsonwebtoken
Autentisering versus autorisasjon
Hva er autentisering?
Autentisering er prosessen der man bekrefter brukerens identitet. Dette skjer ved hjelp av legitimasjon som e-post, passord eller sikkerhetstokens. Den innsendte legitimasjonen blir så sammenlignet med en registrert brukers data lagret lokalt eller i en database. Hvis det er samsvar, fullføres autentiseringen og brukeren får tilgang til ressurser.
Hva er autorisasjon?
Autorisasjon følger etter autentiseringen. Det handler om å gi den autentiserte brukeren tilgang til bestemte ressurser i et system eller en applikasjon. For å illustrere dette i vår opplæring, vil innloggede brukere få tilgang til brukerdata, mens ikke-innloggede brukere vil bli nektet tilgang.
Et godt eksempel på autorisasjon er sosiale medieplattformer som Facebook og Twitter. Uten en konto kan du ikke se innholdet. Et annet eksempel er abonnementstjenester; selv om du er logget inn på et nettsted, må du ha et gyldig abonnement for å få tilgang til premium-innhold.
Forutsetninger
Det antas at du har en grunnleggende forståelse av JavaScript, MongoDB og god kjennskap til Node.js før du fortsetter.
Sjekk også at du har Node.js og npm (Node Package Manager) installert. Du kan bekrefte dette ved å åpne kommandolinjen og skrive `node -v` og `npm -v`. Dette vil vise de installerte versjonene, som illustrert nedenfor.
Din versjon kan variere fra det viste bildet. NPM følger med Node.js. Hvis du ikke har installert dette, kan du laste det ned fra NodeJS sin hjemmeside.
For å skrive kode trenger du også et integrert utviklingsmiljø (IDE). I denne artikkelen bruker vi VS Code, men du kan velge en annen om du foretrekker det. Du kan laste ned VS Code fra Visual Studio sin hjemmeside, og velg den versjonen som passer til operativsystemet ditt.
Prosjektoppsett
Start med å opprette en ny mappe, f.eks. kalt «nodeapi», på datamaskinen din. Åpne deretter mappen i VS Code. I VS Code-terminalen, initialiser Node Package Manager ved å skrive:
npm init -y
Sørg for at du er inne i «nodeapi»-mappen før du utfører kommandoen. Denne kommandoen genererer en `package.json`-fil, der vi vil lagre alle prosjektets avhengigheter.
Nå installerer vi alle nødvendige pakker ved å skrive følgende i terminalen:
npm install express dotenv jsonwebtoken mongoose bcryptjs
Etter installasjonen vil du se en struktur med filer og mapper, omtrent som vist nedenfor:
Opprette server og koble til database
Opprett en fil som heter `index.js` og en mappe kalt `config`. Inni `config`-mappen, opprett to filer: `conn.js` (for databasekobling) og `config.env` (for miljøvariabler). Kopier og lim inn koden nedenfor i de respektive filene.
index.js
const express = require('express'); const dotenv = require('dotenv'); //Konfigurer dotenv-filer før bruk av andre bibliotek dotenv.config({path:'./config/config.env'}); //Opprett express app const app = express(); //Bruk express.json for å motta json-data app.use(express.json()); //Lytt etter server app.listen(process.env.PORT,() => { console.log(`Serveren lytter på port ${process.env.PORT}`); });
Når du bruker dotenv, er det viktig å konfigurere den i `index.js`-filen før du importerer andre filer som trenger tilgang til miljøvariabler.
conn.js
const mongoose = require('mongoose'); mongoose.connect(process.env.URI, { useNewUrlParser: true, useUnifiedTopology: true }) .then((data) => { console.log(`Database koblet til ${data.connection.host}`) });
config.env
URI = 'mongodb+srv://brukernavn:[email protected]/?retryWrites=true&w=majority' PORT = 5000
Her er et eksempel med en MongoDB Atlas URI. Du kan også bruke localhost hvis du foretrekker det.
Lage modeller og ruter
En modell representerer strukturen på dataene i MongoDB-databasen og vil lagres som JSON-dokumenter. Vi bruker Mongoose-skjemaer for å definere modellene.
Ruting definerer hvordan applikasjonen svarer på klientforespørsler. Vi bruker Express sin rutingfunksjonalitet til å opprette ruter.
Rutemetoder har typisk to argumenter: ruten og en tilbakringingsfunksjon som definerer hva ruten skal utføre når en forespørsel mottas. Et tredje argument, en mellomvarefunksjon, kan også brukes ved behov, slik som i autentiseringsprosessen. Siden vi skal lage en autentisert API, vil vi også bruke mellomvare for å autorisere og autentisere brukere.
Opprett to nye mapper: `routes` og `models`. Inne i `routes`-mappen, lag en fil som heter `userRoute.js`. I `models`-mappen lager du en fil med navnet `userModel.js`. Kopier så inn følgende kode i de respektive filene:
userModel.js
const mongoose = require('mongoose'); //Oppretter skjema med Mongoose const userSchema = new mongoose.Schema({ name: { type:String, required:true, minLength:[4,'Navn må ha minst 4 tegn'] }, email:{ type:String, required:true, unique:true, }, password:{ type:String, required:true, minLength:[8,'Passord må ha minst 8 tegn'] }, token:{ type:String } }); //Oppretter modell const userModel = mongoose.model('user',userSchema); module.exports = userModel;
userRoute.js
const express = require('express'); //Oppretter express ruter const route = express.Router(); //Importerer userModel const userModel = require('../models/userModel'); //Oppretter register-rute route.post('/register',(req,res) => { }); //Oppretter login-rute route.post('/login',(req,res) => { }); //Oppretter bruker-rute for å hente brukerdata route.get('/user',(req,res) => { });
Implementere rutefunksjonalitet og lage JWT-tokens
Hva er JWT?
JSON Web Tokens (JWT) er et JavaScript-bibliotek for å generere og validere tokens. Det er en åpen standard for sikker informasjonsutveksling mellom en klient og en server. Vi bruker to hovedfunksjoner med JWT: `sign` for å opprette et nytt token og `verify` for å validere tokenet.
Hva er bcryptjs?
Bcrypt.js er en hashing-funksjon utviklet av Niels Provos og David Mazières. Den bruker en hash-algoritme for å kryptere passord. Bcrypt.js har to vanlige funksjoner som vi skal bruke i dette prosjektet: `hash` for å generere en hashverdi, og `compare` for å sammenligne passord.
Implementere rutefunksjonalitet
Tilbakringingsfunksjonen i ruting tar tre argumenter: request (forespørsel), response (svar) og next (neste). Det siste argumentet er valgfritt og brukes når en annen funksjon eller mellomvare skal kalles. Endre filene `userRoute.js`, `config.env` og `index.js` med følgende kode:
userRoute.js
//Importerer nødvendige filer og biblioteker const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); //Oppretter express ruter const route = express.Router(); //Importerer userModel const userModel = require('../models/userModel'); //Oppretter register-rute route.post("/register", async (req, res) => { try { const { name, email, password } = req.body; //Sjekker om data er tom if (!name || !email || !password) { return res.json({ message: 'Vennligst fyll inn alle detaljene' }); } //Sjekker om brukeren allerede finnes const userExist = await userModel.findOne({ email: req.body.email }); if (userExist) { return res.json({ message: 'Bruker finnes allerede med den e-postadressen' }); } //Krypter passordet 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: 'Bruker registrert', data: user }); } catch (error) { return res.json({ error: error }); } }); //Oppretter login-rute route.post('/login', async (req, res) => { try { const { email, password } = req.body; //Sjekker om data er tom if (!email || !password) { return res.json({ message: 'Vennligst fyll inn alle detaljene' }); } //Sjekker om brukeren finnes const userExist = await userModel.findOne({email:req.body.email}); if(!userExist){ return res.json({message:'Feil brukernavn eller passord'}) } //Sjekker passord const isPasswordMatched = await bcrypt.compare(password,userExist.password); if(!isPasswordMatched){ return res.json({message:'Feil brukernavn eller passord'}); } 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:'Innlogging vellykket'}); } catch (error) { return res.json({ error: error }); } }); //Oppretter bruker-rute for å hente brukerdata route.get('/user', async (req, res) => { try { const user = await userModel.find(); if(!user){ return res.json({message:'Ingen brukere funnet'}); } return res.json({user:user}); } catch (error) { return res.json({ error: error }); } }); module.exports = route;
Husk å bruke try-catch blokker når du bruker Async-funksjoner, da dette hindrer uhåndterte avvisningsfeil.
config.env
URI = 'mongodb+srv://brukernavn:[email protected]/?retryWrites=true&w=majority' PORT = 5000 SECRET_KEY = "en-hemmelig-nøkkel-her" JWT_EXPIRE = 2d
index.js
const express = require('express'); const dotenv = require('dotenv'); //Konfigurerer dotenv filer over andre biblioteker og filer dotenv.config({path:'./config/config.env'}); require('./config/conn'); //Oppretter express app const app = express(); const route = require('./routes/userRoute'); //Bruk express.json for å motta json data app.use(express.json()); //Bruk ruter app.use('/api', route); //Lytt til server app.listen(process.env.PORT,() => { console.log(`Serveren lytter på port ${process.env.PORT}`); });
Opprette mellomvare for å autentisere bruker
Hva er mellomvare?
Mellomvare er en funksjon som har tilgang til request-objektet, response-objektet, og `next`-funksjonen i request-response-syklusen. `next`-funksjonen kalles når funksjonen er ferdig. `next()` brukes når en annen tilbakringingsfunksjon eller mellomvare skal utføres.
Opprett en ny mappe kalt `middleware` og en fil inni der kalt `auth.js`. Legg til følgende kode:
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('Vennligst logg inn for å få tilgang til data'); } 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;
Installer cookie-parser-biblioteket for å konfigurere `cookieParser` i appen din. `cookieParser` hjelper deg med å få tilgang til tokenet som er lagret i informasjonskapslene. Uten `cookieParser` vil du ikke få tilgang til informasjonskapslene fra overskriftene i request-objektet. Skriv følgende i terminalen for å laste ned `cookie-parser`:
npm i cookie-parser
Nå som `cookieParser` er installert, konfigurer appen din ved å endre `index.js`-filen og legg til mellomvare i «/user/» ruten.
index.js-filen
const cookieParser = require('cookie-parser'); const express = require('express'); const dotenv = require('dotenv'); //Konfigurer dotenv filer over andre biblioteker og filer dotenv.config({path:'./config/config.env'}); require('./config/conn'); //Opprett express app const app = express(); const route = require('./routes/userRoute'); //Bruk express.json for å motta json data app.use(express.json()); //Konfigurerer cookie-parser app.use(cookieParser()); //Bruk ruter app.use('/api', route); //Lytt til server app.listen(process.env.PORT,() => { console.log(`Serveren lytter på port ${process.env.PORT}`); });
userRoute.js
//Importerer nødvendige filer og biblioteker const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const isAuthenticated = require('../middleware/auth'); //Oppretter express ruter const route = express.Router(); //Importerer userModel const userModel = require('../models/userModel'); //Oppretter bruker-rute for å hente brukerdata route.get('/user', isAuthenticated, async (req, res) => { try { const user = await userModel.find(); if (!user) { return res.json({ message: 'Ingen brukere funnet' }); } return res.json({ user: user }); } catch (error) { return res.json({ error: error }); } }); module.exports = route;
Ruten «/user» er nå kun tilgjengelig for innloggede brukere.
Sjekker API-ene på POSTMAN
Før du sjekker API-er, må du endre `package.json`-filen og legge til følgende:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node index.js", "dev": "nodemon index.js" },
Du kan starte serveren med `npm start`, men dette vil kun kjøre serveren én gang. For å holde serveren kjørende mens du endrer filer, trenger du `nodemon`. Last ned `nodemon` ved å skrive følgende i terminalen:
npm install -g nodemon
Flagget `-g` vil laste ned `nodemon` globalt på systemet, slik at du ikke trenger å laste det ned for hvert nye prosjekt.
For å starte serveren, skriv `npm run dev` i terminalen. Du vil få et resultat som dette:
Koden din er nå fullført, og serveren kjører. Du kan sjekke APIene med POSTMAN.
Hva er POSTMAN?
POSTMAN er et verktøy for å designe, bygge, utvikle og teste API-er.
Hvis du ikke har lastet ned POSTMAN, last ned herfra: Postman sin hjemmeside.
Åpne POSTMAN og lag en ny samling med navnet `nodeAPItest`, og inni den lag tre forespørsler: `register`, `login` og `user`. Det skal se omtrent slik ut:
Når du sender JSON-data til `localhost:5000/api/register`, vil du få et resultat som ligner på dette:
Siden vi oppretter og lagrer tokenet i informasjonskapsler ved registrering, kan du få brukerdetaljene ved å spørre «/api/user»-ruten. Du kan teste de andre forespørslene i POSTMAN.
Du finner fullstendig kode på min GitHub-konto.
Konklusjon
I denne opplæringen har du lært hvordan man bruker autentisering i en Node.js API ved hjelp av JWT-tokens. Du har også lært hvordan å gi brukere tilgang til brukerdata.
GOD KODING!