I am always quite surprised to hear about frontend application security because precisely a frontend application runs on the user's device and thus cannot be secured. It must even be considered as a potentially malicious client.
Indeed, the source code of the application being at the disposal of the client, it is possible to study and modify it at will in order to understand its internal mechanisms or to recover all the data stored on the device.
I came across many articles from various sources (Callstack, Jscrambler, Tabris, Nativescript, Reactnativecode) that detailed techniques for securing a frontend application using obfuscation, custom encryption (XORing with reuse of key, etc ...), and so on.
This is not the first time we hear recipes about "writing a secure frontend application": It can be dangerous to try to secure an application with inefficient techniques. Here is why.
- Do not trust frontend applications
- Do not roll your own crypto
- Do not use predictable encryption keys
- Do not reuse the same key
- Obfuscation is not security
- Secure your backend instead
Sending authentication information is done securely through an SSL connection, so the confidentiality of communications is ensured in most cases.
Adding a basic encryption layer for the password with a simple XOR and a reused key for each client authentication is unnecessary for several reasons:
- reusing of the key for each client and each request makes encryption vulnerable because it is possible to guess the message with a frequency analysis
- since the key is stored in the device, you only need to download the application to know it
Adding an encryption layer may be a good idea to avoid data compromise in the case of a Man In The Middle attack with a fake SSL certificate.
But for strong encryption, it will be necessary at a minimum to:
- use a key the size of the password
- use a different key for each user and each request
- negotiate this key with an asymmetric encryption algorithm of the Diffie Helman type
This is more or less the same as implementing a custom version of SSL in your application but remember that ruling your own crypto is never a good idea.
When you try to reinvent the wheel
It is much easier to use other security techniques such as SSL Pining to prevent MITM attacks.
Storing sensitive data
When it is necessary to store sensitive data in a frontend application, it is preferable to use the mechanisms provided by the creators of the development environment.
For example, in a mobile application with React Native, developers can use Apple's Keychain or Android's Keystore. These mechanisms make it more difficult to extract sensitive data from a device, but they should not be considered totally secure either (e.g. Apple Keychain exploit).
In any case, it is not necessary to add an additional encryption layer made with a predictable key because an attacker can reverse engineering the application and re-generate the key.
Or even easier, simply access the key stored in memory.
Moreover, adding a custom encryption layer in a frontend application is counterproductive and will consume unnecessary CPU resources for encryption/decryption and thus drain the battery.
Obfuscate the source code
Although I can understand that developers may want to make reverse engineering of source code more difficult, obfuscation must never be considered a security practice.
At most it can discourage some attackers but someone motivated can always reverse engineer the code.
Secure your backend instead
As we have seen, a frontend application cannot be secured. Since it is impossible to have control over the client's device, it is impossible to ensure that it is not compromised.
It is in the backend that most of the security elements must be put in place.
There is no magic recipe for securing a backend, it is the set of good programming practices that will allow you to achieve an optimal result.
Never trust user input
From the body of an HTTP request, to headers or cookies, all information that can be manipulated by the user must not be trusted.
Naive utilization of user input can lead to all kinds of attacks:
It is always necessary to check and sanitize input data before using it in an application.
Denial of Service attacks attempt to make an application unavailable.
It is possible, for example, to send very large JSON payloads, which can impede or even freeze your application.
Mitigate the attack: restrict the payload size at the lowest layer, preferably directly in the network layers.
If your backend is written in Node.js, it is also necessary to be vigilant about when creating new Promises.
Indeed, a Promise is automatically sent to the Event Loop and it is then impossible to withdraw it before its resolution or rejection.
It is therefore possible for an attacker to send a lot of requests on a route generating Promises to saturate the Event Loop.
Mitigate the attack: implement a concurrent request limit system using only callbacks. Developing with callbacks is not fun, but callbacks are just pointers to methods using no resources until invoked. Use promises only after the request limit system.
Prevent brute force
In order to prevent a brute force attack on user authentication, it is necessary to introduce a limit to the number of connection attempts.
This limit may take the form of a block after X attempts added to the authentication route.
Securely store password
In 2019, there are still companies that store their users' passwords in plain text.
This practice must be avoided at all costs in order to protect your users in the event of a data leak from your application. Not only for your own application: the majority of users reuse the same password for many accounts. The impact of a password leak can be catastrophic, both for your users and for your company's image.
Passwords must be stored using a one-way cryptographic function or hash function.
The choice of the hash function should be based on a robust algorithm such as bcrypt for example. If you can, make weak passwords stronger by using key stretching, and for an added security layer, you can also salt and pepper passwords.
There are a lot of possible attacks on a backend, and most of them are little or not known.
For example, a simple comparison of two strings can result in a timing attack vulnerability and allow an attacker to guess a password or token.
But also the use of a well known regular expression library vulnerable to a ReDoS attack.
This is why securing a backend is not a task to be taken lightly and must be entrusted to security experts to train developers but also to audit the code to ensure that there are as few vulnerabilities as possible because perfect security does not exist.
When developing an application, security must be taken into account from start to finish, and reflection must cover the entire scope of the application from the backend to the frontend, including communication channels and hosting.
Security is expensive and is therefore often overlooked. This is why it is preferable to use frameworks and backends designed by engineers with the necessary skills and knowledge to ensure sufficient security for end users.