How I’m Securing an Express App from Scratch Using ASVS

When I stepped into the role of security engineer for a small startup, The Fame Exchange, I didn’t walk into a mature pipeline with hardened infrastructure. Instead, I walked into a clean slate — an Express-based web app just beginning to grow. No automated scanners, no pentests, no security gates. Just me, a developer stack, and a mission: make this secure.

Instead of winging it or applying security piecemeal, I decided to use the OWASP Application Security Verification Standard (ASVS) as my baseline. It’s not just a checklist — it’s a framework for security maturity. Here’s how I’m applying it step-by-step.


Why ASVS?

I picked ASVS because it offers something bug bounty hunting and automated tools alone can’t:

  • Structure. It’s organized into levels and domains. I chose Level 1, which focuses on general web app security.
  • Depth. It doesn’t just tell you what’s wrong — it helps build security from the ground up.
  • Credibility. OWASP standards are trusted industry-wide.

I wanted to do more than just react to issues. I wanted to build security into the app design itself, and ASVS gave me a blueprint to do that.


Step 1: Map ASVS to Express

ASVS breaks down security into categories like authentication, input validation, and session management. I translated these into actionable tasks specific to Express:

ASVS CategoryExpress Implementation Notes
V2: AuthenticationUse passport.js, enforce strong password rules
V4: Access ControlRole-based middleware per route
V5: Validation/SanitizationUse express-validator, validate all user input
V6: Stored Data ProtectionHash passwords (bcrypt), encrypt sensitive data
V7: Error HandlingRemove stack traces from production responses
V9: CommunicationsEnforce HTTPS via middleware and headers

This mapping let me translate ASVS “theory” into Express.js “practice.”


Step 2: Lock Down the Basics

✅ Secure Headers

I used helmet to set HTTP headers and disable common attacks:

jsCopyEditconst helmet = require('helmet');
app.use(helmet());

This included:

  • Strict-Transport-Security
  • X-Frame-Options
  • X-Content-Type-Options

✅ Enforce HTTPS

While Express doesn’t enforce HTTPS by itself, I added middleware to redirect all HTTP traffic:

jsCopyEditapp.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect('https://' + req.headers.host + req.url);
  }
  next();
});

In production, this pairs with an NGINX reverse proxy and Let’s Encrypt certificate.


Step 3: Input Validation and Output Encoding

One of the biggest risk areas in any web app is unsanitized user input.

I used express-validator:

jsCopyEditconst { check, validationResult } = require('express-validator');

app.post('/signup', [
  check('email').isEmail(),
  check('password').isLength({ min: 8 }),
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  // Continue processing...
});

And for templating, I made sure we’re escaping output consistently (using ejs with encoding enabled).


Step 4: Authentication

I implemented local authentication using passport.js with secure password hashing via bcrypt:

jsCopyEditconst bcrypt = require('bcrypt');
const saltRounds = 12;

// Password hash
const hash = await bcrypt.hash(req.body.password, saltRounds);

I also added:

  • Account lockouts after multiple failed logins
  • Email verification on signup
  • Session expiration timeouts

Step 5: Session and Token Management

Using express-session, I configured sessions securely:

jsCopyEditapp.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    maxAge: 1000 * 60 * 15 // 15 min session
  }
}));

This ensures:

  • Cookies are not accessible via JavaScript
  • Sessions expire quickly
  • No insecure cookies over HTTP

Step 6: Logging and Error Handling

We don’t want to leak stack traces or internal info to users:

jsCopyEditapp.use((err, req, res, next) => {
  console.error(err.stack); // Log it
  res.status(500).json({ message: 'Something went wrong.' });
});

I’m also integrating winston for log management and alerts when needed.


Step 7: Manual and Automated Testing

Once I set up ASVS controls, I began verifying them through:

  • Burp Suite: to simulate external attacks
  • ZAP Proxy: to check for missed issues
  • Manual code reviews: to make sure nothing’s slipping through

Eventually, I plan to automate some checks into the CI pipeline with GitHub Actions.


Lessons Learned So Far

  • You can apply ASVS even in small projects. It’s not just for enterprise.
  • Express gives you freedom — which means you must secure it yourself.
  • Security isn’t just a feature. It’s a discipline.

By sticking to ASVS Level 1 as a baseline, I’ve created a security-first culture in a lean startup context.


What’s Next?

My next milestones:

  • Enforcing CSP (Content Security Policy)
  • Adding 2FA for admin logins
  • Implementing rate limiting and DoS protection with express-rate-limit
  • Running OWASP Dependency-Check for NPM modules

If you’re securing your own Express app, I highly recommend downloading the OWASP ASVS and building from there. You don’t have to be perfect — just intentional.

Let me know if you want to see the actual checklist I’m using, or if you’d like me to open-source a starter template. We’re all learning this together.