Skip to main content
CodeRocket
Securitycriticalforms

Submit forms over HTTPS

rule · form-https

Every form that collects user input, whether login, registration, contact, payment, or search, must submit its data over HTTPS. Both MDN's form submission guidance (opens in a new tab) and web.dev's HTTPS guidance (opens in a new tab) assume transport security is already in place before a browser sends user data.

Code Example

A login form posting to an http:// endpoint transmits:

Text
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
 
username=alice&password=hunter2

Anyone on the network between the user and server (ISP, Wi-Fi router, MITM proxy) can read username=alice&password=hunter2.

Why It Matters

A login form that posts credentials to an HTTP endpoint sends usernames and passwords as plain text over the network. The OWASP transport cheat sheet (opens in a new tab) treats that as a fundamental confidentiality failure, because anyone on the same Wi-Fi, the ISP, or a network proxy can read those values without special tooling.

Checking Form Security

HTML Form Actions

HTML
❌ Insecure — credentials sent in plain text
<form method="POST" action="http://example.com/login">
  <input type="text" name="username">
  <input type="password" name="password">
  <button type="submit">Login</button>
</form>
 
✅ Secure — data encrypted by TLS
<form method="POST" action="https://example.com/login">
  <input type="text" name="username" autocomplete="username">
  <input type="password" name="password" autocomplete="current-password">
  <button type="submit">Login</button>
</form>
 
✅ Also secure — relative URL submits to the current page URL
<form method="POST" action="/login">
  <!-- Only secure if the page itself is served over HTTPS -->
</form>

JavaScript Form Submissions

JavaScript
❌ Insecure
const response = await fetch('http://api.example.com/login', {
  method: 'POST',
  body: JSON.stringify({ username, password })
})
 
✅ Secure
const response = await fetch('https://api.example.com/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ username, password })
})

Audit Script

Shell
# Find HTTP form actions in HTML files
grep -rn 'action=["\']http://' ./src --include="*.html" --include="*.jsx" --include="*.tsx" --include="*.vue"
 
# Find HTTP fetch/XHR calls
grep -rn "fetch\(['\"]http://" ./src --include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx"
grep -rn "XMLHttpRequest" ./src --include="*.js" --include="*.ts"

Browser Security Indicators

ScenarioChrome behavior
HTTPS page with HTTPS form actionGreen padlock, autofill works
HTTP page with any form"Not Secure" in address bar
HTTPS page with HTTP form action"Not Secure" warning may appear
Password field on HTTPAutofill disabled (Chrome 86+), warning shown

Forms Without an Explicit Action

When <form> has no action attribute, it submits to the current page URL:

HTML
<!-- Submits to whatever URL the page is served on -->
<form method="POST">
  <input type="email" name="email">
  <button type="submit">Subscribe</button>
</form>

This is safe only if your server enforces HTTPS for all requests (HTTP→HTTPS redirect + HSTS).

Exceptions

  • Local development or internal-only environments can differ, but production user-facing traffic should still satisfy the transport requirement strictly.
  • A redirect or HTTPS control that fails on one hostname, subdomain, or CDN edge path is still a real failure for users and crawlers reaching that surface.
  • Fix the strongest transport weakness first instead of treating every downstream symptom as a separate primary issue.

Verification

Automated Checks

  • Test the affected flow in a production-like environment, not just local development.
  • Document any intentional exceptions explicitly.

Manual Checks

  • Inspect the final HTTP response or browser behavior to confirm the control is actually enforced.
  • Verify third-party integrations or embeds still work after the restriction is applied.