CS478 – Homework 4 Reflection

Authentication and Authorization

The most challenging part of the back end was getting the authorization checks correct and consistent across all the routes. It wasn’t enough to simply "require login" globally. For create operations, I had to reliably identify the user associated with the session cookie and then store ownership information-a created_by column-so that edits and deletes could be restricted to only the creator. I also had to be careful about the order of the checks so that error responses made sense, for example: return 401 when the user is not logged in, return 404 when the resource doesn’t exist, and return 403 when the user is logged in but doesn’t own the resource.

Of course, on the front end, the trickiest thing was keeping the UI and the server's "source of truth" in sync. I wanted the interface to hide or disable actions when the user isn't logged in, and also hide edit and delete buttons on books the user does not own. That required loading the current user (/api/auth/me) and comparing the user id to each row's created_by. I also had to handle the case where the UI state is stale - for example after logout or session expiration - so even if something is visible, the server still rejects the action and the UI displays the error message instead of failing silently.

Deployment

The biggest challenge I encountered during the deployment phase was to ensure all the different parts fell into place, i.e., building React as a production bundle, configuring the Node server to serve them correctly, then figuring out how to expose it to the public internet using a reverse proxy. I faced some path-related issues where TypeScript was outputting our server code to dist/src, which in turn altered the perception of current directory when it came to static assets. However, I had to make things explicit, so I ended up copying the React dist folder to the correct location.

The other problem I encountered was the operational part – where I had to make sure that the necessary tools were installed on the VPS, e.g., git, and access the database created in the same place that the server in the VPS opens up. I also had to make sure that the application continues to run after disconnection from SSH or a server restart. Caddy helped simplify the reverse proxy aspect of the application, and pm2 reinitiates the node application.

Security Audit

For XSS, I focused on preventing user-controlled input from being executed as HTML or script in the browser. The app primarily displays user input as text content in React components, and React escapes text by default. That means values like <script>alert(1)</script> are rendered as literal text rather than executed code. On the server side, I also validated inputs (lengths and formats) to reduce the chance of unexpected payloads. Because I did not use patterns like dangerouslySetInnerHTML or template concatenation into HTML responses, the app is not vulnerable to the classic stored XSS scenario.

For CSRF, the app uses cookie-based sessions, so CSRF is a real threat if requests can be triggered cross-site. I mitigated this by using SameSite=Strict on the session cookie and adding a CSRF token check for unsafe HTTP methods. The server sets a csrf cookie and requires the client to echo that token in an X-CSRF-Token header on POST/PUT/DELETE requests. A malicious third-party site cannot read the CSRF cookie due to browser protections, so it cannot forge the header correctly, and the request is rejected.

For rate limiting, I implemented it in application code using the express-rate-limit package. I set a general limiter for the API and a stricter limiter on the login endpoint to make brute forcing passwords harder. This prevents a single IP from spamming requests and adds friction to credential guessing attacks.

I set security-related HTTP headers using the helmet package. This adds headers like Content-Security-Policy (reduces XSS risk by restricting what resources can load), X-Content-Type-Options: nosniff (prevents MIME-type sniffing), Referrer-Policy (limits referrer leakage), Strict-Transport-Security (encourages HTTPS-only access), and framing protections (X-Frame-Options) to reduce clickjacking risk. These headers harden the browser-side security model even if an attacker finds a way to inject content or trick navigation.

In addition to those items, I also avoided leaking sensitive authentication information by returning generic error messages on login failures (not revealing whether the username exists). I used Argon2 for password hashing, which is designed to be slow and memory-hard, improving resistance to offline cracking if the database is compromised. Finally, I kept the Node server bound to localhost and exposed it only through Caddy, which reduces the attack surface by ensuring all public traffic goes through HTTPS and a single entry point.