Q1Why validate on the server as well, when the form values are already checked on the screen?
Security Points to Watch — Do Not Trust Input, Protect Secrets
This article is part of the IT Foundations course, which builds up from scratch the practical IT knowledge you need at a minimum for programming and vibe coding.
There are two basics: not trusting user input, and protecting secrets. Diagrams show how SQL injection and XSS happen.
This article covers two of the security points to watch.
One is not using the text a user typed as it is, and the other is not putting secret values where more people can read them.
- Why input validation is done on the server
- SQL injection, which happens when input is joined onto a statement, and placeholders
- XSS, prevented by escaping right before output to the screen
- Where you may keep secrets such as API keys and passwords
Once any of these happen after you publish, they cannot be undone, and data that has already been read cannot be taken back.
"No input validation" means using a value as it arrives
Input validation is checking that an incoming value is in the format, length, and range you expect before you use it.
You set a condition for each field: names up to 50 characters, star ratings from 1 to 5.
| Value that arrives | Condition set in advance | What the server does |
|---|---|---|
| Name field: Alice | Up to 50 characters → fits | Goes on to save it |
| Star rating: 7 | A number from 1 to 5 → does not fit | Rejects it without saving |
| Body: left empty | One character or more → does not fit | Rejects it without saving |
Only the first row meets the condition and goes on to be saved; the other two are not saved, and the server returns which part does not fit.
The check on the screen is run by JavaScript (a program that runs inside the browser) delivered to the user's device.
The user can turn it off, and they can also send data straight to the server without using a browser.
The check on the screen is there to prompt the user to fix the entry, and only the validation on the server decides whether a value may be used.
If you save without validation, values that do not meet the conditions stay in the database as they are.
Star ratings outside 1 to 5 throw off averages and sort order, and values that cannot be read as numbers stop the aggregation from running.
Fixing values that have already gone in means checking the remaining data one record at a time.
Check a value on the server before you use it
Input validation is looking at whether a value that has arrived meets the conditions you set, before you use it.
The check on the screen is there to have the user rewrite the entry on the spot and can be turned off before sending, so you check the same thing again on the server.
Join input onto a statement, and the characters typed run as a command
When it looks up saved reviews by name, the server assembles a statement in SQL (the language that passes commands to a database).
When the input text is joined onto that string, the whole thing is passed as a single string.
- SELECT * FROM reviews WHERE name =
- An instruction to pull the rows whose name matches from the reviews table
- This form is written in the code in advance
- Alice — an ordinary name
- ' OR '1'='1 — text containing symbols
- What the user typed goes into this position as it is
Once passed, the part written by the developer and the part from the name field are parts of one and the same string.
The database reads the single string it receives as a command, all of it together.
| Text entered in the name field | Statement produced by joining | What the database returns |
|---|---|---|
| Alice | name = 'Alice' | Only Alice's reviews |
| Alice' | name = 'Alice'' | The statement breaks and errors |
| ' OR '1'='1 | name = '' OR '1'='1' | Every review |
Input text being executed as a command instead of as a value is injection, and the kind that happens in a database is SQL injection.
The way of writing that prevents it is a placeholder: in the statement you mark only the position where the value goes with a symbol, and pass the value separately.
When they are passed separately, the database does not read the value as a command; it looks up that text as a name and ends with 0 rows.
Validation checks the format of a value, while a placeholder makes sure the value is treated as a value whatever its format.
They do different jobs, so you do both.
Do not join the statement and the input text into one string
When input text is joined onto a statement, even the symbols typed are read as part of the command.
Passing the command and the value separately means text containing symbols is treated as just a name, so you keep both: validation that checks the format, and the way of writing that treats the value as a value regardless of its format.
Without a change right before output, a post runs in another user's browser
The same thing happens where a post is put on the screen.
Input text being executed as JavaScript in another user's browser is XSS (cross-site scripting).
In both, the input is read as a command; only what reads it differs.
The fix on the output side is escaping: converting characters that carry a special meaning into a form that is displayed as plain characters.
The dividing point is not when it is saved, but right before it goes on the screen.
| Character with a special meaning in HTML | How it is written after replacement | Character seen on the screen |
|---|---|---|
| < | < | < |
| > | > | > |
| & | & | & |
| " | " | " |
| ' | ' | ' |
Replace right before output, and a post stays plain text
If you put a post on the screen as it is, whatever was mixed into it runs in another user's browser.
What stops this is the replacement made right before output, and because only the symbols are replaced, the text the reader sees does not change.
Stop secret values before more people can read them
The other point is where secrets are kept.
What you look at here is how many people can read the place you put them in.
- .env — the values of API keys and passwords
- Configuration files used only on your computer
- server.js — code that runs on the server
- .gitignore — the list of files not to record
- .env.example — a sample with only the names
- index.html — the review screen
- script.js — code that runs on the page
The further in the frame, the more people can read it: anyone can read script.js, and once server.js is recorded in Git, everyone you gave the code to can read it.
Writing .env in .gitignore (a file listing the names of the files Git is not to record) keeps the values out of the history, but
once they have been recorded they remain in the past history, so you regenerate the values themselves.
A value that has gone into a public repository can be read by anyone in the world.
- script.js — code delivered to the browser
- The end of the URL you call
- Error messages shown on the screen
- server.js — code recorded in Git
- .env once recorded — stays in the history
- Environment variables on the server — API keys and passwords go here
The only thing in the innermost ring is the environment variables on the server.
You do not write them in the places in the two outer rings, or in logs (the record of activity the server keeps), which are sometimes taken outside.
The my-app running inside the box on the right loads the value from the environment variables.
That value never goes into the two boxes on the left, so the number of people who can read it does not grow.
Where to keep it is decided by how many people can read it
Where you may keep a secret value is decided by how many people can read that place.
Anyone can read a file delivered to the browser, and a file recorded in Git reaches everyone you give the code to, so you write it in neither and put it in the environment variables on the server.
Knowledge Check
Answer each question one by one.
Q2What does the placeholder style of writing, which prevents SQL injection, actually do?
Q3Which is the correct place to keep the API key for an external service?