Cannot Read Property of Undefined Postman Nodejs

Application programming interfaces (APIs) are everywhere. They enable software to communicate with other pieces of software—internal or external—consistently, which is a primal 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 similar social media logins, credit card payments, and behavior tracking. The de facto standard they utilize for this is called REpresentational State Transfer (REST).

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

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

Developers post-obit this tutorial should also exist comfortable with the last (or control prompt).

Notation: Nosotros won't comprehend a front-finish codebase here, but the fact that our back finish is written in JavaScript makes information technology convenient to share lawmaking—object models, for case—throughout the full stack.

Anatomy of a Residuum API

Remainder APIs are used to admission and manipulate data using a common set of stateless operations. These operations are integral to the HTTP protocol and correspond essential create, read, update, and delete (CRUD) functionality, although non in a clean 1-to-i manner:

  • Postal service (create a resource or generally provide data)
  • Go (think 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 accost, we tin can build a REST API by creating an endpoint for each performance. And past implementing the pattern, we will take a stable and easily understandable foundation enabling the states to evolve the code rapidly and maintain it afterward. As mentioned earlier, the same foundation volition be used to integrate third-political party features, well-nigh of which likewise employ 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 mutual (and very applied) Remainder API for a resources 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 volition create the following operations for that resource:

  • POST on the endpoint /users (create a new user)
  • Go 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 volition likewise exist using JSON web tokens (JWTs) for access tokens. To that terminate, we volition create another resource called auth that will await a user'due south email and password and, in render, will generate the token used for authentication on certain operations. (Dejan Milosevic's great article on JWT for secure Residuum applications in Java goes into further item most this; the principles are the same.)

Rest API Tutorial Setup

First of all, make sure that you lot take the latest Node.js version installed. For this article, I'll be using version xiv.9.0; information technology may likewise work on older versions.

Adjacent, make certain that you have MongoDB installed. We won't explicate the specifics of Mongoose and MongoDB that are used here, but to go the nuts running, simply first 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, nosotros'll need to interact with MongoDB directly rather than via our Node.js lawmaking.

Notation: With MongoDB, in that location's no need to create a specific database similar there might exist in some RDBMS scenarios. The get-go 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 projection. It's intended instead that you clone the companion repo and merely follow along the highlights as you read through—merely you can too copy in specific files and snippets from the repo as needed, if y'all adopt.

Navigate to the resulting residual-api-tutorial/ binder in your terminal. Y'all'll see that our projection contains 3 module folders:

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

Now, run npm install (or yarn if y'all take it.)

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

Creating the User Module

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

First, nosotros 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 utilize this model to implement all the Grime operations that nosotros want within our Express endpoints.

Let'southward first with the "create user" operation by defining the road in users/routes.config.js:

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

This is pulled into our Limited app in the master 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 table salt = crypto.randomBytes(xvi).toString('base64');    let hash = crypto.createHmac('sha512',salt)                                     .update(req.torso.password)                                     .digest("base64");    req.trunk.password = salt + "$" + hash;    req.body.permissionLevel = 1;    UserModel.createUser(req.trunk)        .then((effect) => {            res.condition(201).ship({id: result._id});        }); };                  

At this betoken, nosotros can exam our Mongoose model by running the server (npm outset) and sending a POST request to /users with some JSON information:

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

There are several tools yous can utilise for this. Insomnia (covered below) and Postman are pop GUI tools, and curl is a mutual CLI pick. Yous can fifty-fifty just use JavaScript, e.g., from your browser's congenital-in evolution tools console:

          fetch('http://localhost:3600/users', {         method: 'POST',         headers: {             "Content-blazon": "application/json"         },         body: JSON.stringify({             "firstName": "Marcos",             "lastName": "Silva",             "e-mail": "marcos.henrique@toptal.com",             "countersign": "s3cr3tp4sswo4rd"         })     })     .and then(part(response) {         return response.json();     })     .then(role(information) {         console.log('Request succeeded with JSON response', data);     })     .grab(function(error) {         console.log('Request failed', error);     });                  

At this betoken, the result of a valid post will be only 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 ready, now we need to see if the user exists. For that, we are going to implement the "get user past id" feature for the following endpoint: users/:userId.

Showtime, we create a road 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(upshot);    }); };                  

And finally, add together the findById method to the model in /users/models/users.model.js:

          exports.findById = (id) => {     return User.findById(id).and then((consequence) => {         result = result.toJSON();         delete result._id;         delete outcome.__v;         render upshot;     }); };                  

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 tin can see the hashed password. For this tutorial, we are showing the password, only the obvious all-time practise is never to reveal the password, even if information technology has been hashed. Another thing we tin can see is the permissionLevel, which we volition employ to handle the user permissions later on.

Repeating the design laid out higher up, we tin now add the functionality to update the user. We will apply the PATCH functioning since it will enable us to transport only the fields we desire to change. The road will, therefore, be PATCH to /users/:userid, and we'll exist sending any fields we desire to alter. We will as well need to implement some extra validation since changes should be restricted to the user in question or an admin, and merely an admin should exist able to change the permissionLevel. We'll skip that for now and get back to information technology once we implement the auth module. For now, our controller will look like this:

          exports.patchById = (req, res) => {    if (req.body.password){        allow table salt = crypto.randomBytes(16).toString('base64');        let hash = crypto.createHmac('sha512', salt).update(req.body.password).assimilate("base64");        req.body.countersign = common 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 torso to indicate that the request was successful.

And nosotros'll demand to add the patchUser method to the model:

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

The user list will be implemented equally a GET at /users/ past the post-obit controller:

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

The respective model method will be:

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

The resulting list response will accept the following structure:

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

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

Our controller for deletion will be:

          exports.removeById = (req, res) => {    UserModel.removeById(req.params.userId)        .and so((effect)=>{            res.status(204).send({});        }); };                  

Same as before, the controller volition render HTTP code 204 and no content body as confirmation.

The respective model method should wait like this:

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

We now take all the necessary operations for manipulating the user resource, and we're done with the user controller. The master idea of this code is to give you the core concepts of using the Remainder blueprint. We'll need to render to this code to implement some validations and permissions to it, only first, nosotros'll need to start building our security. Let's create the auth module.

Creating the Auth Module

Earlier 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. Nosotros volition generate a JWT in response to the user providing a valid email and password. JWT is a remarkable JSON web token that you can employ to accept the user deeply 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 unmarried token per login.

Offset, nosotros volition create an endpoint for POST requests to /auth resource. The asking trunk will contain the user e-mail and countersign:

          {    "email" : "marcos.henrique2@toptal.com",    "countersign" : "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.condition(404).transport({});            }else{                let passwordFields = user[0].password.split('$');                let common salt = passwordFields[0];                let hash = crypto.createHmac('sha512', salt).update(req.body.password).assimilate("base64");                if (hash === passwordFields[i]) {                    req.trunk = {                        userId: user[0]._id,                        email: user[0].email,                        permissionLevel: user[0].permissionLevel,                        provider: 'electronic mail',                        proper noun: user[0].firstName + ' ' + user[0].lastName,                    };                    render adjacent();                } else {                    return res.status(400).send({errors: ['Invalid e-mail or password']});                }            }        }); };                  

Having washed that, we can move on to the controller and generate the JWT:

          exports.login = (req, res) => {    try {        allow refreshId = req.body.userId + jwtSecret;        permit salt = crypto.randomBytes(sixteen).toString('base64');        let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64");        req.trunk.refreshKey = salt;        let token = jwt.sign(req.trunk, 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).ship({errors: err});    } };                  

Even though we won't be refreshing the token in this tutorial, the controller has been ready to enable such generation to make information technology easier to implement it in subsequent development.

All we need now is to create the road and invoke the advisable middleware in /say-so/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, nosotros can use it inside the Authorization header using the course Bearer ACCESS_TOKEN.

Creating Permissions and Validations Middleware

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

  • Public for creating users (registration process). Nosotros volition not use JWT for this scenario.
  • Private for the logged-in user and for admins to update that user.
  • Private for admin just 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 every bit simple as:

          exports.validJWTNeeded = (req, res, next) => {     if (req.headers['authorisation']) {         try {             permit authorization = req.headers['authorization'].split up(' ');             if (authorization[0] !== 'Bearer') {                 return res.status(401).send();             } else {                 req.jwt = jwt.verify(authorization[1], hugger-mugger);                 return adjacent();             }         } catch (err) {             render res.status(403).ship();         }     } else {         render res.status(401).transport();     } };                  

We volition use HTTP fault 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

Nosotros can use the bitwise AND operator (bitmasking) to control the permissions. If nosotros set each required permission every bit a ability of ii, we can care for each flake of the 32-chip integer as a single permission. An admin can then have all permissions past setting their permission value to 2147483647. That user could then have admission to any route. As some other example, a user whose permission value was ready to 7 would take permissions to the roles marked with bits for values i, two, and iv (two to the power of 0, 1, and 2).

The middleware for that would look similar this:

          exports.minimumPermissionLevelRequired = (required_permission_level) => {    render (req, res, side by side) => {        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 {            render res.status(403).send();        }    }; };                  

The middleware is generic. If the user permission level and the required permission level coincide in at least 1 bit, the result volition be greater than naught, and we can let the action go along; otherwise, the HTTP code 403 will exist returned.

Now, we need to add the hallmark 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(Complimentary),    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 skilful free version. The all-time exercise is, of course, to include code tests and implement proper fault reporting in the project, but third-party Remainder clients are great for testing and implementing third-party solutions when mistake reporting and debugging the service is not available. We'll be using it here to play the office 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 at present 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 (recall the space), and add together information technology to the request headers under Authorization:

Setting up the headers to transfer contain the authenticating JWT

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

Response listing the data for the indicated user

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

Let's try to become a listing 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 admission this endpoint. Nosotros will need to alter the permissionLevel of our user from 1 to 7 (or even 5 would do, since our free and paid permissions levels are represented equally ane and 4, respectively.) We can exercise this manually in MongoDB, at its interactive prompt, similar 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

Side by side, 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

Nosotros expect a 204 response as confirmation of a successful operation, just we can request the user in one case once again to verify.

Response after successful change

Finally, nosotros need to delete the user. We'll need to create a new user every bit described higher up (don't forget to note the user ID) and make sure that nosotros have the appropriate JWT for an admin user. The new user will need their permissions ready to 2053 (that'south 2048—ADMIN—plus our earlier five) to exist able to also perform the delete operation. With that washed and a new JWT generated, nosotros'll have to update our Dominance asking header:

Request setup for deleting a user

Sending a DELETE asking to /users/:userId, nosotros should get a 204 response every bit confirmation. Nosotros tin can, again, verify by requesting /users/ to listing all existing users.

Next Steps for Your REST API

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

  • Implement proper validations (e.thousand., 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
  • Foreclose disclosure of sensitive information (e.thou., hashed passwords)
  • Move the JWT hugger-mugger from common/config/env.config.js to an off-repo, non-surroundings-based secret distribution mechanism

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

For those of yous who might be interested, there is at present also a TypeScript version of the project bachelor.

pattersonyous1990.blogspot.com

Source: https://www.toptal.com/nodejs/secure-rest-api-in-nodejs

0 Response to "Cannot Read Property of Undefined Postman Nodejs"

Enregistrer un commentaire

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel