Don’t leave password around in SPA

Nicolas Goy
The missing bit
Published in
2 min readJun 10, 2017

There is one thing I have noticed with SPA (single page app) login forms. Many of them keep the password in memory. And when I say in memory, I don’t mean as freed() memory which may require freezing your PC to access, I mean as actual living object within the javascript state.

For example, given the following pseudo code:

state = {
login = {
username = ""
password = ""
}
currentPage = "login"
}
onFormInput =>
state.login.username = usernameInput.value
state.login.password = passwordInput.value
onFormSubmit =>
loginRequest state.login then =>
state.currentPage = "home"

After this kind of code, on successful login, the user credential stays in memory at state.login. The credentials will also stay in memory if the user is idle. This can happen if the user has a password manager with auto-fill, opens the page, the password manager fills the form, but then nothing happen. The credentials just stay in your state. This is a minor issue, but you are never too careful.

This problem also exist with regular forms to some extent, but here the attack vector lies more in the complex javascript libraries we load all over the place.

Imagine this, the user login to your blogging SPA, once logged in, the credentials stay in memory. Your blogging SPA has a plugin API, where people can write plugins. You didn’t sandbox the plugins correctly, and they have access to your state. Now, in addition to some temporary access token (which is already bad), they have access to the user’s unencrypted password and username.

My suggestion is simple, while the form is submitted, replace the password in the input by a string of the same length, this way, when the authentication is in progress, the password field is not empty and the number of dot didn’t change. After successful login, wipe the state.

My other suggestion is to empty the password field after a minute of inactivity.

In pseudo code:

onFormInput =>
state.login.username = usernameInput.value
state.login.password = passwordInput.value
clearTimeout resetPassword resetPassword = setTimeout 60, =>
state.login.password = ""
onFormSubmit =>
local temporaryCredentials = state.login
state.login = {
username = temporaryCredentials.username
password = "." * length temporaryCredentials.password
}
loginRequest temporaryCredentials then =>
state.currentPage = "home"
state.login = { username = "", password = "" }
# temporaryCredentials should get out of scope here
# also make sure you are not holding to requests objects
# containing the credentials

Keeping the username in the state may be ok, as I’m quite sure it is somewhere else in your state, it depends on how sensible your app is (with something like plugins, you may consider keeping only some kind of display name around and not the email).

--

--