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 Category | Express Implementation Notes |
|---|---|
| V2: Authentication | Use passport.js, enforce strong password rules |
| V4: Access Control | Role-based middleware per route |
| V5: Validation/Sanitization | Use express-validator, validate all user input |
| V6: Stored Data Protection | Hash passwords (bcrypt), encrypt sensitive data |
| V7: Error Handling | Remove stack traces from production responses |
| V9: Communications | Enforce 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-SecurityX-Frame-OptionsX-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.
