Creazione di un'API REST sicura in Node.js

Pubblicato: 2022-03-11

Le API (Application Programming Interface) sono ovunque. Consentono al software di comunicare con altri software, interni o esterni, in modo coerente, che è un ingrediente chiave della scalabilità, per non parlare della riutilizzabilità.

Al giorno d'oggi è abbastanza comune per i servizi online avere API rivolte al pubblico. Questi consentono ad altri sviluppatori di integrare facilmente funzionalità come accessi ai social media, pagamenti con carta di credito e monitoraggio del comportamento. Lo standard de facto che usano per questo è chiamato REpresentational State Transfer (REST).

Sebbene sia possibile utilizzare una moltitudine di piattaforme e linguaggi di programmazione per l'attività, ad esempio ASP.NET Core, Laravel (PHP) o Bottle (Python), in questo tutorial creeremo un back-end API REST di base ma sicuro utilizzando la seguente pila:

  • Node.js, con cui il lettore dovrebbe già avere una certa familiarità
  • Express, che semplifica notevolmente la creazione di attività comuni del server Web in Node.js ed è una tariffa standard nella creazione di un back-end API REST
  • Mongoose, che collegherà il nostro back-end a un database MongoDB

Gli sviluppatori che seguono questo tutorial dovrebbero anche essere a loro agio con il terminale (o il prompt dei comandi).

Nota: qui non tratteremo una base di codice front-end, ma il fatto che il nostro back-end sia scritto in JavaScript rende conveniente condividere il codice, ad esempio i modelli di oggetti, nell'intero stack.

Anatomia di un'API REST

Le API REST vengono utilizzate per accedere e manipolare i dati utilizzando un set comune di operazioni stateless. Queste operazioni sono parte integrante del protocollo HTTP e rappresentano funzionalità essenziali di creazione, lettura, aggiornamento ed eliminazione (CRUD), sebbene non in modo pulito uno a uno:

  • POST (crea una risorsa o generalmente fornisci dati)
  • GET (recupera un indice di risorse o una singola risorsa)
  • PUT (crea o sostituisci una risorsa)
  • PATCH (aggiorna/modifica una risorsa)
  • DELETE (rimuove una risorsa)

Utilizzando queste operazioni HTTP e un nome di risorsa come indirizzo, possiamo creare un'API REST creando un endpoint per ogni operazione. E implementando il modello, avremo una base stabile e facilmente comprensibile che ci consentirà di evolvere rapidamente il codice e mantenerlo in seguito. Come accennato in precedenza, la stessa base verrà utilizzata per integrare funzionalità di terze parti, la maggior parte delle quali utilizza allo stesso modo API REST, rendendo tale integrazione più rapida.

Per ora, iniziamo a creare la nostra API REST sicura utilizzando Node.js!

In questo tutorial creeremo un'API REST piuttosto comune (e molto pratica) per una risorsa chiamata users .

La nostra risorsa avrà la seguente struttura di base:

  • id (un UUID generato automaticamente)
  • firstName
  • lastName
  • email
  • password
  • permissionLevel (cosa può fare questo utente?)

E creeremo le seguenti operazioni per quella risorsa:

  • POST sull'endpoint /users (crea un nuovo utente)
  • GET sull'endpoint /users (elenca tutti gli utenti)
  • GET sull'endpoint /users/:userId (ottieni un utente specifico)
  • PATCH sull'endpoint /users/:userId (aggiorna i dati per un utente specifico)
  • DELETE sull'endpoint /users/:userId (rimuove un utente specifico)

Utilizzeremo anche i token Web JSON (JWT) per i token di accesso. A tal fine, creeremo un'altra risorsa chiamata auth che si aspetterà l'e-mail e la password di un utente e, in cambio, genererà il token utilizzato per l'autenticazione su determinate operazioni. (Il grande articolo di Dejan Milosevic su JWT per applicazioni REST sicure in Java approfondisce ulteriormente questo aspetto; i principi sono gli stessi.)

Configurazione dell'esercitazione API REST

Prima di tutto, assicurati di aver installato l'ultima versione di Node.js. Per questo articolo utilizzerò la versione 14.9.0; potrebbe funzionare anche su versioni precedenti.

Quindi, assicurati di aver installato MongoDB. Non spiegheremo le specifiche di Mongoose e MongoDB che vengono utilizzate qui, ma per ottenere le basi in esecuzione, avvia semplicemente il server in modalità interattiva (cioè dalla riga di comando come mongo ) piuttosto che come servizio. Questo perché, a un certo punto di questo tutorial, dovremo interagire direttamente con MongoDB anziché tramite il nostro codice Node.js.

Nota: con MongoDB, non è necessario creare un database specifico come potrebbe essere in alcuni scenari RDBMS. La prima chiamata di inserimento dal nostro codice Node.js attiverà la sua creazione automaticamente.

Questa esercitazione non contiene tutto il codice necessario per un progetto funzionante. È inteso invece clonare il repository complementare e seguire semplicemente i momenti salienti durante la lettura, ma puoi anche copiare file e frammenti specifici dal repository, se preferisci.

Passa alla cartella risultante rest-api-tutorial/ nel tuo terminale. Vedrai che il nostro progetto contiene tre cartelle di moduli:

  • common (gestione di tutti i servizi condivisi e informazioni condivise tra i moduli utente)
  • users (tutto ciò che riguarda gli utenti)
  • auth (gestione della generazione JWT e del flusso di accesso)

Ora, esegui npm install (o yarn se ce l'hai.)

Congratulazioni, ora hai tutte le dipendenze e la configurazione necessarie per eseguire il nostro semplice back-end API REST.

Creazione del modulo utente

Utilizzeremo Mongoose, una libreria ODM (Object Data Modeling) per MongoDB, per creare il modello utente all'interno dello schema utente.

Innanzitutto, dobbiamo creare lo schema Mongoose in /users/models/users.model.js :

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

Una volta definito lo schema, possiamo facilmente allegare lo schema al modello utente.

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

Successivamente, possiamo utilizzare questo modello per implementare tutte le operazioni CRUD che desideriamo all'interno dei nostri endpoint Express.

Iniziamo con l'operazione "crea utente" definendo il percorso in users/routes.config.js :

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

Questo viene inserito nella nostra app Express nel file index.js principale. L'oggetto UsersController viene importato dal nostro controller, dove eseguiamo l'hashing della password in modo appropriato, definita 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}); }); };

A questo punto, possiamo testare il nostro modello Mongoose eseguendo il server ( npm start ) e inviando una richiesta POST a /users con alcuni dati JSON:

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

Ci sono diversi strumenti che puoi usare per questo. Insomnia (trattato di seguito) e Postman sono strumenti GUI popolari e curl è una scelta CLI comune. Puoi anche usare semplicemente JavaScript, ad esempio, dalla console degli strumenti di sviluppo integrata nel tuo browser:

 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); });

A questo punto, il risultato di un post valido sarà solo l'id dell'utente creato: { "id": "5b02c5c84817bf28049e58a3" } . Dobbiamo anche aggiungere il metodo createUser al modello in users/models/users.model.js :

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

Tutto pronto, ora dobbiamo vedere se l'utente esiste. Per questo, implementeremo la funzione "get user by id" per il seguente endpoint: users/:userId .

Innanzitutto, creiamo un percorso in /users/routes/config.js :

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

Quindi, creiamo il controller in /users/controllers/users.controller.js :

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

E infine, aggiungi il metodo findById al modello 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; }); };

La risposta sarà così:

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

Nota che possiamo vedere la password con hash. Per questo tutorial, stiamo mostrando la password, ma la migliore pratica ovvia è non rivelare mai la password, anche se è stata sottoposta a hash. Un'altra cosa che possiamo vedere è il permissionLevel , che useremo per gestire i permessi dell'utente in seguito.

Ripetendo lo schema descritto sopra, ora possiamo aggiungere la funzionalità per aggiornare l'utente. Utilizzeremo l'operazione PATCH poiché ci consentirà di inviare solo i campi che vogliamo modificare. Il percorso sarà, quindi, PATCH a /users/:userid , e invieremo tutti i campi che vogliamo modificare. Avremo anche bisogno di implementare una convalida aggiuntiva poiché le modifiche dovrebbero essere limitate all'utente in questione o a un amministratore e solo un amministratore dovrebbe essere in grado di modificare il permissionLevel . Lo salteremo per ora e torneremo ad esso una volta implementato il modulo auth. Per ora, il nostro controller sarà simile a questo:

 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({}); }); };

Per impostazione predefinita, invieremo un codice HTTP 204 senza un corpo di risposta per indicare che la richiesta è andata a buon fine.

E dovremo aggiungere il metodo patchUser al modello:

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

L'elenco utenti verrà implementato come GET in /users/ dal seguente controller:

 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); }) };

Il metodo del modello corrispondente sarà:

 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); } }) }); };

La risposta dell'elenco risultante avrà la seguente struttura:

 [ { "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" } ]

E l'ultima parte da implementare è DELETE in /users/:userId .

Il nostro controller per la cancellazione sarà:

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

Come prima, il controller restituirà il codice HTTP 204 e nessun corpo del contenuto come conferma.

Il metodo del modello corrispondente dovrebbe assomigliare a questo:

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

Ora abbiamo tutte le operazioni necessarie per manipolare la risorsa utente e abbiamo finito con il controller utente. L'idea principale di questo codice è di fornire i concetti fondamentali dell'utilizzo del modello REST. Dovremo tornare a questo codice per implementare alcune convalide e autorizzazioni, ma prima dovremo iniziare a costruire la nostra sicurezza. Creiamo il modulo di autenticazione.

Creazione del modulo di autenticazione

Prima di poter proteggere il modulo users implementando il middleware di autorizzazione e convalida, dovremo essere in grado di generare un token valido per l'utente corrente. Genereremo un JWT in risposta all'utente che fornisce un'e-mail e una password valide. JWT è un notevole token Web JSON che puoi utilizzare per fare in modo che l'utente effettui diverse richieste in modo sicuro senza convalidare ripetutamente. Di solito ha una scadenza e un nuovo token viene ricreato ogni pochi minuti per mantenere la comunicazione sicura. Per questo tutorial, tuttavia, rinunceremo all'aggiornamento del token e lo manterremo semplice con un singolo token per accesso.

Innanzitutto, creeremo un endpoint per le richieste POST alla risorsa /auth . Il corpo della richiesta conterrà l'e-mail e la password dell'utente:

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

Prima di coinvolgere il controller, dovremmo convalidare l'utente in /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']}); } } }); };

Fatto ciò, possiamo passare al controller e generare il 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}); } };

Anche se non aggiorneremo il token in questo tutorial, il controller è stato impostato per abilitare tale generazione per semplificarne l'implementazione nello sviluppo successivo.

Tutto ciò di cui abbiamo bisogno ora è creare la rotta e invocare il middleware appropriato in /authorization/routes.config.js :

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

La risposta conterrà il JWT generato nel campo accessToken:

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

Dopo aver creato il token, possiamo usarlo all'interno dell'intestazione Authorization utilizzando il modulo Bearer ACCESS_TOKEN .

Creazione del middleware di autorizzazioni e convalide

La prima cosa che dovremmo definire è chi può utilizzare la risorsa degli users . Questi sono gli scenari che dovremo gestire:

  • Pubblico per la creazione di utenti (processo di registrazione). Non useremo JWT per questo scenario.
  • Privato per l'utente che ha effettuato l'accesso e per gli amministratori per aggiornare quell'utente.
  • Privato solo per l'amministratore per la rimozione degli account utente.

Dopo aver identificato questi scenari, sarà innanzitutto necessario un middleware che convalidi sempre l'utente se sta utilizzando un JWT valido. Il middleware in /common/middlewares/auth.validation.middleware.js può essere semplice come:

 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(); } };

Utilizzeremo i codici di errore HTTP per gestire gli errori di richiesta:

  • HTTP 401 per una richiesta non valida
  • HTTP 403 per una richiesta valida con un token non valido o un token valido con autorizzazioni non valide

Possiamo usare l'operatore AND bit per bit (bitmasking) per controllare i permessi. Se impostiamo ciascuna autorizzazione richiesta come una potenza di 2, possiamo trattare ogni bit dell'intero a 32 bit come una singola autorizzazione. Un amministratore può quindi disporre di tutte le autorizzazioni impostando il valore di autorizzazione su 2147483647. L'utente potrebbe quindi avere accesso a qualsiasi percorso. Come altro esempio, un utente il cui valore di autorizzazione era impostato su 7 avrebbe autorizzazioni per i ruoli contrassegnati con bit per i valori 1, 2 e 4 (due alla potenza di 0, 1 e 2).

Il middleware per questo sarebbe simile a questo:

 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(); } }; };

Il middleware è generico. Se il livello di autorizzazione dell'utente e il livello di autorizzazione richiesto coincidono in almeno un bit, il risultato sarà maggiore di zero e possiamo lasciare che l'azione proceda; in caso contrario, verrà restituito il codice HTTP 403.

Ora, dobbiamo aggiungere il middleware di autenticazione alle route dei moduli dell'utente 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 ]);

Questo conclude lo sviluppo di base della nostra API REST. Non resta che testare il tutto.

Corsa e test con l'insonnia

Insomnia è un discreto client REST con una buona versione gratuita. La best practice è, ovviamente, quella di includere i test del codice e implementare la corretta segnalazione degli errori nel progetto, ma i client REST di terze parti sono ottimi per testare e implementare soluzioni di terze parti quando la segnalazione degli errori e il debug del servizio non sono disponibili. Lo useremo qui per svolgere il ruolo di un'applicazione e ottenere alcune informazioni su cosa sta succedendo con la nostra API.

Per creare un utente, dobbiamo solo eseguire il POST dei campi richiesti sull'endpoint appropriato e archiviare l'ID generato per un uso successivo.

Richiesta con i dati appropriati per la creazione di un utente

L'API risponderà con l'ID utente:

Risposta di conferma con userID

Ora possiamo generare il JWT usando l'endpoint /auth/ :

Richiesta con dati di accesso

Dovremmo ottenere un token come nostra risposta:

Conferma contenente il token Web JSON corrispondente

Prendi accessToken , anteponilo con Bearer (ricorda lo spazio) e aggiungilo alle intestazioni della richiesta in Authorization :

La configurazione delle intestazioni da trasferire contiene il JWT di autenticazione

Se non lo facciamo ora che abbiamo implementato il middleware delle autorizzazioni, ogni richiesta diversa dalla registrazione restituirà il codice HTTP 401. Con il token valido in atto, tuttavia, otteniamo la seguente risposta da /users/:userId :

Risposta che elenca i dati per l'utente indicato

Inoltre, come accennato in precedenza, stiamo visualizzando tutti i campi, per scopi didattici e per semplicità. La password (con hash o altro) non dovrebbe mai essere visibile nella risposta.

Proviamo a ottenere un elenco di utenti:

Richiesta di un elenco di tutti gli utenti

Sorpresa! Otteniamo una risposta 403.

Azione rifiutata per mancanza di un livello di autorizzazione appropriato

Il nostro utente non dispone delle autorizzazioni per accedere a questo endpoint. Dovremo modificare il livello di permissionLevel del nostro utente da 1 a 7 (o anche 5 lo farebbe, poiché i nostri livelli di autorizzazione gratuiti e a pagamento sono rappresentati rispettivamente come 1 e 4). Possiamo farlo manualmente in MongoDB, al suo prompt interattivo , in questo modo (con l'ID modificato nel risultato locale):

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

Quindi, dobbiamo generare un nuovo JWT.

Fatto ciò, otteniamo la risposta corretta:

Risposta con tutti gli utenti e i loro dati

Successivamente, testiamo la funzionalità di aggiornamento inviando una richiesta PATCH con alcuni campi al nostro /users/:userId endpoint:

Richiesta contenente dati parziali da aggiornare

Ci aspettiamo una risposta 204 come conferma di un'operazione riuscita, ma possiamo richiedere all'utente ancora una volta di verificare.

Risposta dopo una modifica riuscita

Infine, dobbiamo eliminare l'utente. Dovremo creare un nuovo utente come descritto sopra (non dimenticare di annotare l'ID utente) e assicurarci di avere il JWT appropriato per un utente amministratore. Il nuovo utente avrà bisogno delle proprie autorizzazioni impostate su 2053 (ovvero 2048— ADMIN —più il nostro precedente 5) per poter eseguire anche l'operazione di eliminazione. Fatto ciò e generato un nuovo JWT, dovremo aggiornare la nostra intestazione della richiesta di Authorization :

Richiedi la configurazione per l'eliminazione di un utente

Inviando una richiesta DELETE a /users/:userId , dovremmo ricevere una risposta 204 come conferma. Possiamo, ancora, verificare chiedendo a /users/ di elencare tutti gli utenti esistenti.

Passaggi successivi per la tua API REST

Con gli strumenti e i metodi trattati in questo tutorial, ora dovresti essere in grado di creare API REST semplici e sicure su Node.js. Molte delle migliori pratiche che non sono essenziali per il processo sono state ignorate, quindi non dimenticare di:

  • Implementare le convalide appropriate (ad esempio, assicurarsi che l'e-mail dell'utente sia univoca)
  • Implementare il test unitario e la segnalazione degli errori
  • Impedisci agli utenti di modificare il proprio livello di autorizzazione
  • Impedisci agli amministratori di rimuoversi
  • Impedire la divulgazione di informazioni sensibili (ad es. password con hash)
  • Sposta il segreto JWT da common/config/env.config.js a un meccanismo di distribuzione dei segreti fuori repository e non basato sull'ambiente

Un ultimo esercizio per il lettore può essere convertire la base di codice dall'uso delle promesse JavaScript alla tecnica async/await.

Per quelli di voi che potrebbero essere interessati, ora è disponibile anche una versione TypeScript del progetto.

Correlati: 5 cose che non hai mai fatto con una specifica REST