Broken Access Control has been OWASP #1 since 2021. Not because developers don't understand authentication — but because auth is a consistency problem.
Your auth can be perfect on 99 endpoints. Endpoint 100 ships without middleware because someone forgot, copied a route template that didn't include it, or added an "admin-only" page during a hackathon and never locked it down.
Most auth tooling is runtime: pentest frameworks, DAST scanners, bug bounties. By the time they find something, the code is deployed and the vulnerability is live. I wanted a pre-commit hook that catches the common stuff before it leaves the developer's machine. So I built one.
How AuthAudit Maps to OWASP
AuthAudit scans for 90 authentication and authorization anti-patterns across 6 categories. Every finding maps to an OWASP Top 10 2021 entry:
| OWASP Category | AuthAudit Coverage |
|---|---|
| A01:2021 Broken Access Control | Missing auth middleware, IDOR patterns, frontend-only role checks |
| A02:2021 Cryptographic Failures | Weak hashing, missing encryption, token mismanagement |
| A04:2021 Insecure Design | Missing rate limiting, authorization logic flaws |
| A07:2021 Identification & Auth Failures | Session management, password handling, CSRF gaps |
Pattern 1: Unprotected Routes
This is the most common finding. A route definition with no authentication middleware between the path and the handler.
// Express — no auth middleware
app.get('/admin/users', (req, res) => {
const users = await User.findAll();
res.json(users);
});
// What it should look like
app.get('/admin/users', requireAuth, requireRole('admin'), (req, res) => {
const users = await User.findAll();
res.json(users);
});
Why it happens: route templates without auth, copy-paste from tutorials, "I'll add auth later" that never happens.
AuthAudit rule AC-001 catches route definitions with no auth middleware. AC-003 specifically flags admin paths without elevated auth checks.
Pattern 2: JWT Stored in localStorage
// After successful login
const response = await fetch('/api/login', { method: 'POST', body: credentials });
const { token } = await response.json();
localStorage.setItem('token', token); // XSS = full account takeover
Any XSS vulnerability gives the attacker the full JWT. Game over. Full account takeover.
The alternative is HttpOnly cookies — they're not accessible to JavaScript. An XSS attack can still make requests (CSRF), but it can't steal the session token and use it from another machine.
AuthAudit rule TK-003 flags JWT/token storage in localStorage. TK-005 catches sensitive tokens accessible to client-side JavaScript.
Pattern 3: Missing CSRF Protection
State-changing endpoints (POST, PUT, DELETE) without CSRF token validation.
// No CSRF middleware on a state-changing route
app.post('/api/transfer', requireAuth, (req, res) => {
const { to, amount } = req.body;
transferFunds(req.user.id, to, amount);
res.json({ success: true });
});
SPA frameworks handle CSRF differently than server-rendered apps. Developers assume "the frontend handles it" — often nobody handles it.
AuthAudit rule CS-004 catches state-changing endpoints without CSRF protection. CS-006 flags missing SameSite cookie attribute.
Pattern 4: Session Mismanagement
// Express session config — missing security flags
app.use(session({
secret: 'keyboard cat',
cookie: {
// Missing: httpOnly, secure, sameSite
// Missing: maxAge (no session expiry)
}
}));
Three things that should always be set on session cookies: HttpOnly (not accessible to JS), Secure (HTTPS only), and SameSite (prevents cross-origin requests from sending the cookie). Plus: rotate the session ID after login to prevent session fixation.
AuthAudit rules CS-001 (missing HttpOnly), CS-002 (missing Secure), CS-007 (no session rotation after authentication).
Pattern 5: Insecure Password Handling
// Plaintext comparison — never do this
if (password === user.password) {
return { authenticated: true };
}
// MD5/SHA1 — not designed for passwords
const hash = crypto.createHash('md5').update(password).digest('hex');
// What it should look like
const match = await bcrypt.compare(password, user.passwordHash);
Plaintext comparison means passwords are stored in plaintext. MD5 and SHA1 are fast hashes — designed for checksums, not passwords. Use bcrypt, scrypt, or argon2.
AuthAudit rule PW-001 (plaintext password comparison), PW-003 (weak hashing algorithm), PW-006 (login endpoint without rate limiting).
Pattern 6: Frontend-Only Role Checks
// React component — frontend-only authorization
function Dashboard() {
const { user } = useAuth();
return (
<div>
{user.role === 'admin' && <AdminPanel />}
{user.role === 'user' && <UserPanel />}
</div>
);
}
// But the API endpoint serves admin data to ANY authenticated user
app.get('/api/admin/dashboard', requireAuth, (req, res) => {
// Missing: requireRole('admin')
const data = await getAdminDashboard();
res.json(data);
});
Frontend checks are UX, not security. Anyone can call your API directly. The backend must enforce authorization independently.
AuthAudit rule AC-008 (role check in frontend only, no backend enforcement), AC-011 (API endpoint missing role-based authorization).
Setting Up the Pre-Commit Hook
Install AuthAudit and run an initial scan:
clawhub install authaudit
authaudit scan .
You'll get a scored report (0-100) with findings mapped to OWASP categories:
$ authaudit scan src/
[CRITICAL] AC-001 Unprotected admin route — routes/admin.js:5
[CRITICAL] TK-003 JWT stored in localStorage — auth/client.js:22
[HIGH] CS-002 Session cookie missing HttpOnly — config/session.js:8
[HIGH] AC-008 Role check in frontend only — components/admin.tsx:45
Score: 41/100 (Grade: F)
For the pre-commit hook (Pro tier), configure lefthook:
# .lefthook.yml
pre-commit:
commands:
authaudit:
run: authaudit scan --staged --min-score 60
This blocks any commit that would drop the auth security score below 60. Start with a low threshold and raise it as you fix existing issues.
What This Doesn't Replace
AuthAudit finds known anti-patterns. It won't find a novel auth bypass in your custom OAuth implementation. For that, you still need manual security review or a dedicated pentest.
This tool catches the stuff that shouldn't survive code review but consistently does — the missing middleware, the insecure cookie config, the localStorage JWT that someone copied from a tutorial.
clawhub install authaudit
authaudit scan .
Free to scan. Pro ($19/mo) adds the pre-commit hook + OWASP compliance reports. 100% local — your auth code never leaves your machine.
Top comments (0)