Security Considerations
Last updated
Last updated
This is not a book on computer security, security is a very broad and complicated topic. Computer security is so broad that it can be separated into each of its specialized topic. Therefore, the scope in which we will cover security is in the context of the web. More specifically, we will be focusing on the application level of our RESTful web API.
When I was building one of my first large scale commercial applications, I really wished someone would have told me, "these are the 80% of security vulnerabilities that you need to protect against, here's a list of them". This would have saved me a bunch of stress and anxiety, and would have definitely made me stop asking the question, "Am I doing this correctly? And will I get hacked?".
In this section, that's exactly what I will be doing. I will be pointing out the top 10 security considerations and guidelines when building your RESTful web API and what you should do to go about protecting against them. The point is to not go in detail about every single possible security concern, but to rather give a general idea as to what you might need to look more into beyond the scope of this book.
The main kinds of attacks we are interested in the context of the web are (No)SQL injection, Man-in-the-middle attack (MITM), Cross Site Scripting (XSS), Cross-site request forgery (CSRF), and Denial-of-service attacks (DDoS).
Authentication determines whether someone accessing an API is really who they say they are, whereas authorization determines the level of access and the permissions a particular user has.
Whatever method of authentication you decide to use, just make sure that from the end user's point of view that proper user authentication and privileges are granted accordingly. Although this topic is vast and broad, the general rule of thumb to keep in mind here is to use common sense and to give this security consideration its due diligence.
Take the following example.
It may seem as if the isAuthenticated
middleware is doing its job, but the developer who wrote this has thought about the aspect of only allowing authenticated users to delete their accounts. The problem though is that any authenticated user may delete any account they choose.
The solution is to add another middleware after the isAuthenticated
middleware, let's call it thehasPermissionsToDelete
middleware.
SQL Injection is extremely common and should be a top consideration when building any RESTful backend systems.
Here is a typical example of SQL injection, where the hacker takes advantage of our sloppy insecure select statement and tries to delete our users table.
If look more closely and replace the post_id
variable, you will see the following statement being executed.
There are plenty of ways to defend against this type of attack, such as using prepared statements, escaping the input, or black listing input. You can find additional details in this awesome SQL Injection Prevention Cheat Sheet which gives you a bunch of options for defending against SQL injection attacks.
For our example, we can do a simple raw check on the post_id
variable, like this.
Note: there's also NoSQL Injection. Since the NoSQL movement has been quite new in the recent years, there are actually less protections against NoSQL injection versus just plain old SQL injection attacks. However, the most tiresome aspect of NoSQL databases and protecting against NoSQL injection attacks is that because there isn't a common language among NoSQL databases, protecting and testing requires knowledge specific to the database, syntax, data model, and their APIs. In fact, according to https://nosql-database.org, there are over more than 225 different types of NoSQL databases on the market. With that being said, it is therefore important to also consider protecting against NoSQL injection attacks depending on the database you choose to use in your application.
Sensitive information that you do not want malicious hackers to know about should be at the very least encrypted before they hit your database. Sensitive information can be anything from passwords, credit card numbers, and social security numbers. Whatever you choose to store in your database, it's always best to have some level of encryption of sensitive information.
Here is a great example of how most people store hashed passwords, we'll be using the built-in crypto
library in node as well. This may not be the best way of doing things, but it is a way.
On the topic of encryption, in order to prevent man-in-the-middle attacks to our API, it's important to encrypt sensitive data in the URI. Better yet, to not send any sensitive data in the URI in the first place. If you do need to leave in sensitive data in the URI, make sure to encrypt it, because TLS will not prevent hackers from sniffing and intercepting HTTP, or rather HTTPS calls, if the data is in the URI. Recall that HTTPS will only protect and encrypt information in the header, not the URI.
Below is an example of what NOT to do.
GET https://www.website.com/api/social-security-number/078-05-1120
Here is a way better version of the above example.
GET https://www.website.com/api/social-security-number/$6$FP1fYsh4CiH_rest_of_the_hash
CSRF (Cross Site Request Forgery ) requires quite an extensive amount of explanation and a little beyond the scope of this book. To learn more in depth, below is the best article I have ever read on the topic of CSRF written by auth0.com. This is especially important if you are using any sort of session based authentication in your application.
I've always been skeptical about the security of using cookies, but the main big 3 concepts about cookies in the context web security can be broken down to these 3 flags, HttpOnly, Secure, and SameSite. Below is a brief summary of these 3 flags and what their purposes are. My suggestion is to look more in depth into these 3 security mechanisms and to play around to see how they work, because simply reading about it is not enough to truly understand them.
HttpOnly flag against XSS
The HttpOnly
flag makes sure that our cookie cannot be read by arbitrary JavaScript code. If you were to go into the chrome console to any website and type into the console document.cookie
, you will notice that the ones with a check mark in the secure column will not show up. This is to protect against the ability for any remote code from executing, you can see that this is especially useful to protect cookies such as session ids.
Secure flag against MITM
The Secure
flag is quite straight forward. Essentially, it instructs the browser to re-attach cookies with HTTPS requests only (and not plain HTTP ones). This can be used to any potential mixed-content vulnerability that would leak a cookie value.
SameSite flag against CSRF
Cookies can sent to third parties with cross-origin requests. This can be abused by CSRF attacks. One way to avoid specific cookies to be sent with cross-origin requests is to set a special flag called SameSite. This will make it so that cookies can only be forwarded from the same domain origin.
Have you ever tried calling your API from the client side and you got a Cross Origin Resource Sharing (CORS) issue?
I'm talking about doing this.
Instead, a more "white-listing" approach is what you should be doing.
For better management of these headers, use the cors package.
Another key point to remember is that CORS is a browser mechanism that prevents one domain origin from access another domain origin's response output. It does not actually prevent anything happening in the background when an API is called. So if you an API endpoint that is not safe, such as deleting a user in the database, don't expect CORS to help you prevent that.
It is important to always remember to never trust the client's input. Always be on the look out to sanitize all input data given by the consumer of our API. Escaping and filtering input are the most common ways to defend against malicious user inputs.
One of the most popular libraries in the node community is the validator package.
Although general and raw techniques of sanitizing data are good, I recommend using a package such as this one to further the defensive mechanism of your RESTful API, especially when it comes to escaping data.
We just talked about how to never trust input, but you also can never trust the output. Imagine you have a list of comments on your website that you want to display to the public, those comments are of course made by the users themselves. You can decide to both sanitize the input and also escape the output when rendering the data to the screen.
There is actually a more sophisticated way of doing this "output escape" from your API, and that is using the CSP header. You can read more about it on MDN.
Here's an example, when we request a comment by an id and we want to output it as a JSON response for which the client wants to consume, we send the following header to the client Content-Security-Policy: script-src 'self'
. This header tells the browser to protect any malicious JavaScript from executing in the browser.
For more in depth look at how CSP works, I recommend this YouTube video: Content Security Policy by Kyle Robinson Young.
If you have a very popular and public application, you might want to think about protecting against DDoS attacks in order to maintain high availability of your API. The most common way to do so (at least for developers) is to add a rate limiter in your RESTful API.
Ideally, we prevent Denial-of-service attacks (DDoS) from outside the application level, so the attacker can never even reach our application. However, since this is not a book on systems administration and server configuration management, we'll just have to put a rate limiter directly in the code of our application.
For this I recommend the express-rate-limit package. Here is a simple sample code on how it works.