A web application does not need to be completely broken to become a security problem.
Sometimes, a single exposed API endpoint, a poorly protected session, an unsafe database query, or an overlooked authorization check can be enough to give an attacker access to data or functionality they should never reach.
Modern frameworks provide many useful security features, but frameworks cannot automatically fix insecure application logic. Developers still have to make decisions about authentication, authorization, input handling, secrets, dependencies, and data protection.
Security should therefore be treated as part of the development process, not as something added after an application is finished.
This article covers five common security mistakes that can put web applications at risk and explains practical ways developers can avoid them.
1. Trusting User Input
One of the oldest security principles in web development is still one of the most important:
Never trust data coming from the client.
User input can come from many places:
- Form fields
- Query parameters
- URL paths
- HTTP headers
- Cookies
- JSON request bodies
- File uploads
- API requests
A common mistake is assuming that because the frontend validates a value, the backend can trust it.
It cannot.
Frontend validation is useful for user experience, but an attacker can bypass the frontend completely and send requests directly to your API.
For example, imagine an API that expects:
{
"age": 25
}
The frontend may restrict the field to numbers between 1 and 100. However, an attacker can send something completely different directly to the server.
The backend must validate the input independently.
Why This Matters
Poor input handling can contribute to vulnerabilities such as:
- SQL injection
- Cross-site scripting (XSS)
- Command injection
- Path traversal
- Malicious file uploads
- Unexpected application behavior
The exact vulnerability depends on how the application processes the input.
How to Fix It
Use server-side validation and define what your application actually expects.
For example:
const schema = {
username: "string",
age: "integer",
email: "valid-email"
};
In a real application, use a well-maintained validation library rather than writing complex validation logic from scratch.
Also validate:
- Type
- Length
- Format
- Allowed values
- Required fields
- File type and size
- Business rules
Validation should happen as close to the application's trust boundary as possible.
Don't Rely Only on Sanitization
Developers sometimes try to solve security problems by removing suspicious characters.
https://goodoff.co/
That is not a universal security strategy.
The better approach is to use the correct protection for the context.
For example:
- Use parameterized queries for SQL
- Use context-aware output encoding for HTML
- Use safe APIs for operating-system commands
- Validate file uploads
- Apply authorization before performing sensitive operations
Security controls should match the actual operation being performed.
2. Confusing Authentication With Authorization
A user successfully logging in does not mean they are allowed to access everything.
This distinction is fundamental.
Authentication answers:
Who are you?
Authorization answers:
What are you allowed to do?
Consider an application where users can access their profile through:
GET /api/users/123/profile
A developer might verify that the requester is logged in and then return the profile.
But what happens if user 456 requests:
GET /api/users/123/profile
If the server only checks whether the requester is authenticated, user 456 may receive user 123's information.
The problem is authorization.
The Principle of Least Privilege
Users should receive only the permissions they need.
For example:
Regular user
↓
View own profile
↓
Edit own profile
Administrator
↓
Manage users
↓
View administrative data
↓
Change system settings
The server should enforce these rules.
Never assume that hiding a button in the frontend is a security control.
A user can still call the underlying API manually.
Example
Instead of relying on:
if (user.isAdmin) {
showAdminButton();
}
the backend should independently check permissions before executing the operation.
Conceptually:
if (!currentUser.isAdmin) {
return response.status(403).json({
error: "Forbidden"
});
}
The frontend can improve the experience, but the backend must enforce authorization.
A Useful Rule
Whenever an API performs an operation involving another user's data, ask:
"Why is this user allowed to do this?"
If the answer is unclear, the authorization model probably needs another look.
3. Storing Secrets in the Codebase
API keys, database passwords, private tokens, and other credentials should not be hardcoded into application source code.
A mistake like this can create a serious security problem:
const API_KEY = "my-secret-production-key";
Even if the repository is private, secrets can accidentally leak through:
- Git history
- Logs
- Screenshots
- Build artifacts
- Error messages
- Shared repositories
- CI/CD systems
- Developer machines
Once a secret has been committed to a repository, deleting the line later does not necessarily remove it from Git history.
Use Environment-Based Configuration
A common pattern is:
const apiKey = process.env.API_KEY;
The actual secret can then be supplied through the deployment environment or an appropriate secrets-management system.
For production systems, organizations may use dedicated secret-management platforms rather than storing credentials directly in configuration files.
Rotate Compromised Credentials
Another important principle is that secrets should be replaceable.
If a production API key is accidentally exposed, simply removing it from the source code is not enough.
The exposed credential should be:
- Revoked or disabled
- Replaced
- Removed from accessible history where appropriate
- Audited for unauthorized usage
Developers should also avoid logging sensitive credentials.
Never do this:
console.log("API key:", apiKey);
Logs often have a much wider audience and longer retention period than developers expect.
Add Secret Scanning to the Workflow
Modern development teams can use secret-scanning tools to detect credentials before they reach public repositories or production systems.
This turns secret protection from a manual habit into part of the development pipeline.
4. Using Weak Session and Authentication Security
Authentication is often treated as simply "adding login."
In reality, authentication involves much more:
- Password storage
- Sessions
- Cookies
- Tokens
- Password reset
- Multi-factor authentication
- Login attempts
- Account recovery
- Session expiration
A secure password system should never store users' passwords as plaintext.
Instead, passwords should be processed using a password hashing algorithm designed for this purpose, such as Argon2id, bcrypt, or another appropriately configured password hashing scheme.
Password Hashing Is Not Encryption
This distinction matters.
Encryption is designed to be reversible with the appropriate key.
Password hashing is designed to be computationally difficult to reverse.
A password database should therefore contain password hashes rather than plaintext passwords.
Protect Session Cookies
For browser-based authentication, session cookies should generally use security attributes such as:
HttpOnly
Secure
SameSite
HttpOnly helps prevent client-side JavaScript from directly reading the cookie.
Secure tells the browser to send the cookie only over HTTPS.
SameSite can help reduce certain cross-site request risks.
The exact configuration depends on the application's architecture and authentication flow.
Don't Build Authentication From Scratch Without a Reason
Authentication has many subtle security requirements.
Whenever possible, developers should use mature, well-reviewed authentication libraries and framework features rather than implementing cryptographic or session-management mechanisms themselves.
This does not eliminate the need for understanding security, but it reduces the number of security-sensitive components developers have to reinvent.
5. Ignoring Dependencies and Security Updates
Your application is not only the code you personally wrote.
Modern applications depend on frameworks, packages, libraries, operating-system components, containers, and third-party services.
That means a vulnerability in a dependency can become a vulnerability in your application.
For example, a project might contain hundreds of dependencies:
Application
├── Framework
├── Authentication library
├── Database driver
├── Image processor
├── HTTP client
└── Other packages
Any of these components may eventually receive a security advisory.
Why Developers Miss Dependency Vulnerabilities
A common workflow is:
npm install
and then leaving the dependency versions untouched for months or years.
This creates maintenance risk.
Developers should regularly review:
- Dependency versions
- Security advisories
- Transitive dependencies
- Framework updates
- Runtime versions
- Container images
Automated dependency scanning can help identify known vulnerabilities.
But Don't Blindly Update Everything
There is another mistake: updating every dependency without testing.
Security updates matter, but updates can also introduce breaking changes.
A better process is:
Detect
↓
Review
↓
Update
↓
Test
↓
Deploy
↓
Monitor
Security maintenance should be part of normal software maintenance.
Security Is a Development Process
The five mistakes above have something in common.
None of them requires an advanced attack technique to understand.
They are mostly about development decisions:
- What input do we trust?
- Who is allowed to access this resource?
- Where are our secrets stored?
- How are sessions protected?
- Are our dependencies maintained?
That is why application security should not be treated as a final checklist before deployment.
It should be considered throughout the software development lifecycle.
A Practical Security Checklist for Developers
Before deploying a web application, review these areas:
Input
- Is every untrusted input validated on the server?
- Are database queries parameterized?
- Is output encoded appropriately?
- Are file uploads restricted?
Authentication
- Are passwords securely hashed?
- Are sessions protected?
- Are authentication cookies configured securely?
- Is account recovery protected?
Authorization
- Does every sensitive endpoint enforce permissions?
- Can users access another user's resources?
- Are administrative operations protected server-side?
Secrets
- Are API keys outside the source code?
- Are production credentials stored securely?
- Are secrets excluded from logs?
- Can compromised credentials be rotated?
Dependencies
- Are dependencies regularly updated?
- Are known vulnerabilities monitored?
- Are outdated frameworks and runtimes identified?
Monitoring
Security does not end after deployment.
Applications should also have appropriate monitoring and logging so that suspicious behavior can be detected and investigated.
However, logs should never expose passwords, tokens, private keys, or other sensitive information.
Security Should Be Designed, Not Added Later
One of the biggest misconceptions in web development is that security is a separate stage that happens after the application has been built.
In practice, security decisions are made throughout development.
When you design an API, you are making security decisions.
When you create a database query, you are making security decisions.
When you implement login, you are making security decisions.
When you choose a dependency, you are making security decisions.
When you decide which user can access a resource, you are making security decisions.
The earlier these decisions are considered, the easier they are to maintain.
A useful mindset is to assume that every request crossing your application's boundary is untrusted until your server has validated it and established that the requested action is permitted.
Final Thoughts
A secure web application is not created by adding one security library or running one vulnerability scanner.
Security comes from many smaller decisions working together.
Validate untrusted input.
Enforce authorization on the server.
Keep secrets out of the codebase.
Protect authentication and sessions.
Maintain your dependencies.
Most importantly, make security part of everyday development rather than something you think about only after an incident.
For developers, this mindset is increasingly important as applications become more connected, APIs become more complex, and software increasingly depends on third-party components.
The goal is not to write perfect code.
The goal is to build systems where common mistakes are harder to make, sensitive operations are properly protected, and security is considered before a problem reaches production.
References and Further Reading
For deeper technical guidance, developers should consult established security resources such as the OWASP Application Security Verification Standard (ASVS), OWASP Cheat Sheet Series, and NIST cybersecurity guidance.
These resources provide detailed recommendations for authentication, access control, input validation, session management, secrets, and other areas of application security.
Top comments (0)