VideoHub’s security model is built around three interlocking mechanisms: CORS origin allowlisting to prevent unauthorized browser clients from reaching the API, HttpOnly cookies to transport tokens safely without exposing them to JavaScript, and signed JWTs to authenticate every protected request in a stateless fashion. Understanding how each layer is configured — and how to harden it for production — is essential before deploying the backend publicly.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/barcode8/VideoHub/llms.txt
Use this file to discover all available pages before exploring further.
CORS configuration
Cross-Origin Resource Sharing is configured insrc/app.js immediately after the Express app is created. The allowed origin is read from the CORS_ORIGIN environment variable, so no code changes are required when switching between environments.
origin— only requests from the specified origin are allowed. Any request from a different origin will be rejected by the browser’s preflight check before it reaches any route handler.credentials: true— instructs the browser to include cookies (andAuthorizationheaders) on cross-origin requests. Without this flag, the browser strips cookies from every cross-origin fetch, which would break authentication entirely.
Supporting multiple origins in production
The current configuration accepts exactly one origin string. If your production deployment serves multiple frontend domains (for example a primary domain and a staging subdomain), you need to updateapp.js to accept an array and match against it dynamically:
CORS_ORIGIN to a comma-separated list of domains: https://videohub.app,https://staging.videohub.app.
Cookie security
Access tokens and refresh tokens are delivered to the client as HttpOnly cookies, not as JSON body values that get saved tolocalStorage. This is the primary defense against cross-site scripting (XSS) attacks stealing authentication credentials.
The cookie options object used in the loginUser controller (src/controllers/user.controller.js) is:
| Flag | Value (dev) | Effect |
|---|---|---|
httpOnly | true | The cookie is inaccessible to document.cookie and any JavaScript running in the browser. |
secure | false | The cookie is sent over both HTTP and HTTPS. Must be set to true in production. |
sameSite | "Lax" | The cookie is sent on same-site requests and top-level cross-site navigations, but not on cross-site sub-resource requests. This provides a reasonable balance between usability and CSRF protection. |
logoutUser controller (which clears cookies) and the refreshAccessToken controller (which reissues token pairs) both use a stricter options object that omits sameSite and enables secure:
Request size limits
The middleware stack insrc/app.js applies explicit size constraints to all incoming request bodies:
JSON payloads
Content-Type: application/json body larger than 16 kilobytes is rejected with a 413 Payload Too Large response before reaching a route handler. This prevents large JSON blobs from consuming server memory.
URL-encoded form data
qs library (extended: true), which supports nested objects and arrays. No explicit size cap is set here beyond the default Express limits.
File uploads
File uploads are handled by Multer (src/middlewares/multer.middleware.js), which writes incoming files to a temporary directory at public/temp/ before the upload controller sends them to Cloudinary:
public/temp/ with a unique timestamped filename, then uploaded to Cloudinary and deleted from disk. Note that Cloudinary enforces its own limits on video duration and file size based on your account plan.
JWT security
JSON Web Tokens are the backbone of VideoHub’s stateless authentication. Both token types are generated insrc/models/user.models.js using jsonwebtoken:
Access tokens are short-lived and carry the user’s _id, email, username, and fullname as claims. They expire after the duration set in ACCESS_TOKEN_EXPIRY (default: 1d).
Refresh tokens carry only the user’s _id and have a longer lifetime controlled by REFRESH_TOKEN_EXPIRY (default: 10d). Crucially, the current refresh token is stored in the user’s database document. On logout, the logoutUser controller sets refreshToken to undefined, which immediately invalidates the token server-side even before it expires:
The verifyJwt middleware
Every protected route passes through the verifyJwt middleware defined in src/middlewares/auth.middleware.js. It checks for a token in two places — cookies first, then the Authorization header — so the API works for both browser clients (cookies) and programmatic clients (bearer tokens):
401 Unauthorized error. The password and refreshToken fields are explicitly excluded from the database query so they are never attached to req.user and cannot accidentally leak into response payloads.
A second middleware, verifyJWTIfAvailable, follows the same lookup pattern but silently continues on failure — it is used on public routes (such as video browsing) where authentication is optional but enriches the response if a valid token is present.
Production security checklistBefore going live, verify all of the following:
- HTTPS is enforced — run the server behind a TLS-terminating reverse proxy (nginx, Caddy) or deploy to a platform that provides HTTPS automatically.
CORS_ORIGINis set to the exact frontend domain — wildcards (*) must never be used alongsidecredentials: true.secure: trueis set on all cookie options in the login and refresh token controllers.- JWT secrets are long and random — at least 64 bytes of entropy. Generate them with
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))". - JWT secrets are rotated periodically — existing sessions will be invalidated, which forces users to log in again; schedule rotations during low-traffic windows.
ACCESS_TOKEN_EXPIRYis short — prefer15mor1hin production and rely on the refresh flow to maintain long-lived sessions.