Authentication

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

Great authentication systems come with great responsibilities. 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.

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

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 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 are 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 reading the post Stop using JWT for sessions. 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, 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 cookies).

However, an attacker can manage to include a script in the web application that makes requests on the user's behalf. A malicious script can be in the form of an image, making it easy to trick the user into executing it. 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 is special about it is that the image is not visible since its dimension is 0px × 0px. 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 includes a script that makes requests to endpoints. As long as it sets request.credentials to true, the browser will still include user credentials in the request even though it originates from an untrusted location.

The badscript.js will then make requests to www.averysecuredapp.com/api/user/delete. The next thing Vic sees is not a cat 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.

When 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 Vic 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 request will not contain the above token and thus be rejected.

The CSRF token needs to be cryptographically secure. It should not be easy to guess or brute force.

Verifying origin with standard headers

Every request carries along with 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.

Samesite Cookie Attribute

When the server grants the sessionId as a 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 appending some text to the end of the Set-Cookie header. Set-Cookie header in response is how a server sets 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

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 searches, 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 HTML:

<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> must 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>

With the above, 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.

Secure cookie

As introduced, a cookie is a means 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 added protection, also set the Secure attribute to force HTTPS connections, which prevent man-in-the-middle attacks.

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

Endnotes on security

Read OWASP guide, just do it! https://www.owasp.org

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

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 a 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.