Cover image
Technology
13 minute read

Creating a Secure REST API in Node.js

As patterns go, REST APIs are so useful and ubiquitous that every web developer, regardless of language or platform, should know how to build one. In this article, Toptal Freelance JavaScript Developer Marcos Henrique da Silva shows how to create a simple and secure REST API for user management on Node.js.

Application programming interfaces (APIs) are everywhere. They enable software to communicate with other pieces of software—internal or external—consistently, which is a key ingredient in scalability, not to mention reusability.

It’s quite common nowadays for online services to have public-facing APIs. These enable other developers to easily integrate features like social media logins, credit card payments, and behavior tracking. The de facto standard they use for this is called REpresentational State Transfer (REST).

While a multitude of platforms and programming languages can be used for the task—e.g., ASP.NET Core, Laravel (PHP), or Bottle (Python)—in this tutorial, we’ll build a basic but secure REST API back end using the following stack:

  • Node.js, which the reader should already have some familiarity with
  • Express, which vastly simplifies building out common web server tasks under Node.js and is standard fare in building a REST API back end
  • Mongoose, which will connect our back end to a MongoDB database

Developers following this tutorial should also be comfortable with the terminal (or command prompt).

Note: We won’t cover a front-end codebase here, but the fact that our back end is written in JavaScript makes it convenient to share code—object models, for instance—throughout the full stack.

Anatomy of a REST API

REST APIs are used to access and manipulate data using a common set of stateless operations. These operations are integral to the HTTP protocol and represent essential create, read, update, and delete (CRUD) functionality, although not in a clean one-to-one manner:

  • POST (create a resource or generally provide data)
  • GET (retrieve an index of resources or an individual resource)
  • PUT (create or replace a resource)
  • PATCH (update/modify a resource)
  • DELETE (remove a resource)

Using these HTTP operations and a resource name as an address, we can build a REST API by creating an endpoint for each operation. And by implementing the pattern, we will have a stable and easily understandable foundation enabling us to evolve the code rapidly and maintain it afterward. As mentioned before, the same foundation will be used to integrate third-party features, most of which likewise use REST APIs, making such integration faster.

For now, let’s start creating our secure REST API using Node.js!

In this tutorial, we are going to create a pretty common (and very practical) REST API for a resource called users.

Our resource will have the following basic structure:

  • id (an auto-generated UUID)
  • firstName
  • lastName
  • email
  • password
  • permissionLevel (what is this user allowed to do?)

And we will create the following operations for that resource:

  • POST on the endpoint /users (create a new user)
  • GET on the endpoint /users (list all users)
  • GET on the endpoint /users/:userId (get a specific user)
  • PATCH on the endpoint /users/:userId (update the data for a specific user)
  • DELETE on the endpoint /users/:userId (remove a specific user)

We will also be using JSON web tokens (JWTs) for access tokens. To that end, we will create another resource called auth that will expect a user’s email and password and, in return, will generate the token used for authentication on certain operations. (Dejan Milosevic’s great article on JWT for secure REST applications in Java goes into further detail about this; the principles are the same.)

REST API Tutorial Setup

First of all, make sure that you have the latest Node.js version installed. For this article, I’ll be using version 14.9.0; it may also work on older versions.

Next, make sure that you have MongoDB installed. We won’t explain the specifics of Mongoose and MongoDB that are used here, but to get the basics running, simply start the server in interactive mode (i.e., from the command line as mongo) rather than as a service. That’s because, at one point in this tutorial, we’ll need to interact with MongoDB directly rather than via our Node.js code.

Note: With MongoDB, there’s no need to create a specific database like there might be in some RDBMS scenarios. The first insert call from our Node.js code will trigger its creation automatically.

This tutorial does not contain all of the code necessary for a working project. It’s intended instead that you clone the companion repo and simply follow along the highlights as you read through—but you can also copy in specific files and snippets from the repo as needed, if you prefer.

Navigate to the resulting rest-api-tutorial/ folder in your terminal. You’ll see that our project contains three module folders:

  • common (handling all shared services, and information shared between user modules)
  • users (everything regarding users)
  • auth (handling JWT generation and the login flow)

Now, run npm install (or yarn if you have it.)

Congratulations, you now have all of the dependencies and setup required to run our simple REST API back end.

Creating the User Module

We will be using Mongoose, an object data modeling (ODM) library for MongoDB, to create the user model within the user schema.

First, we need to create the Mongoose schema in /users/models/users.model.js:

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

Once we define the schema, we can easily attach the schema to the user model.

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

After that, we can use this model to implement all the CRUD operations that we want within our Express endpoints.

Let’s start with the “create user” operation by defining the route in users/routes.config.js:

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

This is pulled into our Express app in the main index.js file. The UsersController object is imported from our controller, where we hash the password appropriately, defined 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});
       });
};

At this point, we can test our Mongoose model by running the server (npm start) and sending a POST request to /users with some JSON data:

{
   "firstName" : "Marcos",
   "lastName" : "Silva",
   "email" : "marcos.henrique@toptal.com",
   "password" : "s3cr3tp4sswo4rd"
}

There are several tools you can use for this. Insomnia (covered below) and Postman are popular GUI tools, and curl is a common CLI choice. You can even just use JavaScript, e.g., from your browser’s built-in development tools console:

fetch('http://localhost:3600/users', {
        method: 'POST',
        headers: {
            "Content-type": "application/json"
        },
        body: JSON.stringify({
            "firstName": "Marcos",
            "lastName": "Silva",
            "email": "marcos.henrique@toptal.com",
            "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);
    });

At this point, the result of a valid post will be just the id from the created user: { "id": "5b02c5c84817bf28049e58a3" }. We need to also add the createUser method to the model in users/models/users.model.js:

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

All set, now we need to see if the user exists. For that, we are going to implement the “get user by id” feature for the following endpoint: users/:userId.

First, we create a route in /users/routes/config.js:

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

Then, we create the controller in /users/controllers/users.controller.js:

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

And finally, add the findById method to the model 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;
    });
};

The response will be like this:

{
   "firstName": "Marcos",
   "lastName": "Silva",
   "email": "marcos.henrique@toptal.com",
   "password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==",
   "permissionLevel": 1,
   "id": "5b02c5c84817bf28049e58a3"
}

Note that we can see the hashed password. For this tutorial, we are showing the password, but the obvious best practice is never to reveal the password, even if it has been hashed. Another thing we can see is the permissionLevel, which we will use to handle the user permissions later on.

Repeating the pattern laid out above, we can now add the functionality to update the user. We will use the PATCH operation since it will enable us to send only the fields we want to change. The route will, therefore, be PATCH to /users/:userid, and we’ll be sending any fields we want to change. We will also need to implement some extra validation since changes should be restricted to the user in question or an admin, and only an admin should be able to change the permissionLevel. We’ll skip that for now and get back to it once we implement the auth module. For now, our controller will look like this:

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

By default, we will send an HTTP code 204 with no response body to indicate that the request was successful.

And we’ll need to add the patchUser method to the model:

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

The user list will be implemented as a GET at /users/ by the following 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);
   })
};

The corresponding model method will be:

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

The resulting list response will have the following structure:

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

And the last part to be implemented is the DELETE at /users/:userId.

Our controller for deletion will be:

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

Same as before, the controller will return HTTP code 204 and no content body as confirmation.

The corresponding model method should look like this:

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

We now have all the necessary operations for manipulating the user resource, and we’re done with the user controller. The main idea of this code is to give you the core concepts of using the REST pattern. We’ll need to return to this code to implement some validations and permissions to it, but first, we’ll need to start building our security. Let’s create the auth module.

Creating the Auth Module

Before we can secure the users module by implementing the permission and validation middleware, we’ll need to be able to generate a valid token for the current user. We will generate a JWT in response to the user providing a valid email and password. JWT is a remarkable JSON web token that you can use to have the user securely make several requests without validating repeatedly. It usually has an expiration time, and a new token is recreated every few minutes to keep the communication secure. For this tutorial, though, we will forgo refreshing the token and keep it simple with a single token per login.

First, we will create an endpoint for POST requests to /auth resource. The request body will contain the user email and password:

{
   "email" : "marcos.henrique2@toptal.com",
   "password" : "s3cr3tp4sswo4rd2"
}

Before we engage the controller, we should validate the user 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']});
               }
           }
       });
};

Having done that, we can move on to the controller and generate the 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});
   }
};

Even though we won’t be refreshing the token in this tutorial, the controller has been set up to enable such generation to make it easier to implement it in subsequent development.

All we need now is to create the route and invoke the appropriate middleware in /authorization/routes.config.js:

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

The response will contain the generated JWT in the accessToken field:

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

Having created the token, we can use it inside the Authorization header using the form Bearer ACCESS_TOKEN.

Creating Permissions and Validations Middleware

The first thing we should define is who can use the users resource. These are the scenarios that we’ll need to handle:

  • Public for creating users (registration process). We will not use JWT for this scenario.
  • Private for the logged-in user and for admins to update that user.
  • Private for admin only for removing user accounts.

Having identified these scenarios, we will first require a middleware that always validates the user if they are using a valid JWT. The middleware in /common/middlewares/auth.validation.middleware.js can be as simple as:

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

We will use HTTP error codes for handling request errors:

  • HTTP 401 for an invalid request
  • HTTP 403 for a valid request with an invalid token, or valid token with invalid permissions

We can use the bitwise AND operator (bitmasking) to control the permissions. If we set each required permission as a power of 2, we can treat each bit of the 32-bit integer as a single permission. An admin can then have all permissions by setting their permission value to 2147483647. That user could then have access to any route. As another example, a user whose permission value was set to 7 would have permissions to the roles marked with bits for values 1, 2, and 4 (two to the power of 0, 1, and 2).

The middleware for that would look like this:

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

The middleware is generic. If the user permission level and the required permission level coincide in at least one bit, the result will be greater than zero, and we can let the action proceed; otherwise, the HTTP code 403 will be returned.

Now, we need to add the authentication middleware to the user’s module routes 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
]);

This concludes the basic development of our REST API. All that remains to be done is to test it all out.

Running and Testing with Insomnia

Insomnia is a decent REST client with a good free version. The best practice is, of course, to include code tests and implement proper error reporting in the project, but third-party REST clients are great for testing and implementing third-party solutions when error reporting and debugging the service is not available. We’ll be using it here to play the role of an application and get some insight into what is going on with our API.

To create a user, we just need to POST the required fields to the appropriate endpoint and store the generated ID for subsequent use.

Request with the appropriate data for creating a user

The API will respond with the user ID:

Confirmation response with userID

We can now generate the JWT using the /auth/ endpoint:

Request with login data

We should get a token as our response:

Confirmation containing the corresponding JSON Web Token

Grab the accessToken, prefix it with Bearer (remember the space), and add it to the request headers under Authorization:

Setting up the headers to transfer contain the authenticating JWT

If we don’t do this now that we have implemented the permissions middleware, every request other than registration would be returning HTTP code 401. With the valid token in place, though, we get the following response from /users/:userId:

Response listing the data for the indicated user

Also, as was mentioned before, we are displaying all fields, for educational purposes and for sake of simplicity. The password (hashed or otherwise) should never be visible in the response.

Let’s try to get a list of users:

Request for a list of all users

Surprise! We get a 403 response.

Action refused due to lack of appropriate permission level

Our user does not have the permissions to access this endpoint. We will need to change the permissionLevel of our user from 1 to 7 (or even 5 would do, since our free and paid permissions levels are represented as 1 and 4, respectively.) We can do this manually in MongoDB, at its interactive prompt, like this (with the ID changed to your local result):

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

Then, we need to generate a new JWT.

After that is done, we get the proper response:

Response with all users and their data

Next, let’s test the update functionality by sending a PATCH request with some fields to our /users/:userId endpoint:

Request containing partial data to be updated

We expect a 204 response as confirmation of a successful operation, but we can request the user once again to verify.

Response after successful change

Finally, we need to delete the user. We’ll need to create a new user as described above (don’t forget to note the user ID) and make sure that we have the appropriate JWT for an admin user. The new user will need their permissions set to 2053 (that’s 2048—ADMIN—plus our earlier 5) to be able to also perform the delete operation. With that done and a new JWT generated, we’ll have to update our Authorization request header:

Request setup for deleting a user

Sending a DELETE request to /users/:userId, we should get a 204 response as confirmation. We can, again, verify by requesting /users/ to list all existing users.

Next Steps for Your REST API

With the tools and methods covered in this tutorial, you should now be able to create simple and secure REST APIs on Node.js. A lot of best practices that are not essential to the process were skipped, so don’t forget to:

  • Implement proper validations (e.g., make sure that user email is unique)
  • Implement unit testing and error reporting
  • Prevent users from changing their own permission level
  • Prevent admins from removing themselves
  • Prevent disclosure of sensitive information (e.g., hashed passwords)
  • Move the JWT secret from common/config/env.config.js to an off-repo, non-environment-based secret distribution mechanism

One final exercise for the reader can be to convert the codebase from its use of JavaScript promises over to the async/await technique.

For those of you who might be interested, there is now also a TypeScript version of the project available.

Comments

Вячеслав Москаленко
The response will be like this: { "firstName": "Marcos", "lastName": "Silva", "email": "marcos.henrique@toptal.com", "password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==", "permissionLevel": 1, "id": "5b02c5c84817bf28049e58a3" } Before we manually convert result into result. toJSON() and remove _id and __v fields (the latter added by mongoose). There are two questions here: a) where did 'id' (not _id) field came from? It is obviously not from toJSON() (we can clearly see in the mongoose documentation, that calling toJSON() returns an object containing '_id' field inside). b) why removing _id and __v manually if we can use projection?
Вячеслав Москаленко
The response will be like this: { "firstName": "Marcos", "lastName": "Silva", "email": "marcos.henrique@toptal.com", "password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==", "permissionLevel": 1, "id": "5b02c5c84817bf28049e58a3" } Before we manually convert result into result. toJSON() and remove _id and __v fields (the latter added by mongoose). There are two questions here: a) where did 'id' (not _id) field came from? It is obviously not from toJSON() (we can clearly see in the mongoose documentation, that calling toJSON() returns an object containing '_id' field inside). b) why removing _id and __v manually if we can use projection?
Вячеслав Москаленко
exports.patchUser = (id, userData) => { return new Promise((resolve, reject) => { User.findById(id, function (err, user) { if (err) reject(err); for (let i in userData) { user[i] = userData[i]; } user.save(function (err, updatedUser) { if (err) return reject(err); resolve(updatedUser); }); }); }) }; I guess we can use couple of shortcuts here: a) use 'findOneAndUpdate' (http://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate) built into mongoose; b) shorten a code without using unnecessary promise wrapper: exports.patchUser = (id, userData) => { return User.findOneAndUpdate({ _id: id }, userData); }; User.findOneAndUpdate returns us a Query, which has then method, hence we can use it as a promise.
Вячеслав Москаленко
exports.patchUser = (id, userData) => { return new Promise((resolve, reject) => { User.findById(id, function (err, user) { if (err) reject(err); for (let i in userData) { user[i] = userData[i]; } user.save(function (err, updatedUser) { if (err) return reject(err); resolve(updatedUser); }); }); }) }; I guess we can use couple of shortcuts here: a) use 'findOneAndUpdate' (http://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate) built into mongoose; b) shorten a code without using unnecessary promise wrapper: exports.patchUser = (id, userData) => { return User.findOneAndUpdate({ _id: id }, userData); }; User.findOneAndUpdate returns us a Query, which has then method, hence we can use it as a promise.
Ernesto Jimenez
This was such an excellent tutorial! Thanks for sharing.
Ernesto Jimenez
This was such an excellent tutorial! Thanks for sharing.
Memoria
You shouldn't be using your own encryption method. If you have to store password, then Argon2 is the current best practice. Here is a list of guidelines that MUST be enforced when building an authentication system: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet https://www.owasp.org/index.php/Authentication_Cheat_Sheet
Memoria
You shouldn't be using your own encryption method. If you have to store password, then Argon2 is the current best practice. Here is a list of guidelines that MUST be enforced when building an authentication system: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet https://www.owasp.org/index.php/Authentication_Cheat_Sheet
Pier Paolo Fumagalli
While I second the idea of using Argon2 (or Srypt for that matter), if we want to use something built into node, we should be using at least PBKDF2 with SHA512 and at least 100,000 rounds. Normally my code looks somehow like this: <pre> const ALGORITHM = 'sha512' const ENCODING = 'utf-8' const ROUNDS = 100000 const LENGTH = 64 // size of sha512 ... let saltBuffer = crypto.randomBytes(LENGTH) let secretBuffer = new Buffer(password, ENCODING) crypto.pbkdf2(secretBuffer, saltBuffer, ROUNDS, LENGTH, ALGORITHM, (error, hashBuffer) => { if (error) return reject(error) return resolve([ ALGORITHM, ROUNDS, saltBuffer.toString('hex'), hashBuffer.toString('hex'), ].join(',')) }) </pre>
Pier Paolo Fumagalli
While I second the idea of using Argon2 (or Srypt for that matter), if we want to use something built into node, we should be using at least PBKDF2 with SHA512 and at least 100,000 rounds. Normally my code looks somehow like this: <pre> const ALGORITHM = 'sha512' const ENCODING = 'utf-8' const ROUNDS = 100000 const LENGTH = 64 // size of sha512 ... let saltBuffer = crypto.randomBytes(LENGTH) let secretBuffer = new Buffer(password, ENCODING) crypto.pbkdf2(secretBuffer, saltBuffer, ROUNDS, LENGTH, ALGORITHM, (error, hashBuffer) => { if (error) return reject(error) return resolve([ ALGORITHM, ROUNDS, saltBuffer.toString('hex'), hashBuffer.toString('hex'), ].join(',')) }) </pre>
Austin Condiff
Hi Marcos, great tutorial but you didn't mention how to configure your server.js to use the files we created in this tutorial.
Austin Condiff
Hi Marcos, great tutorial but you didn't mention how to configure your server.js to use the files we created in this tutorial.
Vedran Aberle Tokić
There is a linked gitHub project at the end of the article that fills in the gaps
Vedran Aberle Tokić
There is a linked gitHub project at the end of the article that fills in the gaps
Des
Great article Marcos, enjoyed reading it. Well done
Des
Great article Marcos, enjoyed reading it. Well done
Mark Spencer
At this point, I’m very happy ( deepwebservice01 AT gmail DOT com ) has gotten my credit where it needs to be. Over the past few days I have watched as one by one the old negative items on my report have dropped off and my score went up and to excellent. I thank them for their hard work and will recommend their services to everyone as they also help with their credit..
Mark Spencer
At this point, I’m very happy ( deepwebservice01 AT gmail DOT com ) has gotten my credit where it needs to be. Over the past few days I have watched as one by one the old negative items on my report have dropped off and my score went up and to excellent. I thank them for their hard work and will recommend their services to everyone as they also help with their credit..
Bruno Carletti
Hi Marcos, great tutorial! Thanks for that! What do you think about to put the header "Location" in response of POST method?
Bruno Carletti
Hi Marcos, great tutorial! Thanks for that! What do you think about to put the header "Location" in response of POST method?
Vishal Gangwar
Hi Macros great tutorial, but you didn't mention how to use swagger in this project
Vishal Gangwar
Hi Macros great tutorial, but you didn't mention how to use swagger in this project
Bruno Santana
Hi! I guess the tutorial is more suitable for developers already familiar with express js, am I right? I tried to follow the article step by step, but it apparently leads to just part of the system being coded. Had to download the full version from github and DIFF the directory, in order to get chunks of code not present in the guide. =]
Bruno Santana
Hi! I guess the tutorial is more suitable for developers already familiar with express js, am I right? I tried to follow the article step by step, but it apparently leads to just part of the system being coded. Had to download the full version from github and DIFF the directory, in order to get chunks of code not present in the guide. =]
Nguyễn Đình Trung
Thanks Marcos so much for a great tutorial.
Nguyễn Đình Trung
Thanks Marcos so much for a great tutorial.
Lem Mikee
Hi! This tutorial is very useful but it is much appreciated by those developer who is already familiar or have used expressJs before. If the person who is reading had no idea or knowledge about MongoDb, NodeJs, ExpressJs would be a chaotic for them.
Lem Mikee
Hi! This tutorial is very useful but it is much appreciated by those developer who is already familiar or have used expressJs before. If the person who is reading had no idea or knowledge about MongoDb, NodeJs, ExpressJs would be a chaotic for them.
Nguyễn Trường Sơn
Hi Bro, thanks for your great tutorial. However, the /auth/refresh route with an error "Key must be a buffer,... at auth.validation.middleware.js" can you check it?
Nguyễn Trường Sơn
Hi Bro, thanks for your great tutorial. However, the /auth/refresh route with an error "Key must be a buffer,... at auth.validation.middleware.js" can you check it?
4nealalan
I feel like this is maybe the 7th node tutorial I've tried to go through but it's missing some huge pieces early on. It just falls apart in the Setup section. Disappointed.
4nealalan
I feel like this is maybe the 7th node tutorial I've tried to go through but it's missing some huge pieces early on. It just falls apart in the Setup section. Disappointed.
Marcos Henrique da Silva
Hi There, which parts you got issues for setting up the project? Did you get the project via github repository, have mongodb running local on your machine? Thanks
Marcos Henrique da Silva
Hi There, which parts you got issues for setting up the project? Did you get the project via github repository, have mongodb running local on your machine? Thanks
Marcos Henrique da Silva
Hi Lem, thanks for your feedback. I must agree with your arguments. The main point of the article was to show a simplified way to create a REST basic app without getting into specific part of NodeJS, MongoDb and Express to deep. For next articles I will provide a better review for each component I will use for the article.
Marcos Henrique da Silva
Hi Lem, thanks for your feedback. I must agree with your arguments. The main point of the article was to show a simplified way to create a REST basic app without getting into specific part of NodeJS, MongoDb and Express to deep. For next articles I will provide a better review for each component I will use for the article.
Marcos Henrique da Silva
Thanks!
Marcos Henrique da Silva
Thanks!
Marcos Henrique da Silva
Hi Vishal, thanks for the feedback. Although the package.json has the swagger there I wasn't focused on it to show as an example of usage. I will try to provide a good example in a next node.js article.
Marcos Henrique da Silva
Hi Vishal, thanks for the feedback. Although the package.json has the swagger there I wasn't focused on it to show as an example of usage. I will try to provide a good example in a next node.js article.
Marcos Henrique da Silva
It's a good pattern that I've avoid in this article. Thanks for pointing it Bruno
Marcos Henrique da Silva
It's a good pattern that I've avoid in this article. Thanks for pointing it Bruno
Marcos Henrique da Silva
Thank you Des
Marcos Henrique da Silva
Thank you Des
Marcos Henrique da Silva
Hi there, sorry for the delay. I've updated the repository with a fix there.
Marcos Henrique da Silva
Hi there, sorry for the delay. I've updated the repository with a fix there.
Marcos Henrique da Silva
Hi there, I've made a quick maintenance at the code and accepted some pull requests. It should be all working from scratch. Thanks for the feedback
Marcos Henrique da Silva
Hi there, I've made a quick maintenance at the code and accepted some pull requests. It should be all working from scratch. Thanks for the feedback
Vishwas C
Great article and I liked the conversation below about using different encryptions. One suggestionn would be to secure the env.config.js file to use credentials at run time from the runtime env, I see that you've jwt.secret = "your secret" added to the env.config.js file, I am not sure if that's a best practice to store credentials in a JS file.
Vishwas C
Great article and I liked the conversation below about using different encryptions. One suggestionn would be to secure the env.config.js file to use credentials at run time from the runtime env, I see that you've jwt.secret = "your secret" added to the env.config.js file, I am not sure if that's a best practice to store credentials in a JS file.
Raghav Arora
Hi Marcos, You made a wonderful blog for this kinda of tutorials like API's.
Raghav Arora
Hi Marcos, You made a wonderful blog for this kinda of tutorials like API's.
Rodney Barbati
Having never written a node js application, this is really totally confusing. I think you are saying that a module is a server side construct. A module contains routes and controllers, both of which are written in javascript and contained in various files. You put your routes in a route.config.js, and your controllers in another file. You export each of your controller methods to make them available for use by the routes. The routes define REST endpoints on the server. These endpoints perform their functionality by calling methods on the modules controller. The model you keep referring to appears to be a data model that would reside in the browser, and which would constitute the user's session (or a portion thereof). Your UI would call methods on the model that would then make HTTP calls to the routes defined on the server, which would process the passed parameters by calling controller methods. As far as what parts of the whole are being supported by express or mongoose or other libraries, it is not clear at all. Basically, there isn't any way for a new node developer to know which parts are native node and which parts are express or mongoose. It isn't even clear if you can actually create REST services using just Node.
Rodney Barbati
Having never written a node js application, this is really totally confusing. I think you are saying that a module is a server side construct. A module contains routes and controllers, both of which are written in javascript and contained in various files. You put your routes in a route.config.js, and your controllers in another file. You export each of your controller methods to make them available for use by the routes. The routes define REST endpoints on the server. These endpoints perform their functionality by calling methods on the modules controller. The model you keep referring to appears to be a data model that would reside in the browser, and which would constitute the user's session (or a portion thereof). Your UI would call methods on the model that would then make HTTP calls to the routes defined on the server, which would process the passed parameters by calling controller methods. As far as what parts of the whole are being supported by express or mongoose or other libraries, it is not clear at all. Basically, there isn't any way for a new node developer to know which parts are native node and which parts are express or mongoose. It isn't even clear if you can actually create REST services using just Node.
Noha Amr
how to implement unit testing?
Noha Amr
how to implement unit testing?
loai amer
Hi Marcos, I've been lost at this point of run the server and check, could you please explain how did yo do that ?
loai amer
Hi Marcos, I've been lost at this point of run the server and check, could you please explain how did yo do that ?
Marcos Henrique da Silva
Hi Rodney, First of all thanks for the polite feedback and sorry for my delay to reply to you. By your comments, I must say sorry for breaking of expectations regarding the technology that I've used on the article and the way I wrote. I was expecting readers to be familiar with Node.JS architecture to read this article. The main idea of the article was to create a hands on to create an initial setup for a REST API using Node.JS. In the beginning was written that we would use express.js as a framework of choice to speed up our development but unfortunately some people got misunderstood about it. Your way of thinking regarding modules is almost accurate. A module can be defined as part of a program which can do a specific routine. In a bigger scenario I set up three folders to call as a module to each routine: shared routines, users and authentication. Between each I broke into other modules to be able to have several pieces of code that should do the minimum routine to avoid bad code maintenance. As by the REST part, it is a style of 'how to build an API'. What we build in this article is a simple starting point of how could you handle it in the back-end side to serve several different clients (such as Postman, Insomnia, Angular, React and any kind of technology which can call this API). Although I gave a brief explanation about REST, there were a lot of missing information to avoid to create a huge article which goes off from the hands-on topic. Regarding your last message point "As far as what parts of the whole are being supported by express or mongoose or other libraries, it is not clear at all." A: Thanks again for the feedback and indeed in the written article here is no proper explanation about it and I will try to improve for next articles that I might write. "Basically, there isn't any way for a new node developer to know which parts are native node and which parts are express or mongoose." A: Again, thanks for the feedback. I was expecting readers that knew at least the basic from Node.JS since that would be enough to know which is express and which is mongoose and on. Although the point of the article was to not teach which library is which, I understood your point and I will work forward for my next articles to be more clear on that. "It isn't even clear if you can actually create REST services using just Node." A: I guess that your point is that if we can or cannot create REST services without using pure Node.JS code, avoiding any extra libraries. If that is the point, I would say that since we are using a node.js library (express.js) that is build with node.js and for node.js, then it is still true that we can build REST services with just Node. If you want to avoid all abstraction I would recommend to go to the http node module (https://nodejs.org/api/http.html) and to try to create what express.js already do for us to easy it up the development process in order to understand better this part. I appreciate your concerns about this article and I hope that you have your doubts a bit more clear regarding it. Best regards,
Marcos Henrique da Silva
Hi Vishwas, Indeed is a bad practice. For the sake of the article I put the environment configs there just to focus in running the code for who are new to node.js and express. With my clients I usually follow your recommendation. That said, thanks for pointing this out also, I should had wrote this in the article as an alert. Best regards,
Marcos Henrique da Silva
Hi Raghav, thanks!
Marcos Henrique da Silva
Hi Noha, you can try mocha (https://mochajs.org/) and supertest (https://github.com/visionmedia/supertest) Usually I use the mix of both to get unit testing on my projects. Best regards,
Marcos Henrique da Silva
Hi loai, Assuming that you are familiar with terminal and command line, at the root of your project please run the command 'npm start'. It will start the server at port 3600. Once that is done, you should be able to use Postman, Insomnia or other client that you might be using to be able to do the API calls described in the article using the initial endpoint as localhost:3600/ (an example look how I made in the article the post to localhost:3600/users and adding a JSON body with all the fields there). Hope it works for you, Best regards
Stephen Lawal
Hi, good tutorial, some key steps were ommitted in the explanantion but if you have a background in express one might just be able to follow through. However, i cloned the repo now and insert user is broken. TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be one of type string, TypedArray, or DataView. Received type undefined
Marcos Henrique da Silva
Hi Stephen, thanks for the feedback. I've just cloned a fresh project from the git, started mongod on my terminal and made a post for users without any issues. Did you created a post request to localhost:3600/users with a application/json body with: { "firstName": "Marcos", "lastName": "Silva", "email" : "marcos.henrique@toptal.com", "password" : "mypasshere" } using postman, I have this code request: ``` POST /users HTTP/1.1 Host: localhost:3600 Content-Type: application/json User-Agent: PostmanRuntime/7.11.0 Accept: */* Cache-Control: no-cache Postman-Token: 06e9191b-8311-4f33-8244-95e9036f4c5d,1f683a36-69b0-4080-aab0-11b901d52cbb Host: localhost:3600 cookie: connect.sid=s%3ARN9D8kQpPNVLvglAQ1DWXy4HId0Z2IkY.u8ZeA3RiyRrYCQR3lB%2Bqtpg7Uuse2t8FCwqAL%2F3aWSQ accept-encoding: gzip, deflate content-length: 107 Connection: keep-alive cache-control: no-cache { "firstName": "Marcos", "lastName": "Silva", "email" : "marcos.henrique@toptal.com", "password" : "pass123" } ```
Stephen Lawal
My bad, i was actually the one who made a mistake, i apologize. it works fine now
Marcos Henrique da Silva
No worries, I'm glad that it is working now
Atul
"we need to create the schema in /users/models/users.model.js" I wish it was made more easy. Where exactly is this folder. Are we suppose to create this file javascript users.model.js or what? Sorry I lost the blog in "Creating the User Module" section
Marcos Henrique da Silva
Hi Atul, thanks for the message. After downloading the project at the git link, you should find a folder called users containing a folder called models and inside the users.model.js file. the link to the file is here: https://github.com/makinhs/rest-api-tutorial/blob/master/users/models/users.model.js I would recommend to download the git project to follow the article. The project already has all the required source files. Just make sure to have node and mongodb installed on your machine. Best regards,
Vinicius Gati
Obrigado Marcos me ajudou pra caramba.
Marcos Henrique da Silva
Fico feliz em saber que te ajudou :)
raniel garcia
Hi, Thank you for this tutorial, but I have a little problem with refreshing the token, I always get the Invalid refresh token error, Can you help me to figure out on how can I use the /auth/refresh ?
raniel garcia
Sorry but I am new with NodeJS
Marcos Henrique da Silva
Hi there, Can you please share your complete request to have the refresh token error? thanks,
Rahul Shetty
This post is not for beginners.
Zangetsu Ichirin
Yeah, right, that way you can place a backdoor and whatever you want in your library :') People should be using their own encryption methods, that way there won't be a big surprise the day we realize 99% of hashed passwords were hacked because some genius found a loophole in the algorithm used to hash them.
Marcos Henrique da Silva
Hi Rahul, thanks for the feedback. Unfortunately was a bit difficult to put all information for beginners. For my next articles i will try to guide at least to where a beginner should look to start learning. Best regards,
Rahul Shetty
I didn't mean to say it's not helpful. It's a gem but beginners might feel a bit lost. Thanks for the efforts you have put in summing up this post. Would be eagerly waiting for your next post. Also, if you could, please let us know the path to learning node.js and the libraries that compliments node.js and is being extensively used by the community. For example, I got to know that most of the node.js devs use typescript with node.js
Kenneth Marshall
Great post! How would you handle all REST operations for a list? e.g. I want to get/delete/patch users for userids:[12,134,532,600,765,890,900]
Marcos Henrique da Silva
Hi Kenneth, Generally speaking we can use bulk operations for this. Sometimes is very trick to try to be 100% purist of a REST implementation. The most consistent that you can be, the better. The main idea is to always think of how easy your API will be to use and maintaining consistency. For all bulk operations your could add a Header pattern to your api like: X-Action: bulk X-Action: single When you pass bulk, your API should handle the request as a List and when it receives a single, then to deal as a single resource. 1) For a get method you could add a query filter if the list is not big enough to add as a query and you can implement it in several ways: 1.1) Example: get to /users?id=12,134,532,600,765,890,900 1.2) Example: get to /users?id=12+134+532+600+765+890+900 1.3) Example: get to /users?id=12|134|532|600|765|890|900 2) For a delete, it might be tricky. One approach that could work for your scenario will depends on your users information. If your users have a field called, for example, 'isDeleted', then you could arrange to send a patch method with your usersList. 2.1) use a PATCH to /users and send a list with all users information and all with the isDeleted: true flag 2.2) A different approach could be to use a list with op field called 'remove' and with the resource url to remove 2.2.2) Patch to /users Headers: X-Action: bulk body: [{ "op": "remove", "path": "/users/12" }, ... { "op": "remove", "path": "/users/900" }, ] 3) For both PATCH and PUT, if you add the bulk value at the X-Action header, then you could deal almost the same as you would do to a single resource, but dealing with the Array. I tried to make it short, but is a topic that could even worth a new article. I hope that you can get a general idea of how to start approaching for your list operations. Best regards,
Jorge Nunes
Hi, Marcos I can't create users. I used postman to fill in the first, last name, email and pass, but red.body is {} in line 18 users.controllers.js let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be one of type string, Buffer, TypedArray, or DataView. Received type undefined at Hmac.update (internal/crypto/hash.js:69:11) at exports.insert (/users/controllers/users.controller.js:18:50)
Saurabh Dang
Hey, Just a questions, why and where you set the permission level between 1 to 7?
Rodrigo Graça
seems @zangetsuichirin:disqus need to learn a bit more about security..... I'd recommend to listen to: Security Now Podcast.
Marcos Henrique da Silva
Hi Saurabh, thanks for asking. When you insert a new user, the code sets a permissionLevel of 1. You should code by yourself the requirement to where change the permissionLevel. As an admin perspective, you should add an endpoint that only admins can use in order to change an user permission level like a put to /users/:userId/permissionLevel/:permissionLevel. The why these numbers goes to the Bitwise AND (&) operator, which allows you to basically add several layers of permissions that goes from 1, 2, 4, ... 128, ... and on and the user can have a combination of these permissions as a sum of it. A 7 permission would allow the user to have 1 + 2 + 4 = 7. In that case, you can define your rules as 1: free plan 2: can edit some things 4: can invite people then an user who has 7 would have all free plan, able to edit things and invite people, and an user with 5 would only be able to edit things and to belong as a free plan. The deep explanation would be quite to big to put it here but I hope that it helped you. Best regards,
Marcos Henrique da Silva
Hi Jorge, line 18 of users.controllers.js doesn't belong to the insert method. The create user method should be between line 4 to 13 (https://github.com/makinhs/rest-api-tutorial/blob/master/users/controllers/users.controller.js) Please make sure that you are doing a request as a POST to users filling up the body with the firstName, lastName, email and password and make sure that the header has the application/json. Even after that you will still facing issues, please add the complete request that you are doing via Postman as a reply here. Best regards,
Roger Snowden
Why does this article not have a date?
Marcos Henrique da Silva
Hi Roger, thanks for the comment. The project and article were built in may of 2018. I unfortunately don't have a proper answer of why the article doesn't have a date. The dependencies are updated at the github repository and its still good to go for newer versions of node. You might be able to use the import features from the latest ecma if you might wish. By the time that I've made the article I didn't decided to make the project in TypeScript and I am willing to create a new and updated article using the latest standards that people are using for new back-end projects using NodeJS. If you have any suggestions or questions please let me know. Thanks in advance,
Tobias Pa
how can we serve this api with ssl?
Marcos Henrique da Silva
Hi Tobias, that will depend on where you are hosting it. If you are using Digital Ocean, for example, you can install apache2, then configure the domain that will serve the API, then you can use a free tool like Let's Encrypt to install the SSL certificate. If you are serving the API at aws, they can provide a SSL for your API after that you configure the domain. Hope that this information can make a good starting point for your search, Best regards,
Tobias Pa
i host on my own server with apache and lets encrypt... but the api give no response if i use https...with http it works nice certificate works great if i use a normal html file... i think it depends on the port right?
Marcos Henrique da Silva
Usually you need to allow 443 port at your firewall. Please try that and let me know if it worked. After you can configure your http api to automatically redirect to https best regards,
Saud Chougle
hi da silva, i like your blogpost image can i use it in my post
Terry Piercson
Hi Marcos, Thank you SO much for the tutorial. I am running into a few issues, however. I set this project up using docker running on port 3600 - code is untouched. - My delete function is returning a 401 unauthorized no matter what I do. All the other functions work. How can I drill into the root cause of the issue? I.e. I am John with permission level 7 deleting a newly created Sarah and returns a 401 unauthorized. I have John's bearer token included and even tried replacing it with Sarah's just to test it. - Where should I include error logging? - I am not familiar with docker. Where would I find the error logs in the docker console? How?
Terry Piercson
Hi Marcos, Thank you so much! Can you shed some light on how we can use the refresh token to keep the users sessions going until logout? Thank you!
Marcos Henrique da Silva
Hi Saud, the images are property of Toptal, I am not sure that you can use it them without their acceptance. Please try to contact Toptal directly
Marcos Henrique da Silva
Hi Terry, at your frontend application you can have a service which will manage to send a refresh token request in the background in order to get a new token without the user to see it happening. The way you will create this service will depend on the stack that you are using. And the logic could go for each X minutes to retrieve a new one as an example. Best regards,
Dave Geez
It seems like you have to already know a lot about Node.js and MongoDB and Mongoose and Express to make this work... there just seems to be a lot of missing glue, and I've only dabbled in Node. Maybe this is for experts that just haven't built an API yet.
Talha Meer
I want to create public apis for my project (like twitter, facebook, google provides public apis). Can you guide me how do I can achieve?
jonathan hill
hello marcos, I appreciate this tutorial is a bit old now but thought i would give it a go anyway. Having tried to get it working using the step-by-step approach I wasn't having any luck so I decided to pull repo in it's entirety and see if I could get a response with a view to reverse engineering it to figure out what was going on. I run node index.js and then make a post request with the raw json for the user in the tutorial above. The url is http://localhost:3600/users. The request in postman hangs and i get no response. It literally says 'Sending request...' and doesn't do anything. Below is the http request taken from postman: POST /users HTTP/1.1 Host: localhost:3600 : Content-Type: application/json { "firstName" : "Marcos", "lastName" : "Silva", "email" : "marcos.henrique@toptal.com", "password" : "s3cr3tp4sswo4rd" } Any ideas what the problem may be? Thanks
Marcos Henrique da Silva
Hi Talha Meer, I will be happy to help in any questions you might have
Marcos Henrique da Silva
Hi Dave, the tutorial was made for who are already used to the basics of Node.JS that would like to speed up an API development using Express.JS If you would be keen to share all the missing points for a beginner it would be my pleasure to update or create a new article focusing on the basics Best regards,
Marcos Henrique da Silva
Hi Jonathan Hill, steps that I am doing to reproduce what you are writing: 1. git clone 2. npm install 3. docker-compose build 4. docker-compose up 5. open postman 5.1) Url to: localhost:3600/users using POST as a method 5.1.1) Adding headers: Content-Type: application/json 5.2) body: { "firstName" : "Marcos", "lastName" : "Silva", "email" : "marcos.henrique@toptal.com", "password" : "s3cr3tp4sswo4rd" } 5.3) received response: { "id": "5e613d8bfa0a950011ae2ad5" } ``` curl --location --request POST 'localhost:3600/users' \ --header 'Content-Type: application/json' \ --data-raw '{ "firstName" : "Marcos", "lastName" : "Silva", "email" : "marcos.henrique@toptal.com", "password" : "s3cr3tp4sswo4rd" }' ``` Please let me know if that works for you. Also, there is a new github updated (2020) project in my github account with Typescript that you might want to try: https://github.com/makinhs/expressjs-api-tutorial Best regards,
Jeff Davidson
What is the publishing date of this article please?
Dave Geez
You can already do that without me needing to point them out.
Marcos Henrique da Silva
Hi Jeff, it was published in the mid of 2018. Although the theory and topics here didn't changed much, you can see an updated code using Typescript and updated dependencies on my other github project here: https://github.com/makinhs/expressjs-api-tutorial/ Best regards,
Jeff Davidson
Thanks for the reply Marcos and thank for the great tutorial
Marcos Henrique da Silva
Hi Sebastiano, thanks for your comments. It was a challenge to try to abstract several topics to put into one single article a tutorial for how to build a Rest API using ExpressJS. The Mongoose wasn't explained to avoid to put more 'confusing' pieces into the article. The main idea was to git clone/fork the project itself and run it and follow the article. If you would run the project using docker then you would not need to care much about Mongoose at this moment. Also, you can replace Mongoose to any other ORM/ODM or pure SQL if you might want/need. For next tutorials I will think about using a curl to copy and paste to easy it up the process of testing meanwhile reading. By the time that I wrote this article it was a good idea to not put in pure text the requests for copying and pasting. Thanks again for your feedback, Best regards
Haoting Liu
not so recommended, article never really breaks each blocks of code down and explains them line by line, hard to understand
Marcos Henrique da Silva
Hello Haoting Liu, Thanks for the reply, This article was aimed to developers who had a bit of pre-knowledge on NodeJS and in order to maintain as short as possible we avoided to write so much details on the code. We will try to improve on the next ones. Best regards,
Sudarshan Kj
Hi Marcos! I must say that this article is very well written and to the point. However would you mind explaining how refresh tokens would work since although there is a route defined for 'auth/refresh', it does not make any use of the function 'refresh_token' defined in 'authorization.controller.js' -kJ
Marcos Henrique da Silva
Hi Sudarshan, sorry for the late reply. When you pass the refresh_token via body at the auth/refresh token there are 2 middlewares who uses it. at the verifyRefreshBodyField I just check if you used the refresh_token as a body field then: at the validRefreshNeeded: we use the variable here: let b = new Buffer(req.body.refresh_token, 'base64'); then: let refresh_token = b.toString(); let hash = crypto.createHmac('sha512', req.jwt.refreshKey).update(req.jwt.userId + secret).digest("base64"); this is the part that that you will be consuming the refresh_token properly. I would also recommend you to try using a consolidate approach such as http://www.passportjs.org/packages/passport-jwt/ that would do all of what I wrote in their library without you to be concerned about it. Cheers,
Sudarshan Kj
Thanks for your reply Marcos!
Sanjay Yadav
Awesome post on securing API . Please share more on securing jwt in react UI .
vipul
<code>app.patch('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.patchById ]); </code> I understood every part of code but this really confusing where list of function passed in [ ] bracket . I am new to express js. Sir ,Can you elaborate this I was visualizing above code to this <code>app.patch('/users/:userId', fun(req, res, next) { ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.patchById } );</code>
Marcos Henrique da Silva
Hi vipul, inside of the array [] you should be using a function that receives request, response and next. In this case, until you arrive to patchById you should be calling the next function in the previous passed function in the case that it matches your condition. The way you wrote cannot work because of syntax issues. The correct way should be app.patch('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.patchById ] ); If I am not mistaken, with express 4+ you can also remove the array brackets [] and it would look like: app.patch('/users/:userId', ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.patchById ); In overall, keep in mind that each function there will be called and receive the request, response and next that you can use to manipulate the request. Hope it get easier to understand, Best regards,
Marcos Henrique da Silva
Hello Sanjay, I had write a short explanation about jwt and react here: https://medium.com/@makinhs/configuring-a-react-app-to-handle-authentication-without-redux-with-hooks-4424e9c30d73 Hope it helps in your case
siok meng
Hi, why the list and removeById you return Promise, and others methods just direct return?
Andrey Dergachev
Very straight and pretty: A little tip : export interface CRUD<TKey,T> { list: (limit: number, page: number) => Promise<T[]>, create: (resource: T) => Promise<T>, updateById: ( resource: T) => Promise<string>, readById: (resourceId: TKey) => Promise<T>, deleteById: (resourceId: TKey) => Promise<string>, // patchById: (resourceId: any) => Promise<string>, }
comments powered by Disqus