Erstellen einer sicheren REST-API in Node.js

Veröffentlicht: 2022-03-11

Anwendungsprogrammierschnittstellen (APIs) sind allgegenwärtig. Sie ermöglichen es Software, konsistent mit anderen Softwareteilen – intern oder extern – zu kommunizieren, was ein wesentlicher Bestandteil der Skalierbarkeit ist, ganz zu schweigen von der Wiederverwendbarkeit.

Heutzutage ist es ziemlich üblich, dass Online-Dienste öffentlich zugängliche APIs haben. Diese ermöglichen es anderen Entwicklern, Funktionen wie Social-Media-Logins, Kreditkartenzahlungen und Verhaltensverfolgung einfach zu integrieren. Der De-facto -Standard, den sie dafür verwenden, heißt REpresentational State Transfer (REST).

Während eine Vielzahl von Plattformen und Programmiersprachen für die Aufgabe verwendet werden können – z. B. ASP.NET Core, Laravel (PHP) oder Bottle (Python) – werden wir in diesem Tutorial ein einfaches, aber sicheres REST-API-Back-End mit erstellen folgender Stapel:

  • Node.js, mit der der Leser bereits vertraut sein sollte
  • Express, das den Aufbau gängiger Webserver-Aufgaben unter Node.js erheblich vereinfacht und Standard beim Aufbau eines REST-API-Backends ist
  • Mongoose, das unser Backend mit einer MongoDB-Datenbank verbindet

Entwickler, die diesem Tutorial folgen, sollten auch mit dem Terminal (oder der Eingabeaufforderung) vertraut sein.

Hinweis: Wir werden hier keine Front-End-Codebasis behandeln, aber die Tatsache, dass unser Back-End in JavaScript geschrieben ist, macht es bequem, Code – beispielsweise Objektmodelle – im gesamten Stack gemeinsam zu nutzen.

Anatomie einer REST-API

REST-APIs werden verwendet, um mithilfe eines gemeinsamen Satzes zustandsloser Operationen auf Daten zuzugreifen und diese zu bearbeiten. Diese Vorgänge sind integraler Bestandteil des HTTP-Protokolls und stellen die wesentlichen CRUD-Funktionen (Create, Read, Update, and Delete) dar, wenn auch nicht in sauberer Eins-zu-eins-Weise:

  • POST (eine Ressource erstellen oder allgemein Daten bereitstellen)
  • GET (einen Ressourcenindex oder eine einzelne Ressource abrufen)
  • PUT (Erstellen oder Ersetzen einer Ressource)
  • PATCH (Aktualisieren/Ändern einer Ressource)
  • DELETE (eine Ressource entfernen)

Mit diesen HTTP-Operationen und einem Ressourcennamen als Adresse können wir eine REST-API erstellen, indem wir für jede Operation einen Endpunkt erstellen. Und durch die Implementierung des Musters haben wir eine stabile und leicht verständliche Grundlage, die es uns ermöglicht, den Code schnell weiterzuentwickeln und anschließend zu pflegen. Wie bereits erwähnt, wird die gleiche Grundlage verwendet, um Funktionen von Drittanbietern zu integrieren, von denen die meisten ebenfalls REST-APIs verwenden, was eine solche Integration beschleunigt.

Beginnen wir zunächst mit der Erstellung unserer sicheren REST-API mit Node.js!

In diesem Tutorial erstellen wir eine ziemlich gängige (und sehr praktische) REST-API für eine Ressource namens users .

Unsere Ressource wird die folgende Grundstruktur haben:

  • id (eine automatisch generierte UUID)
  • firstName
  • lastName
  • email
  • password
  • permissionLevel (was darf dieser Benutzer tun?)

Und wir werden die folgenden Operationen für diese Ressource erstellen:

  • POST auf dem Endpunkt /users (neuen Benutzer erstellen)
  • GET auf dem Endpunkt /users (alle Benutzer auflisten)
  • GET auf dem Endpunkt /users/:userId (einen bestimmten Benutzer abrufen)
  • PATCH auf dem Endpunkt /users/:userId (Aktualisierung der Daten für einen bestimmten Benutzer)
  • DELETE auf dem Endpunkt /users/:userId (einen bestimmten Benutzer entfernen)

Wir werden auch JSON-Web-Tokens (JWTs) für Zugriffstoken verwenden. Zu diesem Zweck erstellen wir eine weitere Ressource namens auth , die die E-Mail-Adresse und das Passwort eines Benutzers erwartet und im Gegenzug das Token generiert, das für die Authentifizierung bei bestimmten Vorgängen verwendet wird. (Der großartige Artikel von Dejan Milosevic über JWT für sichere REST-Anwendungen in Java geht näher darauf ein; die Prinzipien sind die gleichen.)

Einrichtung des REST-API-Tutorials

Stellen Sie zunächst sicher, dass Sie die neueste Version von Node.js installiert haben. Für diesen Artikel verwende ich Version 14.9.0; es kann auch auf älteren Versionen funktionieren.

Stellen Sie als Nächstes sicher, dass Sie MongoDB installiert haben. Wir werden die Besonderheiten von Mongoose und MongoDB, die hier verwendet werden, nicht erläutern, aber um die Grundlagen zum Laufen zu bringen, starten Sie den Server einfach im interaktiven Modus (dh von der Befehlszeile als mongo ) und nicht als Dienst. Das liegt daran, dass wir an einem Punkt in diesem Tutorial direkt mit MongoDB interagieren müssen, anstatt über unseren Node.js-Code.

Hinweis: Mit MongoDB muss keine spezielle Datenbank erstellt werden, wie dies in einigen RDBMS-Szenarien der Fall sein könnte. Der erste Einfügungsaufruf von unserem Node.js-Code löst seine Erstellung automatisch aus.

Dieses Tutorial enthält nicht den gesamten Code, der für ein funktionierendes Projekt erforderlich ist. Es ist stattdessen beabsichtigt, dass Sie das begleitende Repo klonen und einfach den Highlights folgen, während Sie es durchlesen – aber Sie können bei Bedarf auch bestimmte Dateien und Ausschnitte aus dem Repo kopieren, wenn Sie dies bevorzugen.

Navigieren Sie in Ihrem Terminal zum resultierenden Ordner rest-api-tutorial/ . Sie werden sehen, dass unser Projekt drei Modulordner enthält:

  • common (Handhabung aller gemeinsam genutzten Dienste und Informationen, die zwischen Benutzermodulen geteilt werden)
  • users (alles über Benutzer)
  • auth (Handhabung der JWT-Generierung und des Anmeldeflusses)

Führen Sie nun npm install (oder yarn , wenn Sie es haben) aus.

Herzlichen Glückwunsch, Sie verfügen jetzt über alle Abhängigkeiten und Einstellungen, die zum Ausführen unseres einfachen REST-API-Backends erforderlich sind.

Erstellen des Benutzermoduls

Wir verwenden Mongoose, eine ODM-Bibliothek (Object Data Modeling) für MongoDB, um das Benutzermodell innerhalb des Benutzerschemas zu erstellen.

Zuerst müssen wir das Mongoose-Schema in /users/models/users.model.js :

 const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });

Sobald wir das Schema definiert haben, können wir das Schema einfach an das Benutzermodell anhängen.

 const userModel = mongoose.model('Users', userSchema);

Danach können wir dieses Modell verwenden, um alle gewünschten CRUD-Operationen in unseren Express-Endpunkten zu implementieren.

Beginnen wir mit der Operation „Benutzer erstellen“, indem wir die Route in users/routes.config.js :

 app.post('/users', [ UsersController.insert ]);

Dies wird in unsere Express-App in der Hauptdatei index.js gezogen. Das UsersController Objekt wird von unserem Controller importiert, wo wir das Passwort entsprechend hashen, definiert in /users/controllers/users.controller.js :

 exports.insert = (req, res) => { let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512',salt) .update(req.body.password) .digest("base64"); req.body.password = salt + "$" + hash; req.body.permissionLevel = 1; UserModel.createUser(req.body) .then((result) => { res.status(201).send({id: result._id}); }); };

An diesem Punkt können wir unser Mongoose-Modell testen, indem wir den Server ausführen ( npm start ) und eine POST -Anforderung mit einigen JSON-Daten an /users senden:

 { "firstName" : "Marcos", "lastName" : "Silva", "email" : "[email protected]", "password" : "s3cr3tp4sswo4rd" }

Es gibt mehrere Tools, die Sie dafür verwenden können. Insomnia (siehe unten) und Postman sind beliebte GUI-Tools, und curl ist eine gängige CLI-Wahl. Sie können sogar einfach JavaScript verwenden, z. B. über die integrierte Entwicklungstool-Konsole Ihres Browsers:

 fetch('http://localhost:3600/users', { method: 'POST', headers: { "Content-type": "application/json" }, body: JSON.stringify({ "firstName": "Marcos", "lastName": "Silva", "email": "[email protected]", "password": "s3cr3tp4sswo4rd" }) }) .then(function(response) { return response.json(); }) .then(function(data) { console.log('Request succeeded with JSON response', data); }) .catch(function(error) { console.log('Request failed', error); });

An diesem Punkt ist das Ergebnis eines gültigen Beitrags nur die ID des erstellten Benutzers: { "id": "5b02c5c84817bf28049e58a3" } . Wir müssen auch die Methode createUser zum Modell in users/models/users.model.js :

 exports.createUser = (userData) => { const user = new User(userData); return user.save(); };

Alles klar, jetzt müssen wir sehen, ob der Benutzer existiert. Dazu implementieren wir die Funktion „Get user by id“ für den folgenden Endpunkt: users/:userId .

Zuerst erstellen wir eine Route in /users/routes/config.js :

 app.get('/users/:userId', [ UsersController.getById ]);

Dann erstellen wir den Controller in /users/controllers/users.controller.js :

 exports.getById = (req, res) => { UserModel.findById(req.params.userId).then((result) => { res.status(200).send(result); }); };

Fügen Sie schließlich die Methode findById zum Modell in /users/models/users.model.js :

 exports.findById = (id) => { return User.findById(id).then((result) => { result = result.toJSON(); delete result._id; delete result.__v; return result; }); };

Die Antwort wird wie folgt sein:

 { "firstName": "Marcos", "lastName": "Silva", "email": "[email protected]", "password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==", "permissionLevel": 1, "id": "5b02c5c84817bf28049e58a3" }

Beachten Sie, dass wir das gehashte Passwort sehen können. Für dieses Tutorial zeigen wir das Passwort, aber die offensichtliche Best Practice besteht darin, das Passwort niemals preiszugeben, selbst wenn es gehasht wurde. Eine andere Sache, die wir sehen können, ist die permissionLevel , die wir später verwenden werden, um die Benutzerberechtigungen zu verwalten.

Indem wir das oben beschriebene Muster wiederholen, können wir jetzt die Funktionalität hinzufügen, um den Benutzer zu aktualisieren. Wir werden die PATCH Operation verwenden, da sie es uns ermöglicht, nur die Felder zu senden, die wir ändern möchten. Die Route lautet daher PATCH to /users/:userid , und wir senden alle Felder, die wir ändern möchten. Wir müssen auch einige zusätzliche Validierungen implementieren, da Änderungen auf den betreffenden Benutzer oder einen Administrator beschränkt sein sollten und nur ein Administrator in der Lage sein sollte, die permissionLevel zu ändern. Wir überspringen das vorerst und kommen darauf zurück, sobald wir das Authentifizierungsmodul implementiert haben. Im Moment sieht unser Controller so aus:

 exports.patchById = (req, res) => { if (req.body.password){ let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); req.body.password = salt + "$" + hash; } UserModel.patchUser(req.params.userId, req.body).then((result) => { res.status(204).send({}); }); };

Standardmäßig senden wir einen HTTP-Code 204 ohne Antworttext, um anzuzeigen, dass die Anfrage erfolgreich war.

Und wir müssen die Methode patchUser zum Modell hinzufügen:

 exports.patchUser = (id, userData) => { return User.findOneAndUpdate({ _id: id }, userData); };

Die Benutzerliste wird als GET unter /users/ durch den folgenden Controller implementiert:

 exports.list = (req, res) => { let limit = req.query.limit && req.query.limit <= 100 ? parseInt(req.query.limit) : 10; let page = 0; if (req.query) { if (req.query.page) { req.query.page = parseInt(req.query.page); page = Number.isInteger(req.query.page) ? req.query.page : 0; } } UserModel.list(limit, page).then((result) => { res.status(200).send(result); }) };

Die entsprechende Modellmethode lautet:

 exports.list = (perPage, page) => { return new Promise((resolve, reject) => { User.find() .limit(perPage) .skip(perPage * page) .exec(function (err, users) { if (err) { reject(err); } else { resolve(users); } }) }); };

Die resultierende Listenantwort hat die folgende Struktur:

 [ { "firstName": "Marco", "lastName": "Silva", "email": "[email protected]", "password": "z4tS/DtiH+0Gb4J6QN1K3w==$al6sGxKBKqxRQkDmhnhQpEB6+DQgDRH2qr47BZcqLm4/fphZ7+a9U+HhxsNaSnGB2l05Oem/BLIOkbtOuw1tXA==", "permissionLevel": 1, "id": "5b02c5c84817bf28049e58a3" }, { "firstName": "Paulo", "lastName": "Silva", "email": "[email protected]", "password": "wTsqO1kHuVisfDIcgl5YmQ==$cw7RntNrNBNw3MO2qLbx959xDvvrDu4xjpYfYgYMxRVDcxUUEgulTlNSBJjiDtJ1C85YimkMlYruU59rx2zbCw==", "permissionLevel": 1, "id": "5b02d038b653603d1ca69729" } ]

Und der letzte zu implementierende Teil ist das DELETE at /users/:userId .

Unser Verantwortlicher für die Löschung ist:

 exports.removeById = (req, res) => { UserModel.removeById(req.params.userId) .then((result)=>{ res.status(204).send({}); }); };

Wie zuvor gibt der Controller den HTTP-Code 204 und keinen Inhaltstext als Bestätigung zurück.

Die entsprechende Modellmethode sollte wie folgt aussehen:

 exports.removeById = (userId) => { return new Promise((resolve, reject) => { User.deleteMany({_id: userId}, (err) => { if (err) { reject(err); } else { resolve(err); } }); }); };

Wir haben jetzt alle notwendigen Operationen zum Bearbeiten der Benutzerressource und sind mit dem Benutzercontroller fertig. Die Hauptidee dieses Codes besteht darin, Ihnen die Kernkonzepte der Verwendung des REST-Musters zu vermitteln. Wir müssen zu diesem Code zurückkehren, um einige Validierungen und Berechtigungen dafür zu implementieren, aber zuerst müssen wir mit dem Aufbau unserer Sicherheit beginnen. Lassen Sie uns das Authentifizierungsmodul erstellen.

Erstellen des Auth-Moduls

Bevor wir das users durch Implementieren der Berechtigungs- und Validierungsmiddleware sichern können, müssen wir in der Lage sein, ein gültiges Token für den aktuellen Benutzer zu generieren. Wir generieren ein JWT als Antwort darauf, dass der Benutzer eine gültige E-Mail-Adresse und ein gültiges Passwort angibt. JWT ist ein bemerkenswertes JSON-Web-Token, das Sie verwenden können, damit der Benutzer mehrere Anfragen sicher stellen kann, ohne wiederholt zu validieren. Es hat normalerweise eine Ablaufzeit, und alle paar Minuten wird ein neues Token neu erstellt, um die Kommunikation sicher zu halten. Für dieses Tutorial verzichten wir jedoch auf die Aktualisierung des Tokens und halten es mit einem einzigen Token pro Anmeldung einfach.

Zuerst erstellen wir einen Endpunkt für POST -Anforderungen an die /auth -Ressource. Der Anfragetext enthält die E-Mail-Adresse und das Passwort des Benutzers:

 { "email" : "[email protected]", "password" : "s3cr3tp4sswo4rd2" }

Bevor wir den Controller aktivieren, sollten wir den Benutzer in /authorization/middlewares/verify.user.middleware.js validieren:

 exports.isPasswordAndUserMatch = (req, res, next) => { UserModel.findByEmail(req.body.email) .then((user)=>{ if(!user[0]){ res.status(404).send({}); }else{ let passwordFields = user[0].password.split('$'); let salt = passwordFields[0]; let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); if (hash === passwordFields[1]) { req.body = { userId: user[0]._id, email: user[0].email, permissionLevel: user[0].permissionLevel, provider: 'email', name: user[0].firstName + ' ' + user[0].lastName, }; return next(); } else { return res.status(400).send({errors: ['Invalid email or password']}); } } }); };

Danach können wir zum Controller übergehen und das JWT generieren:

 exports.login = (req, res) => { try { let refreshId = req.body.userId + jwtSecret; let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64"); req.body.refreshKey = salt; let token = jwt.sign(req.body, jwtSecret); let b = Buffer.from(hash); let refresh_token = b.toString('base64'); res.status(201).send({accessToken: token, refreshToken: refresh_token}); } catch (err) { res.status(500).send({errors: err}); } };

Auch wenn wir das Token in diesem Tutorial nicht aktualisieren, wurde der Controller so eingerichtet, dass er eine solche Generierung ermöglicht, um die Implementierung in der nachfolgenden Entwicklung zu vereinfachen.

Jetzt müssen wir nur noch die Route erstellen und die entsprechende Middleware in /authorization/routes.config.js :

 app.post('/auth', [ VerifyUserMiddleware.hasAuthValidFields, VerifyUserMiddleware.isPasswordAndUserMatch, AuthorizationController.login ]);

Die Antwort enthält das generierte JWT im Feld accessToken:

 { "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YjAyYzVjODQ4MTdiZjI4MDQ5ZTU4YTMiLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicGVybWlzc2lvbkxldmVsIjoxLCJwcm92aWRlciI6ImVtYWlsIiwibmFtZSI6Ik1hcmNvIFNpbHZhIiwicmVmcmVzaF9rZXkiOiJiclhZUHFsbUlBcE1PakZIRG1FeENRPT0iLCJpYXQiOjE1MjY5MjMzMDl9.mmNg-i44VQlUEWP3YIAYXVO-74803v1mu-y9QPUQ5VY", "refreshToken": "U3BDQXBWS3kyaHNDaGJNanlJTlFkSXhLMmFHMzA2NzRsUy9Sd2J0YVNDTmUva0pIQ0NwbTJqOU5YZHgxeE12NXVlOUhnMzBWMGNyWmdOTUhSaTdyOGc9PQ==" }

Nachdem wir das Token erstellt haben, können wir es im Authorization -Header verwenden, indem wir das Formular Bearer ACCESS_TOKEN .

Erstellen von Berechtigungen und Validierungen Middleware

Das erste, was wir definieren sollten, ist, wer die users verwenden kann. Dies sind die Szenarien, mit denen wir umgehen müssen:

  • Öffentlich zum Erstellen von Benutzern (Registrierungsprozess). Wir werden JWT für dieses Szenario nicht verwenden.
  • Privat für den angemeldeten Benutzer und für Administratoren, um diesen Benutzer zu aktualisieren.
  • Privat nur für Administratoren zum Entfernen von Benutzerkonten.

Nachdem wir diese Szenarien identifiziert haben, benötigen wir zunächst eine Middleware, die den Benutzer immer validiert, wenn er ein gültiges JWT verwendet. Die Middleware in /common/middlewares/auth.validation.middleware.js kann so einfach sein wie:

 exports.validJWTNeeded = (req, res, next) => { if (req.headers['authorization']) { try { let authorization = req.headers['authorization'].split(' '); if (authorization[0] !== 'Bearer') { return res.status(401).send(); } else { req.jwt = jwt.verify(authorization[1], secret); return next(); } } catch (err) { return res.status(403).send(); } } else { return res.status(401).send(); } };

Wir verwenden HTTP-Fehlercodes zur Behandlung von Anforderungsfehlern:

  • HTTP 401 für eine ungültige Anfrage
  • HTTP 403 für eine gültige Anfrage mit einem ungültigen Token oder einem gültigen Token mit ungültigen Berechtigungen

Wir können den bitweisen UND-Operator (Bitmasking) verwenden, um die Berechtigungen zu steuern. Wenn wir jede erforderliche Berechtigung als Potenz von 2 festlegen, können wir jedes Bit der 32-Bit-Ganzzahl als eine einzelne Berechtigung behandeln. Ein Administrator kann dann alle Berechtigungen haben, indem er seinen Berechtigungswert auf 2147483647 setzt. Dieser Benutzer könnte dann Zugriff auf jede Route haben. Als weiteres Beispiel hätte ein Benutzer, dessen Berechtigungswert auf 7 gesetzt wurde, Berechtigungen für die Rollen, die mit Bits für die Werte 1, 2 und 4 (zwei hoch 0, 1 und 2) gekennzeichnet sind.

Die Middleware dafür würde so aussehen:

 exports.minimumPermissionLevelRequired = (required_permission_level) => { return (req, res, next) => { let user_permission_level = parseInt(req.jwt.permission_level); let user_id = req.jwt.user_id; if (user_permission_level & required_permission_level) { return next(); } else { return res.status(403).send(); } }; };

Die Middleware ist generisch. Wenn die Benutzerberechtigungsstufe und die erforderliche Berechtigungsstufe in mindestens einem Bit übereinstimmen, ist das Ergebnis größer als Null, und wir können die Aktion fortsetzen. Andernfalls wird der HTTP-Code 403 zurückgegeben.

Jetzt müssen wir die Authentifizierungs-Middleware zu den Modulrouten des Benutzers in /users/routes.config.js :

 app.post('/users', [ UsersController.insert ]); app.get('/users', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(PAID), UsersController.list ]); app.get('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.getById ]); app.patch('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.patchById ]); app.delete('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(ADMIN), UsersController.removeById ]);

Damit ist die grundlegende Entwicklung unserer REST-API abgeschlossen. Es bleibt nur noch, alles auszuprobieren.

Laufen und Testen bei Schlaflosigkeit

Insomnia ist ein anständiger REST-Client mit einer guten kostenlosen Version. Die bewährte Methode besteht natürlich darin, Codetests einzubeziehen und eine ordnungsgemäße Fehlerberichterstattung in das Projekt zu implementieren, aber REST-Clients von Drittanbietern eignen sich hervorragend zum Testen und Implementieren von Lösungen von Drittanbietern, wenn Fehlerberichterstattung und Debugging des Dienstes nicht verfügbar sind. Wir werden es hier verwenden, um die Rolle einer Anwendung zu spielen und einen Einblick in das zu bekommen, was mit unserer API vor sich geht.

Um einen Benutzer zu erstellen, müssen POST nur die erforderlichen Felder an den entsprechenden Endpunkt senden und die generierte ID für die spätere Verwendung speichern.

Anfrage mit den entsprechenden Daten zum Anlegen eines Benutzers

Die API antwortet mit der Benutzer-ID:

Bestätigungsantwort mit Benutzer-ID

Wir können jetzt das JWT mit dem Endpunkt /auth/ generieren:

Anfrage mit Zugangsdaten

Als Antwort sollten wir ein Token erhalten:

Bestätigung mit dem entsprechenden JSON Web Token

Schnappen Sie sich das accessToken , stellen Sie ihm Bearer voran (denken Sie an das Leerzeichen) und fügen Sie es den Anforderungsheadern unter Authorization hinzu:

Das Einrichten der zu übertragenden Header enthält das authentifizierende JWT

Wenn wir dies jetzt nicht tun, nachdem wir die Berechtigungs-Middleware implementiert haben, würde jede Anfrage außer der Registrierung den HTTP-Code 401 zurückgeben. Mit dem gültigen Token erhalten wir jedoch die folgende Antwort von /users/:userId :

Antwort, die die Daten für den angegebenen Benutzer auflistet

Außerdem zeigen wir, wie bereits erwähnt, alle Felder aus pädagogischen Gründen und der Einfachheit halber. Das Passwort (gehasht oder anderweitig) sollte niemals in der Antwort sichtbar sein.

Versuchen wir, eine Liste der Benutzer zu erhalten:

Fordern Sie eine Liste aller Benutzer an

Überraschung! Wir erhalten eine 403-Antwort.

Aktion abgelehnt, da keine entsprechende Berechtigungsstufe vorhanden ist

Unser Benutzer ist nicht berechtigt, auf diesen Endpunkt zuzugreifen. Wir müssen das permissionLevel unseres Benutzers von 1 auf 7 ändern (oder sogar 5 würde ausreichen, da unsere kostenlosen und kostenpflichtigen Berechtigungsstufen als 1 bzw. 4 dargestellt werden). Wir können dies manuell in MongoDB an der interaktiven Eingabeaufforderung tun , so (mit geänderter ID in Ihr lokales Ergebnis):

 db.users.update({"_id" : ObjectId("5b02c5c84817bf28049e58a3")},{$set:{"permissionLevel":5}})

Dann müssen wir ein neues JWT generieren.

Danach erhalten wir die richtige Antwort:

Response mit allen Benutzern und deren Daten

Als Nächstes testen wir die Update-Funktion, indem wir eine PATCH Anfrage mit einigen Feldern an unseren Endpunkt /users/:userId :

Anfrage mit zu aktualisierenden Teildaten

Wir erwarten eine 204-Antwort als Bestätigung einer erfolgreichen Operation, aber wir können den Benutzer erneut zur Überprüfung auffordern.

Reaktion nach erfolgreicher Änderung

Schließlich müssen wir den Benutzer löschen. Wir müssen wie oben beschrieben einen neuen Benutzer erstellen (vergessen Sie nicht, die Benutzer-ID zu notieren) und sicherstellen, dass wir das richtige JWT für einen Administratorbenutzer haben. Der neue Benutzer muss seine Berechtigungen auf 2053 setzen (das ist 2048 ADMIN plus unsere früheren 5), um auch den Löschvorgang ausführen zu können. Nachdem dies erledigt und ein neues JWT generiert wurde, müssen wir unseren Authorization -Header aktualisieren:

Fordern Sie die Einrichtung zum Löschen eines Benutzers an

Wenn wir eine DELETE -Anforderung an /users/:userId , sollten wir als Bestätigung eine 204-Antwort erhalten. Wir können dies wiederum überprüfen, indem wir /users/ auffordern, alle vorhandenen Benutzer aufzulisten.

Nächste Schritte für Ihre REST-API

Mit den in diesem Tutorial behandelten Tools und Methoden sollten Sie jetzt in der Lage sein, einfache und sichere REST-APIs auf Node.js zu erstellen. Viele Best Practices, die für den Prozess nicht wesentlich sind, wurden übersprungen, also vergessen Sie nicht:

  • Implementieren Sie geeignete Validierungen (stellen Sie z. B. sicher, dass die E-Mail-Adresse des Benutzers eindeutig ist).
  • Implementieren Sie Unit-Tests und Fehlerberichte
  • Verhindern Sie, dass Benutzer ihre eigene Berechtigungsstufe ändern
  • Administratoren daran hindern, sich selbst zu entfernen
  • Verhindern Sie die Offenlegung vertraulicher Informationen (z. B. gehashte Passwörter)
  • Verschieben Sie das JWT-Secret von common/config/env.config.js in einen nicht umgebungsbasierten Off-Repo-Secret-Verteilungsmechanismus

Eine letzte Übung für den Leser kann darin bestehen, die Codebasis von der Verwendung von JavaScript-Promises auf die Async/Await-Technik umzustellen.

Für Interessierte unter Ihnen gibt es jetzt auch eine TypeScript-Version des Projekts.

Siehe auch: 5 Dinge, die Sie noch nie mit einer REST-Spezifikation gemacht haben