Handling Best Practices with Express

I am writing a website that implements a user management system, and I wonder what best practices regarding form processing should I consider.

Performance, security, SEO and user experience are important to me. When I was working on this, I came across a couple of questions, and I did not find the complete node / express code snippet where I could figure out all my questions below.

Use case: Someone is about to update the birthday of their profile. Right now I am making a POST request to the same URL to process the form on this page, and the POST request will respond with a 302 redirect to the same URL.

General questions about form processing:

  • Should I execute a POST + 302 request to process the form, or rather something like an AJAX request?
  • How do I handle invalid FORM requests (for example, an invalid login or email address is already in use during registration)?

Express specific questions about form processing:

  1. I suppose, before inserting anything into my database, I need to sanitize and check all the form fields on the server side. How do you do this?

  2. I read something about CSRF, but I never implemented CSRF protection. I would be glad to see that in the code snippet too

  3. Do I need to take care of any other possible vulnerabilities when processing forms with Express?

HTML / Pug Example:

form#profile(method='POST', action='/settings/profile') input#profile-real-name.validate(type='text', name='profileRealName', value=profile.name) label(for='profile-real-name') Name textarea#profile-bio.materialize-textarea(placeholder='Tell a little about yourself', name='profileBio') | #{profile.bio} label(for='profile-bio') About input#profile-url.validate(type='url', name='profileUrl', value=profile.bio) label(for='profile-url') URL input#profile-location.validate(type='text', name='profileLocation', value=profile.location) label(for='profile-location') Location .form-action-buttons.right-align a.btn.grey(href='' onclick='resetForm()') Reset button.btn.waves-effect.waves-light(type='submit') 

Example route handlers:

 router.get('/settings/profile', isLoggedIn, profile) router.post('/settings/profile', isLoggedIn, updateProfile) function profile(req, res) { res.render('user/profile', { title: 'Profile', profile: req.user.profile }) } function updateProfile(req, res) { var userId = req.user._id var form = req.body var profile = { name: form.profileRealName, bio: form.profileBio, url: form.profileUrl, location: form.profileLocation } // Insert into DB } 

Note. The full code snippet, which deals with all the advanced forms processing methods adapted to this example, is highly appreciated. I am fine using publicly available express middleware.

+5
source share
3 answers

Should I execute a POST + 302 request to process the form, or rather something like an AJAX request?

No , the best practice for a good user experience since 2004 or so (mainly since launching gmail) has been submitting the form via AJAX, and not to full-text POST downloads. In particular, error handling using AJAX is less likely to leave your user on the browser error page with errors, and then remove the problems using the back button. AJAX in this case should send an HTTP PATCH request as the most semantically correct, but POST or PUT will also do the job.

How do I handle invalid FORM requests (for example, an invalid login or email address is already in use during registration)?

Incorrect user input should lead to a response to the HTTP 400 Bad Request status code with detailed information about errors (errors) in the body of the JSON response (the format depends on each application, but either a general message or field errors are common topics)

For email that is already in use, I use the 409 Conflict HTTP status code 409 Conflict as a more specific flavor of the general request payload.

I assume that before inserting anything into my database, I need to sanitize and check all form fields on the server side. How do you do this?

That's right. There are many tools. Usually I define a schema for a valid request in JSON Schema and use a library from npm to check such as is-my-json-valid or ajv . In particular, I recommend being as strict as possible: rejecting invalid or coercive types, if necessary, removing unexpected properties, using small but reasonable string length restrictions and strict regular expression patterns for strings whenever you can, and, of course, make sure that your BB library property prevents injection.

I read something about CSRF, but I never implemented CSRF protection.

OWSAP Node Goat Project CSRF Exercise is a good place to start with a vulnerable application, understand and use the vulnerability, then implement the fix (in this case, with simple integration of express.csrf() .

Do I need to take care of all other possible vulnerabilities when processing forms with Express?

Yes Application developers must understand and actively protect the code. There is a lot of material for this, but special attention should be paid when user input takes part in database queries, spawning a subprocess, or writing back to HTML. Solid query libraries and template engines will handle most of the work here, you just need to know about the mechanism and potential places that malicious user input can penetrate (for example, image file names, etc.).

+2
source

Of course, I am not an express expert, but I think I can answer at least to # 1:

You should follow the Post / Redirect / Get web development pattern to prevent duplicate forms. I heard that 303-redirect is the correct http status code to redirect form submissions.

I process the forms using the POST route, and as soon as I finish, I start the 302 redirect.

Starting with number 3, I recommend looking at the express validator, which is well represented here: https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms . This is a middleware that allows you to check and misinform the following:

 req.checkBody('name', 'Invalid name').isAlpha(); req.checkBody('age', 'Invalid age').notEmpty().isInt(); req.sanitizeBody('name').escape(); 

I could not comment on the answer, although this is not a complete answer. Just thought it might help you.

+1
source

If user experience is what you think about, page redirection is strong. Providing a smooth flow for people visiting your site is important to prevent drops, and since the forms are not so pleasant to fill out, their use is basic. You do not want to reload your page, which might take some time to load only the error message. Once the form is valid and you have created the user's cookie, the redirect will be fine, although even if you can do something in the client application to prevent it, it’s out of scope.

As pointed out by Levent, you should check the express-validator , which is a more established solution for this kind of purpose.

 req.check('profileRealName', 'Bad name provided').notEmpty().isAlpha() req.check('profileLocation', 'Invalid location').optional().isAlpha(); req.getValidationResult().then(function (result) { if (result.isEmpty()) { return null } var errors = result.array() // [ // { param: "profileRealName", msg: "Bad name provided", value: ".." }, // { param: "profileLocation", msg: "Invalid location", value: ".." } // ] res.status(400).send(errors) }) .then(function () { // everything is fine! insert into the DB and respond.. }) 

Be that as it may, I can assume that you are using MongoDB. Given this, I would recommend using ODM like Mongoose . This will allow you to define models for your circuits and set limits directly on it, allowing the models to handle these redundant checks for you.

For example, a model for your user might be

 var User = new Schema({ name: { type: String, required: [true, 'Name required'] }, bio: { type: String, match: /[az]/ }, age: { type: Number, min: 18 }, // I don't know the kind of site you run ;) }) 

Using this pattern on your route will look like

 var user = new User({ name: form.profileRealName, bio: form.profileBio, url: form.profileUrl, location: form.profileLocation }) user.save(function (err) { // and you could grab the error here if it exists, and react accordingly }); 

As you can see, this provides a pretty cool api that you should read about in your docs if you want to know more.

About CRSF, you should install csurf , which has pretty good instructions and usage examples in their readme.

After that you did a very good job, I don’t think about doing without having to keep track of your critical dependencies if day 0 occurs, for example the one that was shown in 2015 with JWT, but this is still rare.

+1
source

All Articles