Add Express trust proxy setting and improve session cookie configuration
to work properly behind nginx reverse proxy.
Changes:
- Add app.set('trust proxy', 1) before session middleware
- Update session cookie with sameSite: 'lax' and httpOnly: true
- Add explicit cookie name: 'connect.sid'
This works together with nginx location blocks to route /api/projects
and /api/recycle-bin requests to the Obsidian Web Interface (port 3010)
instead of the generic Next.js backend (port 8080).
Fixes "Failed to load on projects" error on production domain.
See AUTHENTICATION_FIX_REPORT.md for full details.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
9.9 KiB
Authentication Fix Report - Obsidian Web Interface
Problem Summary
The Obsidian Web Interface at https://www.rommark.dev/claude was showing "Failed to load on projects" error when accessing the Projects page.
Root Cause Analysis
After systematic debugging, I identified TWO critical issues:
Issue #1: Nginx Configuration Conflict (PRIMARY CAUSE)
Location: /etc/nginx/sites-enabled/rommark.dev
Problem: The nginx configuration had a generic /api location block that was routing ALL /api requests to the Next.js application running on port 8080, overriding the specific /api/projects route that should go to the Obsidian Web Interface on port 3010.
Evidence:
# This returned a 404 from WordPress/Next.js instead of the projects API
curl https://www.rommark.dev/api/projects
# Returned: <body class="error404 wp-embed-responsive...">
# But this worked fine
curl https://www.rommark.dev/claude/api/auth/status
# Returned: {"authenticated":true,"username":"admin"}
Why it worked locally but not in production:
- Local: Direct connection to port 3010, no nginx proxy
- Production: Requests go through nginx, which has conflicting location blocks
Issue #2: Session Cookie Configuration
Location: /home/uroma/obsidian-web-interface/.worktrees/project-organization/server.js (lines 51-59)
Problem: The session middleware configuration was missing:
trust proxysetting - Required for proper session handling behind nginxsameSiteattribute - Should be 'lax' for proper cookie behavior- Explicit
httpOnlyflag - Security best practice
Original Configuration:
app.use(session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true if using HTTPS
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
Solution Implemented
Fix #1: Updated Nginx Configuration
File: /etc/nginx/sites-enabled/rommark.dev
Changes: Added specific location blocks for Obsidian API routes BEFORE the generic /api block (nginx uses first matching pattern):
# Obsidian Web Interface API routes (must come before generic /api block)
location /api/projects {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/recycle-bin {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# API routes for Next.js (generic catch-all, must come after specific /api routes)
location /api {
proxy_pass http://127.0.0.1:8080;
# ... rest of config
}
Fix #2: Updated Session Configuration
File: /home/uroma/obsidian-web-interface/.worktrees/project-organization/server.js
Changes: Added proxy trust and improved cookie settings:
// Trust proxy for proper session handling behind nginx
app.set('trust proxy', 1);
app.use(session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Will work with both HTTP and HTTPS behind proxy
sameSite: 'lax',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
domain: undefined // Allow cookie to work on all subdomains
},
name: 'connect.sid'
}));
Deployment Instructions
A deployment script has been created at:
/home/uroma/obsidian-web-interface/.worktrees/project-organization/deploy-fix.sh
To Deploy the Fix:
# Run the deployment script (requires sudo)
sudo ./deploy-fix.sh
Manual Deployment (Alternative):
1. Update Nginx Configuration:
sudo cp /tmp/rommark.dev.updated /etc/nginx/sites-enabled/rommark.dev
sudo nginx -t
sudo systemctl reload nginx
2. Update server.js:
cd /home/uroma/obsidian-web-interface/.worktrees/project-organization
# Add trust proxy and update session config (lines 48-60)
# Edit server.js and make the changes shown in Fix #2 above
3. Restart the Server:
pkill -f "node server.js"
cd /home/uroma/obsidian-web-interface/.worktrees/project-organization
nohup node server.js > /tmp/claude/server.log 2>&1 &
Verification Steps
After deployment, verify the fix:
# 1. Test login
curl -c /tmp/cookies.txt -X POST https://www.rommark.dev/claude/api/login \
-H "Content-Type: application/json" \
--data-binary '{"username":"admin","password":"!@#$q1w2e3r4!A"}'
# 2. Test auth status
curl -b /tmp/cookies.txt https://www.rommark.dev/claude/api/auth/status
# Should return: {"authenticated":true,"username":"admin"}
# 3. Test /api/projects endpoint
curl -b /tmp/cookies.txt https://www.rommark.dev/api/projects
# Should return: {"success":true,"projects":[...]}
# 4. Test in browser
# Open https://www.rommark.dev/claude and verify:
# - Login works
# - Projects page loads without errors
# - Session persists across page loads
Files Modified
-
Nginx Configuration:
/etc/nginx/sites-enabled/rommark.dev- Added specific location blocks for
/api/projectsand/api/recycle-bin
-
Server Configuration:
/home/uroma/obsidian-web-interface/.worktrees/project-organization/server.js- Added
trust proxysetting - Updated session cookie configuration
-
Deployment Script (Created):
/home/uroma/obsidian-web-interface/.worktrees/project-organization/deploy-fix.sh- Automated deployment of all fixes
Technical Details
Why Nginx Location Block Order Matters
Nginx processes location blocks in a specific order:
- Exact matches (
=) - Prefix matches (longest first)
- Regex matches (in order of appearance)
- Prefix match (if used with
^~)
The generic /api location was matching /api/projects before any more specific rules could be evaluated. By placing specific /api/projects and /api/recycle-bin blocks BEFORE the generic /api block, we ensure they take precedence.
Why Trust Proxy is Required
When the application sits behind a reverse proxy (nginx):
- The proxy receives the HTTPS connection
- The proxy forwards requests to the app over HTTP
- The app sees the request as HTTP (not HTTPS)
- Session cookies with
secure: truewould be rejected - The
X-Forwarded-Protoheader tells the app the original protocol
By setting app.set('trust proxy', 1), Express.js:
- Trusts the
X-Forwarded-*headers - Correctly identifies the original request protocol
- Properly handles session cookies
Why SameSite: 'lax'
The sameSite attribute controls when cookies are sent with cross-site requests:
strict: Never send cookies with cross-site requests (most secure, but breaks navigation)lax: Send cookies with top-level navigations (balance of security and usability)none: Always send cookies (required for cross-origin requests, needssecure: true)
For this application:
- Login happens on the same origin
- Navigation between pages needs the session cookie
laxprovides the right balance
Security Considerations
Current Configuration:
- Cookie Security:
httpOnly: trueprevents XSS attacks from accessing the cookie - SameSite:
laxprevents CSRF attacks while allowing legitimate navigation - Secure Flag: Set to
falseto allow both HTTP and HTTPS (can be set totrueif HTTP is disabled) - Domain:
undefinedallows cookie to work on all subdomains
Recommendations for Production:
- Set
secure: trueif HTTP access is disabled - Use a strong, random
SESSION_SECRETfrom environment variable - Implement rate limiting on login endpoint
- Add CSRF protection for state-changing operations
- Regularly rotate session secrets
Troubleshooting
If authentication still fails after deployment:
-
Check Server Logs:
tail -f /tmp/claude/server.log -
Check Nginx Logs:
tail -f /var/log/nginx/error.log -
Verify Nginx Configuration:
nginx -T | grep -A 10 "location /api" -
Test Direct Connection (bypass nginx):
curl http://localhost:3010/api/projects -
Check Cookie Headers:
curl -v https://www.rommark.dev/claude/api/login \ -H "Content-Type: application/json" \ --data-binary '{"username":"admin","password":"!@#$q1w2e3r4!A"}' \ 2>&1 | grep -i "set-cookie" -
Verify Session Store:
- Ensure express-session is properly configured
- Check for memory issues or session store problems
Additional Notes
- The JSON parsing error seen in logs (
Bad escaped character in JSON at position 33) was due to shell escaping during testing with curl, not an actual application bug - The
credentials: 'include'setting in frontend fetch calls was already correct - WebSocket connections already had proper session verification in place
- The application is using in-memory session storage (consider Redis for production deployments)
Conclusion
The authentication failure was caused by nginx routing /api/projects requests to the wrong backend server. The fix involves:
- Adding specific nginx location blocks for Obsidian API routes
- Configuring Express to trust proxy headers
- Improving session cookie configuration
After deployment, the application should work correctly at:
- HTTPS: https://www.rommark.dev/claude
- HTTP: http://www.rommark.dev/claude (redirects to HTTPS)
Report Generated: 2026-01-19 Debugged By: Claude (Sonnet 4.5) Deployment Ready: Yes