Common mistakes that are making your web apps insecure, and how to fix them.
Tags: security, webdev, redis
Published: 2022-09-13 21:05:48

The internet has become an integral part of our lives as humans. We rely on web apps to perform daily activities like shopping, communicating with friends and family, collaborating with colleagues, learning, and many more. A successful web app must be reliable, secure, and fast. Compromising on any of these qualities can result in a catastrophe.

In this article, I will share some widespread mistakes I’ve seen many developers make that compromise the security, reliability, and performance of web apps. Additionally, I provide suggestions on how these mistakes can be corrected. While security is a very broad topic, I’ll only concentrate on application-level security i.e the web app and not the infrastructure where the web app is deployed.

I would include some code samples, which will be written in NodeJS. However, knowledge of NodeJS is not necessary. The same principle applies to other languages.

### Table of content

* [Trusting the user](#section-1)

* [Including sensitive information in responses](#section-2)

* [Authentication flaws](#section-3)

* [Rate limiting](#section-4)

* [Multifactor authentication (MFA)] (#section-5)

* [Inadequate access control](#section-6)

* [Insufficient logging](#section-7)

### 1. Trusting the user <a name="section-1"></a>

Trust no one, especially your user. All web apps accept some form of input, for example, an email, phone number, etc. Many developers assume users would provide the correct data in the proper format. However, an attacker can deliberately enter malicious data into your web app. Consider this example.
{% gist https://gist.github.com/georgeben/f4b67421eb533f4b12c6cdf2f2a7b855.js %}
Instead of sending a legitimate email, an attacker can send a value like `{email: {$exists: true}}`. So, rather than checking if an email exists as intended, this retrieves all user accounts with an email, giving the attacker access to tonnes of sensitive information. If user inputs aren’t properly validated and sanitized, an attacker can gain access to restricted data. This is referred to as an injection attack. <br>
**Input validation best practices**
1. Perform input validation on the client (e.g the user’s browser) and the server. 
2. Use a popular validation library for your language/framework instead of writing your custom validation logic.
3. Sanitize all user inputs before using them as values in your database queries.

### 2. Including sensitive information in responses <a name="section-2"></a>

Sometimes, web apps unknowingly return sensitive data in their responses e.g a user’s password, token, etc. Sanitizing responses is as equally crucial as sanitizing user input. Make sure you are returning only data the client (user interface) needs to function correctly, nothing more.

### 3. Authentication flaws <a name="section-3"></a>

Authentication is an essential part of any web app. Authentication verifies the identity of a user. A typical web app would require users to enter an email (or username) and password before granting access to its features. A popular method of implementing authentication in web apps is [JSON Web Tokens (JWTs)](https://jwt.io/introduction). The use of JWT for authentication is prevalent because it is simple to use, compact and self-contained.

**The Problem with JWTs**
JWTs aren’t very secure. Some common flaws made when using JWTs are:

 1. Including too much private and sensitive information in a JWT: This information can easily be obtained by decoding the JWT.

![](https://cdn-images-1.medium.com/max/5760/1*tXW-ybKW1cwFfQJN1XOJgw.png)

2. Not setting an expiration for JWTs: When the developer does not explicitly set the expiry of a JWT, the JWT is valid forever. This is a serious security risk that can lead to grave consequences.

3. Failure to invalidate existing JWTs: JWTs have a fixed expiration. This means they are valid until that expiration is reached. Invalidating a JWT before it expires is complicated. If an attacker obtains a valid JWT, they can steal the identity of the user the JWT was issued to until the JWT expires.

**Implementing authentication using sessions**
Sessions provide a more secure way to implement authentication in your web app. When a user logs in, the details of the authenticated user, along with a randomly generated session id, are stored in a persistent store which could be a database. The session id is returned to the client. Every time a client makes a request, it includes the session id in the request. The server uses the session id to retrieve the user’s authentication state.

![Authentication using sessions](https://cdn-images-1.medium.com/max/2784/1*vh-1_Slhvk1xMK5T9LkoCw.png)

Since the session state would need to be retrieved on each request to the app, the session data should be stored in a fast and efficient data store like Redis. Redis is an in-memory data store that can be used as a cache, database, streaming engine, and message broker. Redis can exponentially increase the speed of session lookups, which makes your application faster. Thousands of companies serving millions of users use Redis for session storage because of its performance benefits.

The easiest way to get started with Redis is by creating an account on [Redis Cloud](https://redis.com/try-free/). Redis Cloud provides on-demand access to a Redis database in a couple of minutes.

Here’s a sample code snippet demonstrating how to implement authentication with sessions, using Redis as a session store.
{% gist https://gist.github.com/georgeben/e60f9fdac57dd05404eb4b6f70c6a7d2.js %}
Redis Insight is an extremely helpful GUI tool for interacting and managing data stored in Redis.

![Viewing sessions in Redis using RedisInsight](https://cdn-images-1.medium.com/max/5144/1*zoc2Diz0ZLlgDePUOOYVvg.png)

**Best Practices when using sessions for authentication**
1. Always regenerate sessions after a user logs in to prevent session fixation attacks.
2. Destroy a user’s session when they log out.
3. When a user updates or resets their password, destroy all active user sessions. In the code sample above, I prefixed the session id with the user’s id for this purpose. To destroy all a user’s active sessions, I retrieve all session ids beginning with the user’s id and delete them from Redis.
{% gist https://gist.github.com/georgeben/f17d6d0ab3bbbb204f162b8ed7138b5b.js %}

### 4. Rate limiting <a name="section-4"></a>

An attacker might try to gain access to restricted data by trying many combinations of usernames and passwords, with the hope of eventually guessing the correct one. This is known as [brute forcing](https://www.imperva.com/learn/application-security/brute-force-attack/).

In some other cases, an attacker might flood your web app with a huge amount of traffic, which could use up your available server resources, leaving your web app inaccessible to its intended users. This is referred to as a [Denial of Service](https://www.cloudflare.com/en-gb/learning/ddos/what-is-a-ddos-attack/) attack.

A proper strategy to secure your web apps against these attacks is rate-limiting. Rate limiting restricts the number of requests a client can make to your web app within a specific time window. For example, you can only allow a particular user to make 100 requests within five (5) minutes. When the user exceeds the allowed number of requests, all subsequent requests are rejected or delayed. The purpose of rate limiting is to improve the availability of your web app by protecting it from excessive use/abuse.

In order to implement rate limiting, we store the user’s IP address, along with the number of requests they have made in a time period. Each time a user makes a request, we retrieve the current number of requests using their IP address and check that it is not above our limit. An ideal location to store this data is in a cache — Redis. Here’s a sample code to implement rate limiting in your web app.
{% gist https://gist.github.com/georgeben/d6e9c0d11624048efdfd8f1777f0dc9b.js %}
Sensitive functions of your application such as login, password reset, etc must be rate limited.

### 5. Multi-factor authentication (MFA) <a name="section-5"></a>

Another strategy to prevent brute force and improve the security of your web app is multi-factor authentication. Instead of relying solely on one form of authentication, typically an email and password, additional authentication layers are added.

For example, after a user successfully inputs the correct email and password, an OTP (One-Time-Password) is sent to the user’s phone number. The user must then provide the OTP before being granted access to the web app. So even when an attacker has access to a user’s login credentials, they would be unable to access the user’s account. This can easily be implemented using Redis.

Here’s a code sample of how MFA can be implemented using Node and Redis.
{% gist https://gist.github.com/georgeben/81b4e9a6838be0b1d5f45ddd639e45be.js %}

### 6. Inadequate access control <a name="section-6"></a>

While authentication verifies the identity of a user, authorization determines if a user should be granted access to a specific resource or functionality in a web app. Authorization determines what a user can do. The lack of a proper authorization strategy would give users unrestricted access to data stored on a web app. How would you feel if any Amazon user could access your order history? Scary right?

Role Based Access Control (RBAC) is a possible solution to this problem. Users of your web app are assigned roles. Each role is granted a set of permissions that specify the actions allowed. Whenever a request is sent to the web app, verify that the role assigned to a user has the necessary permission to perform the requested action.
{% gist https://gist.github.com/georgeben/559b5774fe0eabf2c9a5d26bec7b58b8.js %}

### 7. Insufficient logging and monitoring <a name="section-7"></a>

Logging is to a web app what security cameras are to a building. Logging is crucial for detecting and responding to security attacks. Logs are simply messages that inform you about the state of the web app. Logs can also inform you about errors that occur in your web app. For example, logs can be written for activities like log-ins, password reset, payments, etc.

Application logs should be stored so they can be accessed. Several logging solutions exist. For example Elastic Search +Logstash + Kibana stack (ELK), Elastic Search + FluentD + Kibana stack (EFK), Promtail + Loki + Grafana stack, etc. You can even use RedisJSON to store logs and RedisSearch for analyzing logs. Do proper research and pick a solution that works best for you.

### Conclusion

The security of your web app is crucial to its success. Thank you for reading, I hope it was worth it. To learn more about how you can improve the security of your app, check out [The Open Web Application Security Project (OWASP).](https://owasp.org/)

This post is in collaboration with Redis.

** Learn more:
. [**Try Redis Cloud for free**](https://redis.info/3NBGJRT)
. [Watch this video on the benefits of Redis Cloud over other Redis providers](https://redis.info/3Ga9YII)
. [Redis Developer Hub — tools, guides, and tutorials about Redis](https://redis.info/3LC4GqB)
. [RedisInsight Desktop GUI](https://redis.info/3wMR7PR)