0

I am building a JavaScript application that will run in a web browser but also as a pseudo-native mobile application via Apache Cordova. It communicates via API to a separate backend service.

The app requires that the user be prompted for some kind of identifying information whenever it is launched, but for ease of use this need not be their full username and password. Password entry in particular (and especially on mobile devices) will be too cumbersome to make this feasible. Therefore, we are considering a once-off login procedure where the full credentials are supplied, followed by a “setup” step where the user creates a PIN for future access. This could, in future, be extended to allow a fingerprint/face scan to also “unlock” the app on supported devices.

We are also hoping to avoid the use of cookies. Doing so subsequently avoids CSRF concerns but also, support for cookies in Cordova applications appears to be either non-existent or at least unreliable.

My initial thoughts on implementation are:

  • The user submits a valid username/password combination to a login/ endpoint of the API and receives an “ID token” in response.
  • During the “setup” phase, the PIN chosen by the user is used to encrypt the ID token.
  • The encrypted ID token is stored in LocalStorage.
  • A secondary request is made to an authorise/ endpoint of the API, including the plaintext ID token. Assuming the token validates, a second token is issued to the app.
  • This second token is what is used in all subsequent requests to the API to prove the user is trusted, has a relatively short expiry, and is not stored in any persistent manner by the application.
  • Upon returning to the app at a later date, the user need only provide their chosen PIN.
  • It can be used to decrypt the stored ID token, which is then used in the authorise/ request to generate and return a new short-lived session token.

The internet abounds with articles advising against the use of LocalStorage for anything sensitive, due to its exposure in the event of XSS attacks. The threat is that a token in LocalStorage could be stolen when the same token in an httpOnly cookie could not. It is worth noting that in both cases, a malicious script running within the app could successfully issue fraudulent requests to the backend API.

I believe the XSS threat of the ID token being stolen is mitigated by it being encrypted under the user’s PIN, and neither the decrypted value nor the PIN itself being stored or used beyond the authorise/ request.

The session token is also vulnerable to being stolen by XSS. It is only stored in memory, but is obviously still accessible to JavaScript and thus to a malicious script. These tokens would be given short expiry times to mitigate this threat. Not to mention we would do our best to harden against XSS in the first place.

I think the above sounds like a secure way to implement our requirements, but I am no security expert. Am I missing any obvious weaknesses here? Does this actually sound secure?

oogles
  • 101
  • 2
  • I think you are missing some security measure against reply attacks. Token is encrypted but you are sending it in plain what does in your opinion stop attacker from re-sending the token? Encrypting with short PIN is like no encryption at all in my opinion. – nethero Oct 01 '19 at 06:09
  • The only concern you should be aware of is dom XSS. Have a look at the following resource: https://security.stackexchange.com/questions/174672/localstorage-vs-http-only-cookies-xsrf-is-either-better-when-it-comes-to-xss?rq=1 – Aayush Oct 01 '19 at 06:15
  • @KamilKurzynowski As I understand it, I would also have no protection against replay attacks should the token be stored in a cookie. All communication will be over HTTPS, which (again, as I understand it) is where such protection is implemented. – oogles Oct 01 '19 at 23:00
  • @oogles well I can't make any asssumptions, if the session is HTTPS then it should be safe from any replay. – nethero Oct 02 '19 at 11:58

1 Answers1

0

Storing information in localStorage is reasonably secure, but:

  • It does not expire. Data in localStorage remains there forever, which increases the period that it can be leaked or obtained. For session tokens this generally doesn't matter, since you can expire them server-side.
  • Values are not encrypted on disk. Chrome encrypts cookie values, but doesn't encrypt localStorage values. In Firefox there's no difference.

During the “setup” phase, the PIN chosen by the user is used to encrypt the ID token.

If the PIN is numeric and short, the encrypted token would be easy to brute force. So this doesn't protect access when someone has the encrypted token but not the PIN. You can mitigate this a little by using a slow key derivation function.

The threat is that a token in LocalStorage could be stolen when the same token in an httpOnly cookie could not.

I agree with you that once an attacker has XSS, he doesn't need your token anymore. However, you can get the best of both worlds by using both a token in localStorage, and a token in a HttpOnly cookie. If every request needs both, that protects both against CSRF and stolen session tokens through XSS.

but is obviously still accessible to JavaScript and thus to a malicious script

I don't know for sure whether that's true. The application could have a variable with a limited scope that is usable but not easily readable by any other JavaScript.

but also as a pseudo-native mobile application

On mobile, you should definitely try to store your token in the keystore of the device. This is by far the most secure, and protected by the OS and hardware of the phone.

Sjoerd
  • 28,707
  • 12
  • 74
  • 102
  • You make a good point re expiry. The goal is a long term token, so that's not ideal, but we will definitely implement server-side expiry. Would it help to move the encryption/decryption of the token server-side (i.e. submit the *encrypted* token + PIN to the server for validation)? An opaque token should not yield an obvious plaintext result on a successful guess, preventing offline brute forcing, and requests to the server can have rate limits and lockouts put in place (as well as a slow key derivation function). – oogles Oct 01 '19 at 23:29
  • Re using device keystores, Cordova may well support that (I do not currently know one way or the other), but we are also limited by the lowest common denominator. I'd like to do the best job we can for the web browser use case as well, without access to such a keystore. – oogles Oct 01 '19 at 23:29