Server-side validation & mass assignment

Validate on the server

Lesson 17 of 20 · 13 min · Module 5: Server-side defences

Your form can check everything, and attackers can still send whatever they like straight to your server. The server must decide for itself what is valid, which fields may change, and what things cost.

Start this lesson →

Free. No installation: you write and run the code in your browser, and it is checked automatically.

By the end, you’ll be able to…

  • Validate every field of a request on the server
  • Accept only the fields you expect
  • Work out prices, totals and identities on the server

The idea

Treat a request body like any other untrusted input: check the type of every field, accept only the fields you expect, and work out anything important yourself. Copying the whole body into a record lets people set fields they should never touch, such as role or verified; this is called mass assignment. Prices, totals and the identity of the user must come from your own data and the session, never from the request.

const order = { ...body };      // copies role: 'admin' too
const { items, note } = body;   // reads only what you expect

01The request is not your form

Anyone can send a request with curl, a script or the browser’s developer tools, with any fields and any values. Your server only ever sees the request, never the form that may or may not have produced it.

02Allow-list the fields

Copying the body into a database record with {...body} lets people add role: 'admin' or verified: true. Read the fields you expect by name, and refuse the rest so mistakes are noticed.

const unexpected = Object.keys(body).find(key => !['items', 'note'].includes(key));

03Watch for object tricks

Keys such as __proto__ and constructor exist on every plain object. Look things up in a Map, or check Object.hasOwn(), so a request cannot reach built-in properties.

const prices = { 1: 400 };
console.log(prices['constructor'] !== undefined, new Map([[1, 400]]).get('constructor'));
Output
true undefined

Your turn

Rewrite createOrder(body, user). Return { ok: false, error } with the first matching message: Body must be an object. (not a plain object, or an array); Unexpected field: … for the first key other than items and note; Add between 1 and 20 items.; Unknown product. when an item’s id is not a whole number in PRODUCTS; Quantity must be a whole number from 1 to 10.; Note must be text of at most 200 characters. (the note is optional). Otherwise return { ok: true, order } where order has userId from user.id, lines with id, name, quantity and price from PRODUCTS, the total in cents, and note ('' when missing).

What the checks look for

  • A valid order is priced on the server
  • Prices sent by the client are ignored
  • Unexpected fields are refused
  • The user always comes from the session
  • Products and quantities are checked
  • The body and the item list must make sense
  • Notes are limited text
  • The pretend API uses your function
Try it in the editor →

Take this with you

The server checks every field, accepts only known ones, and works out what matters itself.

Go a little further

Add a coupon field that accepts only codes from a server-side list, and allow each code once per user.

Learn Web Security Foundations for free

20 short lessons with real code and instant checks.

See the whole course →