HRDK Logo

Security

Web Security and Authentication: A Developer's Guide

By Hardik YadavSecurity, API, Backend

Web Security and Authentication: A Developer's Guide

Quick answer: Web security for developers centers on a small set of recurring concerns: verifying who a user is (authentication), controlling what they're allowed to do (authorization), protecting data in transit and storage, and defending against a known set of common attacks (like XSS and SQL injection). Most real-world breaches come from skipping these basics, not from exotic attacks.

Authentication vs Authorization

These two terms get confused constantly, but they answer different questions:

| | Question it answers | Example | |---|---|---| | Authentication | Who are you? | Logging in with email/password | | Authorization | What are you allowed to do? | Only admins can delete users |

Full breakdown: Authentication vs Authorization

How JWT Authentication Works

JSON Web Tokens (JWT) are a common way to handle authentication in REST APIs:

const jwt = require("jsonwebtoken");

// After verifying email/password on login:
const token = jwt.sign(
  { userId: user._id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: "7d" }
);

res.json({ token });
// Middleware to protect routes
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) return res.status(401).json({ error: "No token provided" });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch {
    res.status(401).json({ error: "Invalid or expired token" });
  }
}

app.get("/api/profile", requireAuth, (req, res) => {
  res.json({ userId: req.user.userId });
});

The token is issued once at login and sent by the client on every subsequent request, letting the server verify identity without a database lookup on every call. Full guide: What Is JWT and How Does JWT Authentication Work?

Never Store Passwords in Plain Text

const bcrypt = require("bcrypt");

// When a user signs up
const hashedPassword = await bcrypt.hash(plainPassword, 10);
await User.create({ email, password: hashedPassword });

// When a user logs in
const isMatch = await bcrypt.compare(plainPassword, user.password);

Hashing is one-way — even if your database is ever exposed, attackers can't reverse a properly hashed password back to the original. Full guide: How to Store Passwords Safely

Common Vulnerabilities to Know

| Vulnerability | What it is | Basic defense | |---|---|---| | SQL Injection | Malicious input alters a database query | Parameterized queries / an ORM | | XSS (Cross-Site Scripting) | Malicious scripts injected into pages | Escape/sanitize user input before rendering | | CSRF (Cross-Site Request Forgery) | Tricking a logged-in user's browser into unwanted actions | CSRF tokens, SameSite cookies |

These map directly to entries in the OWASP Top 10, the industry-standard list of the most critical web security risks. Full breakdown: Common Web Security Vulnerabilities

Securing a REST API in Practice

app.post("/api/orders", requireAuth, async (req, res) => {
  const { productId, quantity } = req.body;

  if (!productId || typeof quantity !== "number" || quantity <= 0) {
    return res.status(400).json({ error: "Invalid order data" });
  }

  const order = await Order.create({
    userId: req.user.userId, // never trust a userId sent from the client
    productId,
    quantity,
  });

  res.status(201).json(order);
});

Note that userId comes from the verified token, not from the request body — trusting client-supplied identity fields is a common and dangerous mistake. Full guide: How to Secure a REST API

Common Mistakes Developers Make

  • Storing JWT secrets or database passwords directly in source code instead of environment variables.
  • Trusting user-supplied IDs (userId, role) from the request body instead of the verified token.
  • Not setting token expiration, so a stolen token remains valid forever.
  • Returning detailed error messages (stack traces, database errors) directly to API clients.

Best Practices

  • Hash passwords with bcrypt (or argon2); never store or log plain-text passwords.
  • Keep JWT expiration short and use refresh tokens for longer sessions where needed.
  • Validate and sanitize all user input on the server — never trust the frontend alone.
  • Use HTTPS everywhere; never transmit credentials or tokens over plain HTTP.
  • Keep dependencies updated — many real-world breaches exploit known vulnerabilities in outdated packages.

FAQs

What's the difference between session-based and token-based authentication? Session-based auth stores login state on the server and references it with a cookie; token-based auth (like JWT) encodes identity directly in a token the client holds. See Session-Based vs Token-Based Authentication for a full comparison.

Is JWT the best authentication method? It's a good default for stateless REST APIs, but not universally "best" — session-based auth is still a reasonable, often simpler choice for traditional server-rendered apps.

Do small projects really need to worry about security? Yes — many real breaches target small, poorly secured apps precisely because they're easier targets, not because attackers only go after large companies.

What is OWASP? A nonprofit organization that publishes widely referenced security resources, most notably the OWASP Top 10 list of critical web application risks. See What Is OWASP?

How do I know if my API is secure enough? Start with the basics covered here — authentication, authorization checks on every route, input validation, and hashed passwords — then consider a dedicated security review or automated scanning tool as the application grows.

Should secrets like API keys ever be in frontend code? No — anything shipped to the browser is visible to users. Secrets belong on the server, accessed through environment variables, never in client-side JavaScript.

Conclusion

Most real-world security problems come from skipping a small number of well-known basics: hash passwords, validate all input, never trust client-supplied identity, and keep secrets out of source code. Get these right consistently, and you've addressed the majority of real-world risk before touching anything more advanced.

Related Articles

CTA

Building an app that handles user accounts or payments? Get in touch — secure authentication is built into projects like VertexCRM.


Image Plan

Image 01 — Featured image Type: Technical illustration Prompt: "Minimalist illustration of a padlock icon over a code window, dark navy and red accent color, clean white background, premium tech editorial style" Filename: web-security-best-practices-hardik-yadav.webp Alt: "Illustration representing web security and authentication for developers" Ratio: 16:9 Source: AI-generated

Image 02 — After JWT code example Type: Diagram Prompt: "Flat sequence diagram showing a user logging in, receiving a JWT token, and sending it back on future requests to a protected route, minimal labeled arrows" Filename: jwt-authentication-flow-diagram.webp Alt: "Diagram of the JWT authentication flow" Ratio: 16:9 Source: Diagram (self-made/AI-generated)

Image 03 — After vulnerabilities table Type: Diagram Prompt: "Flat illustration of three labeled shield icons representing defenses against SQL injection, XSS, and CSRF, minimal clean style" Filename: web-security-vulnerabilities-defense-diagram.webp Alt: "Diagram of common web security vulnerabilities and their defenses" Ratio: 4:3 Source: Diagram (self-made/AI-generated)