API Security for Your DTC Site: Authentication, Rate Limiting, CORS, Input Validation, and Vulnerability Protection
1. Why “HTTPS enabled” doesn’t mean your API is secure
Many site owners treat HTTPS as synonymous with security, assuming that once traffic between the browser and server is encrypted, the API is solid. That’s the first misconception: HTTPS solves “someone eavesdropping on the wire.” It does not solve “a legitimate identity being used to hammer your endpoints,” “malicious scripts mass-attacking your login API,” or “a SQL snippet stuffed into a request that dumps your tables.”
Here’s the counterintuitive judgment:What actually gets DTC sites breached is almost always application-layer vulnerabilities, not transport-layer ones. Database dumps, database deletions, APIs getting hammered into the ground — 90% of it comes from unvalidated input, weak authentication, and missing rate limits. A WAF can block some known attack signatures, but it can’t block the holes your own business logic creates. This matches the post-mortem conclusions from After your site gets hacked: clean up the code first, then talk about hardening.
Each of the five layers covers a different segment. Miss one, and a whole class of attacks goes straight through. The table below lays out what each layer blocks and what happens if you skip it.
| Security Layer | Attacks Blocked | Typical Implementation | What happens if you skip it |
|---|---|---|---|
| Certifications | Anonymous calls, identity spoofing | API Key, JWT, OAuth2 | Data APIs pulled directly by anyone |
| Rate Limiting | Brute-force enumeration, bulk API abuse | Sliding window by IP/Key | Login API credential stuffing, database dumped |
| CORS | Malicious sites reading data cross-origin | Origin whitelist | Logged-in user data read by third-party sites |
| Input Validation | SQL injection, XSS, dirty data writes | Whitelist validation + parameterized queries | Full database exfiltration, backend hit with XSS session hijacking |
| Vulnerability Scanning | Known backdoors, outdated dependencies | Dependency scanning + regular vulnerability scans | Running an old plugin with a public exploit |
2. Authentication: How to Choose Among Three Methods, and How to Manage Keys
Your site’s public APIs fall into two categories: those for the frontend browser (login sessions, form submissions) and those for external system integration (webhooks, third-party sync). The two use different authentication methods — don’t mix them up.
Use JWT for front-end login sessions: tokens carry an expiry, 2 hours by default, and an expired token is swapped for a new one with a refresh token. For external integrations, use a long-lived API Key and put it in the request header, not in URL parameters — URLs end up in logs and browser history. Only bring in OAuth2 when you need to let a user authorize a third party to access data on their behalf. A one-person standalone site doesn’t need it in 90% of cases; it just adds complexity you have to maintain yourself.
How to decide: if an endpoint only serves your own front end, JWT is enough. If a partner’s program needs to call it, issue a separate API Key, and create one Key per partner so you can revoke any of them individually at any time. Rotate keys once a quarter, and during rotation keep the old and new keys both valid for 7 days so partners don’t suddenly break.
| Auth method | Use cases | Token lifetime | Rotation / revocation |
|---|---|---|---|
| Session Cookie | Traditional form-based admin login | Browser session | Invalid on logout |
| JWT | Decoupled front end calling an API | Access token: 2 hours | Refresh: 7 days, server-side blacklist |
| API Key | External systems, webhook callbacks | Long-lived, issued per partner | Quarterly rotation, individually revocable |
| OAuth2 | Third-party app acting on a user’s behalf | Short-lived access + refresh | User can revoke access anytime |
3. Rate limiting: who to limit by, and what thresholds to set
The most common rate-limiting mistake is using one threshold for the whole site. A login endpoint and a product listing endpoint see normal traffic that differs by an order of magnitude. With a single threshold you either block real users or fail to stop an attack. Do it right by tiering limits per endpoint and limiting along several dimensions.
Stack three rate-limiting dimensions: by IP to stop a single attacker from saturating you, by account to stop a password dictionary from cycling through one account, and by API Key to stop one partner’s program from running wild. Thresholds need real numbers. The table below gives starting values for common standalone-site endpoints; recalibrate against real traffic after launch.
Example scenario: Login endpoint under credential stuffing. Set 10 requests per minute per IP and 5 failures per minute per account; if the limit is exceeded for 5 minutes straight, block that IP for 1 hour. This works better than simply locking an account after 5 failed logins, because attackers try the same password across many accounts, and a per-account threshold won’t catch that. For handling spam traffic on forms, see the anti-spam approach in Inquiry Form Reliability.
| Endpoint type | Limit by | Starting threshold | Action when exceeded |
|---|---|---|---|
| Login / password reset | IP + account | 10 per minute | Temporary 1-hour block |
| Inquiry form submission | IP | 3 per minute | Show a CAPTCHA |
| Public product listing | IP | 60 per minute | Return 429 |
| External API integration | API Key | QPS as agreed in contract | 429 + notify the partner |
| On-site search | Session | 20 per minute | Return 429 |
4. CORS: when to enable it, and how far to open it
CORS (Cross-Origin Resource Sharing) is a browser built-in protection: by default, your page scripts cannot read responses from other domains’ APIs. It’s not that “enabling it makes you insecure” — it’s that “misconfiguring it makes you insecure.” The most common mistake is taking the lazy route and writing Access-Control-Allow-Origin: * together with Allow-Credentials: true — that throws your cross-origin door wide open to the entire internet.
The rule: if your API is only called by your own frontend, put only your own domain in the CORS allowlist (both the www and non-www versions), and never add *. If third-party frontends need to call it, add each domain one by one — don’t use fuzzy regex patterns like *.yourdomain.com, because once a subdomain gets registered or compromised, it becomes an attack surface.
A purely public API with no cookies (say, a product listing) doesn’t actually need CORS credentials at all — browsers aren’t that strict about simple requests, and every setting you skip is one less risk surface. For the boundary between credentials and cross-border data compliance, see Compliance foundation for global sites the notes on cookies and cross-border data.
| Scenario | Allow-Origin setting | Allow-Credentials | Safe? |
|---|---|---|---|
| Called only by your own frontend | Allowlist exactly your own domain | true (login state required) | Security |
| Public product data API | Not set, or * | false | Security |
| Lazy * + credentials true | * | true | Dangerous, must fix |
| Regex *.yourdomain.com | Fuzzy match | true | Dangerous, breaks the moment a subdomain is compromised |
| Multiple partner frontends | Allowlist each domain individually | true | Safe, clean out unused entries regularly |
5. Input validation and vulnerability protection: keep dirty data out
Input validation is the layer solo teams skip most often and the one with the heaviest consequences. The principle is one sentence:Never trust any data coming from the frontend or external APIs. Validate every input against an allowlist — type, length, format, enum values. If it doesn’t match, reject it outright; don’t try to “clean it up and keep using it.”
SQL injection is cured at the root with parameterized queries — never concatenate SQL strings yourself. XSS is cured at the root with output escaping: store user-submitted content in the database unescaped, then escape it by context when outputting to the page (HTML context, JS context, and URL context have different escaping rules). File uploads need an extension allowlist, renamed files, and storage outside the web directory. These are the basics of the OWASP Top 10 — there are no “advanced tricks.”
Put vulnerability scanning on a fixed schedule: auto-scan dependency lock files weekly, and within 72 hours of finding a high-severity issue (CVE score 9 or above), upgrade or temporarily take that feature offline; run a passive scan of the whole site monthly and use the results to clear out expired plugins. For how to wire up monitoring and alerts, see Site monitoring and alerts. For the security trade-offs of the tech stack itself, compare crater Tech Stack.
| Protection item | How to do it | Frequency / threshold | Red line |
|---|---|---|---|
| Input validation | Type + length + allowlist enum | Required for every API | Never validate only on the frontend |
| SQL injection | Parameterized queries / prepared statements | Code review checklist | Never build SQL by string concatenation |
| XSS | Escape output by context | Every point where user data is output | Never output raw rich text without sanitizing |
| File uploads | Extension allowlist + rename files | Required on every upload endpoint | Never let uploaded files execute scripts |
| Dependency scanning | Auto-scan lock files | Once a week | Fix anything rated 9+ within 72 hours |
FAQ
Does a standalone site really need a WAF to be secure?
JWT or API key: which one should I use?
How do I set rate limits without blocking real users?
What’s wrong with setting CORS to Access-Control-Allow-Origin: *?
Why validate input on the backend if I already do it on the frontend?
How often should I run vulnerability scans?
Final thoughts
API security for a standalone site isn’t about stacking up tools. It’s about layers: if one layer gets bypassed, the next one still holds. Single-point defense is the most fragile of all. You can configure your WAF beautifully, but if inputs aren’t validated, injections still land in your database. Of the five layers, prioritize input validation and authentication. Those two give you the most return for the least effort. Rate limiting and CORS are configuration work you can finish in half a day, and vulnerability scanning should be a routine task that runs automatically every week.
Three steps to get it done. First, add authentication to every external endpoint, and issue separate long-lived keys per partner. Second, set tiered rate limits on login and form endpoints, and keep them loose on public endpoints at first. Third, validate every input parameter against a whitelist on the backend, and add dependency scanning to your weekly tasks. Once these three are in place, your endpoints face the occasional probe instead of standing wide open. For your day-to-day maintenance rhythm, see Website Maintenance Schedule and plan them together.
One line worth remembering: API security isn’t a single wall, it’s five doors. Every door needs a lock. Miss one, and the other four might as well not be there.
