Tworzenie bezpiecznego interfejsu API REST w Node.js
Opublikowany: 2022-03-11Interfejsy programowania aplikacji (API) są wszędzie. Umożliwiają one spójną komunikację oprogramowania z innymi częściami oprogramowania — wewnętrznymi lub zewnętrznymi — co jest kluczowym elementem skalowalności, nie wspominając o możliwości ponownego wykorzystania.
W dzisiejszych czasach często zdarza się, że usługi online mają publiczne interfejsy API. Umożliwiają one innym programistom łatwą integrację funkcji, takich jak logowanie do mediów społecznościowych, płatności kartą kredytową i śledzenie zachowań. De facto standard, którego używają do tego celu, nazywa się REpresentational State Transfer (REST).
Chociaż do tego zadania można użyć wielu platform i języków programowania — np. ASP.NET Core, Laravel (PHP) lub Bottle (Python) — w tym samouczku zbudujemy podstawowe, ale bezpieczne zaplecze interfejsu API REST przy użyciu następujący stos:
- Node.js, z którym czytelnik powinien już trochę się zapoznać
- Express, który znacznie upraszcza tworzenie typowych zadań serwera WWW w Node.js i jest standardową opłatą w tworzeniu zaplecza API REST
- Mongoose, który połączy nasz backend z bazą danych MongoDB
Deweloperzy korzystający z tego samouczka powinni również czuć się komfortowo z terminalem (lub wierszem poleceń).
Uwaga: nie będziemy tutaj omawiać kodu front-endu, ale fakt, że nasz back-end jest napisany w JavaScript, ułatwia współdzielenie kodu — na przykład modeli obiektowych — przez cały stos.
Anatomia REST API
Interfejsy API REST służą do uzyskiwania dostępu do danych i manipulowania nimi przy użyciu wspólnego zestawu operacji bezstanowych. Operacje te są integralną częścią protokołu HTTP i reprezentują podstawowe funkcje tworzenia, odczytu, aktualizacji i usuwania (CRUD), ale nie w sposób czysty jeden-do-jednego:
-
POST
(utwórz zasób lub ogólnie podaj dane) -
GET
(pobierz indeks zasobów lub pojedynczy zasób) -
PUT
(utwórz lub zamień zasób) -
PATCH
(aktualizacja/modyfikacja zasobu) -
DELETE
(usuń zasób)
Używając tych operacji HTTP i nazwy zasobu jako adresu, możemy zbudować REST API, tworząc punkt końcowy dla każdej operacji. A dzięki wdrożeniu wzorca uzyskamy stabilną i łatwo zrozumiałą podstawę, która umożliwi nam szybką ewolucję kodu i późniejsze jego utrzymanie. Jak wspomniano wcześniej, ta sama podstawa zostanie wykorzystana do integracji funkcji innych firm, z których większość również korzysta z interfejsów API REST, co przyspiesza taką integrację.
Na razie zacznijmy tworzyć nasze bezpieczne API REST przy użyciu Node.js!
W tym samouczku stworzymy dość powszechny (i bardzo praktyczny) interfejs API REST dla zasobu o nazwie users
.
Nasz zasób będzie miał następującą podstawową strukturę:
-
id
(automatycznie wygenerowany UUID) -
firstName
-
lastName
-
email
-
password
-
permissionLevel
(co może robić ten użytkownik?)
Dla tego zasobu utworzymy następujące operacje:
-
POST
na punkcie końcowym/users
(utwórz nowego użytkownika) -
GET
na punkcie końcowym/users
(wymień wszystkich użytkowników) -
GET
na punkcie końcowym/users/:userId
(pobierz określonego użytkownika) -
PATCH
na punkcie końcowym/users/:userId
(aktualizacja danych dla konkretnego użytkownika) -
DELETE
w punkcie końcowym/users/:userId
(usuń określonego użytkownika)
Będziemy również używać tokenów internetowych JSON (JWT) do tokenów dostępu. W tym celu stworzymy kolejny zasób o nazwie auth
, który będzie oczekiwał adresu e-mail i hasła użytkownika, a w zamian wygeneruje token służący do uwierzytelniania niektórych operacji. (Świetny artykuł Dejana Miloszevicia na temat JWT dla bezpiecznych aplikacji REST w Javie zawiera więcej szczegółów na ten temat; zasady są takie same.)
Konfiguracja samouczka interfejsu API REST
Przede wszystkim upewnij się, że masz zainstalowaną najnowszą wersję Node.js. W tym artykule będę używał wersji 14.9.0; może również działać na starszych wersjach.
Następnie upewnij się, że masz zainstalowaną MongoDB. Nie będziemy wyjaśniać specyfiki Mongoose i MongoDB, które są tutaj używane, ale aby uruchomić podstawy, po prostu uruchom serwer w trybie interaktywnym (tzn. z wiersza poleceń jako mongo
), a nie jako usługę. Dzieje się tak, ponieważ w pewnym momencie tego samouczka będziemy musieli wchodzić w interakcję z MongoDB bezpośrednio, a nie za pośrednictwem naszego kodu Node.js.
Uwaga: Dzięki MongoDB nie ma potrzeby tworzenia określonej bazy danych, jak to może mieć miejsce w niektórych scenariuszach RDBMS. Pierwsze wywołanie insertu z naszego kodu Node.js automatycznie uruchomi jego utworzenie.
Ten samouczek nie zawiera całego kodu niezbędnego do działającego projektu. Zamiast tego zamierzasz sklonować repozytorium towarzyszące i po prostu podążać za najważniejszymi punktami podczas czytania — ale możesz także kopiować określone pliki i fragmenty z repozytorium w razie potrzeby, jeśli wolisz.
Przejdź do wynikowego folderu rest-api-tutorial/
w swoim terminalu. Zobaczysz, że nasz projekt zawiera trzy foldery modułów:
-
common
(obsługa wszystkich udostępnionych usług i informacji udostępnianych między modułami użytkowników) -
users
(wszystko dotyczące użytkowników) -
auth
(obsługa generowania JWT i przepływu logowania)
Teraz uruchom npm install
(lub yarn
, jeśli ją masz).
Gratulacje, masz teraz wszystkie zależności i konfigurację wymagane do uruchomienia naszego prostego zaplecza interfejsu API REST.
Tworzenie modułu użytkownika
Będziemy używać Mongoose, biblioteki modelowania danych obiektowych (ODM) dla MongoDB, aby utworzyć model użytkownika w schemacie użytkownika.
Najpierw musimy stworzyć schemat Mongoose w /users/models/users.model.js
:
const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });
Po zdefiniowaniu schematu możemy łatwo dołączyć schemat do modelu użytkownika.
const userModel = mongoose.model('Users', userSchema);
Następnie możemy użyć tego modelu do zaimplementowania wszystkich operacji CRUD, które chcemy w naszych punktach końcowych Express.
Zacznijmy od operacji „utwórz użytkownika” poprzez zdefiniowanie trasy w users/routes.config.js
:
app.post('/users', [ UsersController.insert ]);
Jest to pobierane do naszej aplikacji Express w głównym index.js
. Obiekt UsersController
jest importowany z naszego kontrolera, gdzie odpowiednio hashujemy hasło, zdefiniowane w /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}); }); };
W tym momencie możemy przetestować nasz model Mongoose, uruchamiając serwer ( npm start
) i wysyłając żądanie POST
do /users
z niektórymi danymi JSON:
{ "firstName" : "Marcos", "lastName" : "Silva", "email" : "[email protected]", "password" : "s3cr3tp4sswo4rd" }
Istnieje kilka narzędzi, których możesz do tego użyć. Bezsenność (omówiona poniżej) i Postman to popularne narzędzia GUI, a curl
jest częstym wyborem CLI. Możesz nawet użyć JavaScript, np. z wbudowanej konsoli narzędzi programistycznych przeglądarki:
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); });
W tym momencie wynikiem prawidłowego wpisu będzie tylko id utworzonego użytkownika: { "id": "5b02c5c84817bf28049e58a3" }
. Musimy również dodać metodę createUser
do modelu w users/models/users.model.js
:
exports.createUser = (userData) => { const user = new User(userData); return user.save(); };
Wszystko gotowe, teraz musimy sprawdzić, czy użytkownik istnieje. W tym celu zaimplementujemy funkcję „pobierz użytkownika według identyfikatora” dla następującego punktu końcowego: users/:userId
.
Najpierw tworzymy trasę w /users/routes/config.js
:
app.get('/users/:userId', [ UsersController.getById ]);
Następnie tworzymy kontroler w /users/controllers/users.controller.js
:
exports.getById = (req, res) => { UserModel.findById(req.params.userId).then((result) => { res.status(200).send(result); }); };
I na koniec dodaj metodę findById
do modelu w /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; }); };
Odpowiedź będzie następująca:
{ "firstName": "Marcos", "lastName": "Silva", "email": "[email protected]", "password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==", "permissionLevel": 1, "id": "5b02c5c84817bf28049e58a3" }
Zauważ, że możemy zobaczyć zaszyfrowane hasło. W tym samouczku pokazujemy hasło, ale oczywistą najlepszą praktyką jest nigdy nie ujawniać hasła, nawet jeśli zostało zaszyfrowane. Kolejną rzeczą, którą możemy zobaczyć, jest permissionLevel
, którego użyjemy później do obsługi uprawnień użytkownika.
Powtarzając wzór przedstawiony powyżej, możemy teraz dodać funkcjonalność aktualizacji użytkownika. Użyjemy operacji PATCH
, ponieważ pozwoli nam ona wysłać tylko te pola, które chcemy zmienić. Trasą będzie zatem PATCH
do /users/:userid
, a my wyślemy wszystkie pola, które chcemy zmienić. Będziemy również musieli zaimplementować dodatkową walidację, ponieważ zmiany powinny być ograniczone do danego użytkownika lub administratora, a tylko administrator powinien mieć możliwość zmiany permissionLevel
. Na razie pominiemy to i wrócimy do tego, gdy zaimplementujemy moduł auth. Na razie nasz kontroler będzie wyglądał tak:
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({}); }); };
Domyślnie wyślemy kod HTTP 204 bez treści odpowiedzi, aby wskazać, że żądanie zakończyło się powodzeniem.
I musimy dodać do modelu metodę patchUser
:
exports.patchUser = (id, userData) => { return User.findOneAndUpdate({ _id: id }, userData); };
Lista użytkowników zostanie zaimplementowana jako GET
w /users/
przez następujący kontroler:
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); }) };
Odpowiednią metodą modelową będzie:
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); } }) }); };
Wynikowa odpowiedź listy będzie miała następującą 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" } ]
A ostatnią częścią do zaimplementowania jest DELETE
w /users/:userId
.
Naszym kontrolerem do usunięcia będzie:
exports.removeById = (req, res) => { UserModel.removeById(req.params.userId) .then((result)=>{ res.status(204).send({}); }); };
Tak samo jak poprzednio, kontroler zwróci kod HTTP 204 i brak treści jako potwierdzenie.
Odpowiednia metoda modelu powinna wyglądać tak:
exports.removeById = (userId) => { return new Promise((resolve, reject) => { User.deleteMany({_id: userId}, (err) => { if (err) { reject(err); } else { resolve(err); } }); }); };
Mamy teraz wszystkie niezbędne operacje do manipulowania zasobami użytkownika i skończyliśmy z kontrolerem użytkownika. Główną ideą tego kodu jest przedstawienie podstawowych koncepcji używania wzorca REST. Będziemy musieli wrócić do tego kodu, aby zaimplementować do niego pewne walidacje i uprawnienia, ale najpierw musimy zacząć budować nasze zabezpieczenia. Stwórzmy moduł auth.

Tworzenie modułu uwierzytelniania
Zanim będziemy mogli zabezpieczyć moduł users
przez zaimplementowanie oprogramowania pośredniczącego z uprawnieniami i walidacją, musimy być w stanie wygenerować ważny token dla bieżącego użytkownika. Wygenerujemy token JWT w odpowiedzi na podanie przez użytkownika prawidłowego adresu e-mail i hasła. JWT to niezwykły token internetowy JSON, którego możesz użyć, aby użytkownik mógł bezpiecznie wykonywać kilka żądań bez wielokrotnego sprawdzania poprawności. Zwykle ma czas wygaśnięcia, a nowy token jest odtwarzany co kilka minut, aby zapewnić bezpieczną komunikację. Jednak w tym samouczku zrezygnujemy z odświeżania tokena i zachowamy prostotę za pomocą jednego tokena na logowanie.
Najpierw utworzymy punkt końcowy dla żądań POST
do zasobu /auth
. Treść żądania będzie zawierać adres e-mail użytkownika i hasło:
{ "email" : "[email protected]", "password" : "s3cr3tp4sswo4rd2" }
Zanim zaangażujemy kontrolera, powinniśmy zweryfikować użytkownika w /authorization/middlewares/verify.user.middleware.js
:
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']}); } } }); };
Po wykonaniu tej czynności możemy przejść do kontrolera i wygenerować JWT:
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}); } };
Mimo że nie będziemy odświeżać tokena w tym samouczku, kontroler został skonfigurowany tak, aby umożliwić takie generowanie, aby ułatwić jego implementację w kolejnych programach.
Wszystko, czego teraz potrzebujemy, to utworzyć trasę i wywołać odpowiednie oprogramowanie pośredniczące w /authorization/routes.config.js
:
app.post('/auth', [ VerifyUserMiddleware.hasAuthValidFields, VerifyUserMiddleware.isPasswordAndUserMatch, AuthorizationController.login ]);
Odpowiedź będzie zawierać wygenerowany token JWT w polu accessToken:
{ "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YjAyYzVjODQ4MTdiZjI4MDQ5ZTU4YTMiLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicGVybWlzc2lvbkxldmVsIjoxLCJwcm92aWRlciI6ImVtYWlsIiwibmFtZSI6Ik1hcmNvIFNpbHZhIiwicmVmcmVzaF9rZXkiOiJiclhZUHFsbUlBcE1PakZIRG1FeENRPT0iLCJpYXQiOjE1MjY5MjMzMDl9.mmNg-i44VQlUEWP3YIAYXVO-74803v1mu-y9QPUQ5VY", "refreshToken": "U3BDQXBWS3kyaHNDaGJNanlJTlFkSXhLMmFHMzA2NzRsUy9Sd2J0YVNDTmUva0pIQ0NwbTJqOU5YZHgxeE12NXVlOUhnMzBWMGNyWmdOTUhSaTdyOGc9PQ==" }
Po utworzeniu tokena możemy go użyć wewnątrz nagłówka Authorization
za pomocą formularza Bearer ACCESS_TOKEN
.
Tworzenie oprogramowania pośredniczącego dotyczącego uprawnień i walidacji
Pierwszą rzeczą, którą powinniśmy zdefiniować, jest to, kto może korzystać z zasobu users
. Oto scenariusze, z którymi będziemy musieli sobie poradzić:
- Publiczny do tworzenia użytkowników (proces rejestracji). W tym scenariuszu nie będziemy używać JWT.
- Prywatne dla zalogowanego użytkownika i dla administratorów do aktualizacji tego użytkownika.
- Prywatne dla administratora tylko do usuwania kont użytkowników.
Po zidentyfikowaniu tych scenariuszy najpierw będziemy potrzebować oprogramowania pośredniczącego, które zawsze weryfikuje użytkownika, jeśli używa on prawidłowego tokena JWT. Oprogramowanie pośredniczące w /common/middlewares/auth.validation.middleware.js
może być tak proste, jak:
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(); } };
Do obsługi błędów żądań będziemy używać kodów błędów HTTP:
- HTTP 401 dla nieprawidłowego żądania
- HTTP 403 dla ważnego żądania z nieprawidłowym tokenem lub ważnego tokena z nieprawidłowymi uprawnieniami
Możemy użyć bitowego operatora AND (maskowanie bitów), aby kontrolować uprawnienia. Jeśli ustawimy każde wymagane uprawnienie jako potęgę 2, możemy traktować każdy bit 32-bitowej liczby całkowitej jako pojedyncze uprawnienie. Administrator może wtedy mieć wszystkie uprawnienia, ustawiając ich wartość uprawnień na 2147483647. Ten użytkownik może wtedy mieć dostęp do dowolnej trasy. Jako inny przykład, użytkownik, którego wartość uprawnień była ustawiona na 7, miałby uprawnienia do ról oznaczonych bitami o wartościach 1, 2 i 4 (dwa do potęgi 0, 1 i 2).
Oprogramowanie pośredniczące do tego wyglądałoby tak:
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(); } }; };
Oprogramowanie pośredniczące jest ogólne. Jeśli poziom uprawnień użytkownika i wymagany poziom uprawnień pokrywają się przynajmniej w jednym bicie, wynik będzie większy od zera i możemy pozwolić, aby akcja była kontynuowana; w przeciwnym razie zostanie zwrócony kod HTTP 403.
Teraz musimy dodać oprogramowanie pośredniczące uwierzytelniania do tras modułu użytkownika w /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 ]);
Na tym kończy się podstawowy rozwój naszego REST API. Wszystko, co pozostaje do zrobienia, to przetestować to wszystko.
Bieganie i testowanie z bezsennością
Insomnia to przyzwoity klient REST z dobrą darmową wersją. Najlepszą praktyką jest oczywiście uwzględnienie testów kodu i wdrożenie odpowiedniego raportowania błędów w projekcie, ale klienci REST innych firm doskonale nadają się do testowania i wdrażania rozwiązań innych firm, gdy raportowanie błędów i debugowanie usługi nie jest dostępne. Użyjemy go tutaj, aby odegrać rolę aplikacji i uzyskać wgląd w to, co dzieje się z naszym API.
Aby utworzyć użytkownika, wystarczy POST
wymaganych pól do odpowiedniego punktu końcowego i zapisać wygenerowany identyfikator do późniejszego wykorzystania.
API odpowie, podając identyfikator użytkownika:
Możemy teraz wygenerować token JWT za pomocą punktu końcowego /auth/
:
W odpowiedzi powinniśmy otrzymać token:
Chwyć accessToken
, poprzedź go Bearer
(zapamiętaj spację) i dodaj go do nagłówków żądania w obszarze Authorization
:
Jeśli nie zrobimy tego teraz, gdy zaimplementowaliśmy oprogramowanie pośredniczące z uprawnieniami, każde żądanie inne niż rejestracja będzie zwracać kod HTTP 401. Jednak z prawidłowym tokenem otrzymujemy następującą odpowiedź z /users/:userId
:
Ponadto, jak wspomniano wcześniej, wyświetlamy wszystkie pola w celach edukacyjnych i dla uproszczenia. Hasło (zaszyfrowane lub inne) nigdy nie powinno być widoczne w odpowiedzi.
Spróbujmy uzyskać listę użytkowników:
Niespodzianka! Otrzymujemy odpowiedź 403.
Nasz użytkownik nie ma uprawnień dostępu do tego punktu końcowego. Będziemy musieli zmienić poziom permissionLevel
naszego użytkownika z 1 na 7 (lub nawet 5, ponieważ nasze darmowe i płatne poziomy uprawnień są reprezentowane odpowiednio jako 1 i 4.) Możemy to zrobić ręcznie w MongoDB, w jego interaktywnym podpowiedzi , tak (ze zmienionym identyfikatorem na wynik lokalny):
db.users.update({"_id" : ObjectId("5b02c5c84817bf28049e58a3")},{$set:{"permissionLevel":5}})
Następnie musimy wygenerować nowy token JWT.
Po wykonaniu tej czynności otrzymujemy właściwą odpowiedź:
Następnie przetestujmy funkcjonalność aktualizacji, wysyłając żądanie PATCH
z niektórymi polami do naszego punktu końcowego /users/:userId
:
Oczekujemy odpowiedzi 204 jako potwierdzenia udanej operacji, ale możemy ponownie poprosić użytkownika o weryfikację.
Na koniec musimy usunąć użytkownika. Musimy utworzyć nowego użytkownika zgodnie z powyższym opisem (nie zapomnij zanotować identyfikatora użytkownika) i upewnić się, że mamy odpowiedni token JWT dla administratora. Nowy użytkownik będzie potrzebował swoich uprawnień ustawionych na 2053 (czyli 2048 — ADMIN
— plus nasze wcześniejsze 5), aby móc również wykonać operację usuwania. Po wykonaniu tych czynności i wygenerowaniu nowego tokena JWT będziemy musieli zaktualizować nasz nagłówek żądania Authorization
:
Wysyłając żądanie DELETE
do /users/:userId
, powinniśmy otrzymać odpowiedź 204 jako potwierdzenie. Możemy ponownie zweryfikować, prosząc /users/
o listę wszystkich istniejących użytkowników.
Kolejne kroki dla Twojego REST API
Dzięki narzędziom i metodom opisanym w tym samouczku powinieneś być teraz w stanie tworzyć proste i bezpieczne interfejsy API REST w Node.js. Wiele najlepszych praktyk, które nie są niezbędne w procesie, zostało pominiętych, więc nie zapomnij:
- Wdróż odpowiednie walidacje (np. upewnij się, że adres e-mail użytkownika jest unikalny)
- Wdrażaj testy jednostkowe i raportowanie błędów
- Uniemożliwić użytkownikom zmianę własnego poziomu uprawnień
- Uniemożliwić administratorom usunięcie się
- Zapobiegaj ujawnianiu poufnych informacji (np. zaszyfrowanych haseł)
- Przenieś klucz tajny JWT z
common/config/env.config.js
do mechanizmu dystrybucji klucza tajnego poza repozytorium, który nie jest oparty na środowisku
Ostatnim ćwiczeniem dla czytelnika może być przekształcenie bazy kodu z użycia obietnic JavaScript na technikę async/await.
Dla tych z Was, którzy mogą być zainteresowani, dostępna jest teraz również wersja projektu w języku TypeScript.