Things I wish I knew when building an authentication system (Part 2)

Great authentication system comes great responsiblity. If I am going to implement my authentication system, it needs to be secure.

This is the second part in the series Things I wished I knew when building an authentication system.

Step 3: Implementing security practices

As I said, you need to be responsible for the security of the authentication system. You should not write the system and hope that it does not go wrong.

One of the best resources I can refer to is the Open Web Application Security Project (OWASP).

There are a lot of security measures to consider when designing an authentication system. I will include some of the most famous ones.

undraw hacker mind 6y85

Some myths when it comes to security

HTTPS does not mitigate attacks

Although it is helpful to have HTTPS on your website to encrypt data transmission. It can indeed help protect users from various attacks such as man-in-the-middle, where the data transmission is intercepted and modified.

JSON Web Token, so hot right now

There are lots of advertisements about how it prevents modification of data and such. However, in reality, it does not really protect the user from everything.

Furthermore, it is also very misunderstood. If you look up How to create an authentication system, almost half of them is about How to use JSON Web Token for authentication.

I see most of them set the token in the cookie. It is not how it goes!!! Some even inject every piece of data into the token. What if the token gets leaked? Your users' information will be in the hands of bad people.

I recommend this post. It does a good job in the spirit of expressing my frustration.

Prevent CSRF attacks

Cross Site Request Forgery (CSRF) is a type of attack that makes use of the user session.

In the old days, every time a user wants to do something, he or she has to reenter the password, which is very inconvenient. To tackle this problem, instead a session is established when the user to login to the website so that he or she does not have to sign in again. While the session is active, the user can make changes to their account by making requests to endpoints while attaching the session token (usually in the form of cookie).

However, an attacker can manage to include a script in the web application that makes requests on user behalf. A script can be in the form of an image, making it easy to trick the user. For example, the attacker can create a post on www.verysecuredapp.com/forum/post/averycutecat. Vic, who loves cat, go to see the post. What Vic does not know is that the post includes an image:

<img height="0" width="0" src="www.badboyx123.com/badscript.js">

What special about it is that the image is not visible since its dimension is 0 x 0. The browser will try to load everything on the page. It will load badscript.js even though it is included as an image.

It does not necessary for the script to be included as an image. Request can be made cross-origin. For example, the attacker can trick the user into visiting www.averyattractivelookingsite.com, which include a script that make requests to endpoints. As long as it sets request.credentials to true. The browser will still include user credential in the request even though it originates from untrusted location.

The badscript.js will then make requests to www.averysecuredapp.com/api/user/delete. The next thing Vic sees are not cast but his account deletion.

CSRF Token

One way to prevent CSRF is to implement a security token. In every form, a CSRF token is attached as a hidden field.

hen a form is rendered, it is rendered along with a randomly generated token.

<form method="post" action="/api/user/delete">
  <input type="hidden" name="csrfToken" value="EFUjo5SBOE2N21m9hOB0AStgVy5iKgsUpCfPDMuSCB6GDlEFKPnVbm8/>
  <input type="submit" value="Delete my account"/>
</form>

When the Vic actually wants to delete his account, he will click the button in the above form. The csrfToken is sent along with the request, which will be verified when it reaches the server. badscript.js's request will not contain the above token and thus be rejected.

Obviously, the CSRF token needs to cryptographically secure. It should not be easy to guess or brute-force.

Verifying origin with standard headers

Every request carries along specific information that tells the server about its origin.

Two common headers to check against are Origin and Referer. With the two headers, the server can see that the request comes from www.averyattractivelookingsite.com and reject it.

When the server grants the sessionId as cookie, it may set the Samesite attribute. This attribute forces the browser to reject sending along with the cookie if the request is not originated from the trusted domain.

This requires the Cookie to be configured properly. Doing so is as easy as append some text to the end of the Set-Cookie header. Set-Cookie header in response is how server set the cookie when a user signs in. It goes like this:

Set-Cookie: sessionId=secret

Setting Samesite attribute will require the server to send this instead.

Set-Cookie: sessionId=secret;SameSite=Strict

More information can be found here.

For more information on how to mitigate CSRF, see OWASP guide.

Cross site scripting (XSS)

Cross site scripting (XSS) is very similar to CSRF. It describes the method of including malicious scripts on vulnerable websites.

If your website has a blog, chances are it has a search bar. When the user makes a search, he/she will be redirected to an URL like the one below:

www.averysecuredapp.com/search?q=Picture+Of+Cats

On the destination, users should see something like:

Search results for "Picture Of Cats"

Its HTML is

<p>Search results for Picture Of Cats</p>

Picture Of Cats was copied from the query in the URL!

What if an attacker crafts an URL:

www.averysecuredapp.com/search?q=<script>hack()</script>

and somehow trick the user to click on it.

He or she will be shown a page with the following content:

<p>Search results for <script>hack()</script></p>

Every <script> tag will be executed by the browser. In this case hack() is executed.

Sanitize the input

It is important to sanitize the input

In this case, <script>hack()</script> will be escaped (Special characters like < > will be replaced). The html of the webpage will now be:

<p>Search results for &lt;script&gt;hack()&lt;/script&gt;</p>

The browser will not executed hack().

Do not rely on the framework

Many frameworks inform developers that they take care of Input Sanitization. We should still be careful nevertheless.

As introduced, cookie is a mean to store a user's session. If the attacker can get access to the cookie, he or she can hijack the user's session. A malicious script can call document.cookie to retrieve the cookie.

HttpOnly attribute forces the browser to make the cookie inaccessible from client-side scripts. Doing so by:

Set-Cookie: sessionId=secret; HttpOnly;

As an addition protection, also set the Secure attribute to forces HTTPS connection.

Set-Cookie: sessionId=secret; HttpOnly; Secure;

Endnotes on security

Read OWASP guide, just do it!

Security is not simple seriously. Refer to the guide to learn how to secure your app.

https://www.owasp.org

Conclusion

With the above introductions, I hope I can get you started to write your authentication system. It may take a lot of effort into implementing your own, and it is alright to go with third-party service. I personally want to challenge myself and create my own. However, if I use it in production, I will need to work a lot on making sure that it is safe and convenient for my users.

You are visiting the previous version of this website.

Move to the new site