Don’t forget to POST

Modern best practices in both React and Angular make a dangerous omission: the method attribute on form elements. This can lead to accidental logging of user credentials.

It is now considered standard to hijack a form’s submit behavior in these frameworks using onSubmit or ng-submit, and most code examples don’t even bother to give their form element a method. This is because the JS function that hijacks the submit behavior takes care of the form’s job using an XHR request; the form element is mostly just there for semantic reasons.

This is documented best practice for the top two JS frameworks:

<form> with no method and an onSubmit callback, React documentation

Here are high-search-ranking tutorials showing how to make a login form in React. They all use forms with no method, which have password inputs.

Here’s a similar tutorial for Angular:

The Problem

As the W3 spec will tell you, the default value for method is GET, not POST. Over the years it’s become clear that this is usually not desired behavior for an HTML form, but we are stuck with this default.

That’s a problem when combined with these so-called best practices, because server-side rendering for React and Angular is becoming easier and more common in recent years thanks to tools like Next.JS and Angular Universal. This means that there’s a lot of code out there written under the assumption that the login form wouldn’t exist if the client-side JavaScript had thrown some exception and failed to run.

Adding server-side rendering to this existing code breaks that assumption; the server might render your components just fine while the client-side script doesn’t run or throws an exception. If you omit method you’ll wind up serving a GET form to users and logging their passwords when any of the following is true:

  • The script throws an exception before the submit override can bind to the form
  • The script throws an error inside the submit override, before the override can call e.preventDefault()
  • You have a visitor with JavaScript disabled.

We discovered this by chance when reviewing some changes to the Cryptowatch login UI – our React components rendered fine in the Next.JS server but JavaScript threw an exception locally due to a failed network request. When I clicked “Log in” the page just refreshed with my username and password printed in clear text in the URL bar as query parameters.

The Solution

Avoiding this is simple: just don’t omit method! Make sure the form still submits as a POST as a regular old form, with no JavaScript enabled.

It would also be nice if web browsers intercepted GET forms that contain a password field and show a warning dialog to the end user, similar to how they do with unencrypted HTTP connections. Maybe someone on the Chrome team is reading? 🙂

Are you a security conscious web developer? We’re hiring!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s