Skip to content

Security: Implement JWT auth, auth rate limiting, and secure error handling (OWASP Top 10)#474

Open
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1776165193-security-fixes
Open

Security: Implement JWT auth, auth rate limiting, and secure error handling (OWASP Top 10)#474
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1776165193-security-fixes

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot commented Apr 14, 2026

Summary

Addresses the top 3 findings from an OWASP Top 10 security review of the timesheet app:

1. Broken Authentication (Critical): Replaced the insecure x-user-email header-based authentication with JWT Bearer token verification. The login endpoint now issues signed JWTs (24h expiry), and all protected routes verify tokens via the Authorization: Bearer <token> header instead of trusting a raw email header. The middleware no longer auto-creates users on every request — it only verifies that the token's email exists in the database.

2. Hardcoded JWT Secret (High): Updated .env.example to remove the guessable default secret and added a generation command. Added a runtime warning when the secret is weak or missing.

3. Auth Rate Limiting + Error Handler (High): Added a dedicated rate limiter on /api/auth routes (5 requests per 15 minutes per IP) to prevent brute-force and email enumeration. Updated the error handler to suppress internal error details when NODE_ENV=production.

Frontend updated to store/send JWT tokens via localStorage('authToken') and Authorization header. All 160 tests updated and passing.

Review & Testing Checklist for Human

  • Login still requires only an email — no password or credential verification was added. The JWT prevents impersonation on protected routes (attackers can no longer just set a header), but anyone who hits POST /api/auth/login with a valid email format still gets a token. Evaluate whether this is acceptable for your threat model or if password/OTP should be added.
  • This is a breaking API change. Any external clients or scripts using x-user-email header will stop working. Verify no other services depend on the old auth mechanism.
  • Verify the full auth flow end-to-end: login → receive token → use token on protected endpoints → token expiry handling → logout clears token. The frontend TypeScript build passes, but the token field on the login response may not be explicitly typed (check if the API response types in the frontend are loose/any).
  • Auth rate limit of 5 requests/15min may be aggressive for users behind shared IPs (offices, VPNs). Consider whether this threshold is appropriate for your deployment.
  • JWT_SECRET is read once at module load time (const JWT_SECRET = process.env.JWT_SECRET). Ensure the env var is set before the server starts; it cannot be changed at runtime.

Notes

  • SQL injection, XSS, and CSRF were also reviewed. SQL queries already use parameterized statements. React's JSX escaping mitigates XSS. CSRF risk is low with Bearer tokens (not cookie-based). A full findings report was provided separately.
  • The jsonwebtoken package was already a dependency — no new packages were added.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/10807ffdd5b740768d9bc784aecba943


Open with Devin

…andling

Security fixes for top 3 OWASP findings:

1. CRITICAL - Broken Authentication: Replace insecure email-header auth with
   JWT Bearer token verification. Login now issues signed JWT tokens (24h expiry).
   Protected routes verify tokens via Authorization header instead of trusting
   raw x-user-email header.

2. HIGH - Hardcoded JWT Secret: Update .env.example to remove guessable default
   secret and add generation instructions. Add runtime warning when secret is
   weak or missing.

3. HIGH - Auth Rate Limiting + Error Handler: Add strict rate limiter on auth
   endpoints (5 attempts/15min) to prevent brute-force. Improve error handler
   to hide internal details in production mode.

Frontend updated to store/send JWT tokens instead of email headers.
All 160 tests updated and passing.
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

// Simple email-based authentication middleware
const JWT_SECRET = process.env.JWT_SECRET;

if (!JWT_SECRET || JWT_SECRET.includes('change-this')) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Placeholder detection check doesn't match the new placeholder value

The startup warning check at backend/src/middleware/auth.js:6 uses JWT_SECRET.includes('change-this') to detect if the developer forgot to set a real secret. However, the new .env.example placeholder was changed to CHANGE_ME_generate_a_random_secret_with_the_command_above (backend/.env.example:11), which does not contain the substring change-this. A developer who copies .env.example to .env without changing the secret will not see the warning, and the server will silently use the well-known placeholder as the JWT signing secret.

Suggested change
if (!JWT_SECRET || JWT_SECRET.includes('change-this')) {
if (!JWT_SECRET || JWT_SECRET.includes('CHANGE_ME')) {
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


// Routes
app.use('/api/auth', authRoutes);
app.use('/api/auth', authLimiter, authRoutes);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Auth rate limiter (max 5 / 15 min) applies to GET /api/auth/me, breaking normal usage

The authLimiter with max: 5 is applied to all routes under /api/auth (backend/src/server.js:54), including GET /api/auth/me. The frontend calls getCurrentUser() (which hits /api/auth/me) on every page load via the useEffect in AuthContext.tsx:20. After just 5 page loads/refreshes within 15 minutes, the rate limiter returns 429, which triggers the catch block in AuthContext.tsx:22-26 that removes authToken from localStorage — effectively logging the user out. The authLimiter should only be applied to the login endpoint, not the entire /api/auth prefix.

Prompt for agents
The authLimiter rate limiter (max 5 requests per 15 minutes) is mounted on the entire /api/auth prefix in backend/src/server.js:54. This means it also affects GET /api/auth/me, which the frontend calls on every page load (frontend/src/contexts/AuthContext.tsx:20). After 5 page loads, the user gets rate-limited and effectively logged out.

The fix should apply the authLimiter only to the login endpoint. Two approaches:
1. In server.js, change the mounting so authRoutes gets the normal global limiter, and apply authLimiter specifically. For example, add the authLimiter inside the routes/auth.js file only on the POST /login route.
2. Alternatively, in server.js, mount authRoutes without authLimiter, and add a separate route-specific mount: app.post('/api/auth/login', authLimiter) before the authRoutes mount (though this may cause ordering issues).

The cleanest approach is probably option 1: import the rate limiter in routes/auth.js and apply it only to router.post('/login', authLimiter, ...) handler.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants