How we redesigned Devfolio's authentication system

In this article we will discuss why we made the change, how the old system worked, the changes we made to incorporate the new authentication system, the challenges we faced, and how it benefits you as a user.

Motivation

We used to use JWTs with a stateless API for making requests in the frontend. Initially, when we built Devfolio we used to store these JWTs in localstorage when the user logged in or signed up and sent them with every subsequent request in the request headers.

Although localstorage is very versatile and provides very simple APIs for getting and storing the data, it comes with its own limitations like being accessible only on the domain it was created on. This worked fine initially with the hacker dashboard on devfolio.co as a standalone application, but when we added the organizer dashboard on org.devfolio.co, because of the nature of localstorage we couldn't access any authentication info of the user saved on the hacker dashboard from the organizer dashboard, so we ended up maintaining its authentication in isolation as a separate app. This was not ideal because you could log in to the hacker dashboard with one account and then had to log in again to access the organizer dashboard, this was bad UX.

The problem was more prevalent when it came to the hackathon microsite in which every hackathon has its own individual subdomain(E.g. - https://inout.devfolio.co ). We couldn't implement the isolated authentication method as we had for the organizer dashboard here, because then every hackathon microsite would end up having its own authentication, which wouldn't be ideal. But since the information on this page was mostly public so we ended up showing the logged-out state regardless of whether a user was logged in or not -

The only downside was the user needed to login every time they wanted to apply to a hackathon from this page even when they were logged in. Again this was not good UX.

Lastly, localstorage can also be slightly insecure as it can be prone to XSS since the tokens are readily available to JavaScript. But this is debatable because if your site is prone to XSS then even if you don't store your tokens in a JavaScript accessible storage a hacker can still add code in the script that makes a request to your server, check out this talk for a detailed explanation.

The new authentication system

Looking at the problems mentioned above we decided to rewrite the whole authentication architecture for Devfolio using HTTPOnly cookies. So now instead of storing the tokens in localstorage and manually sending the tokens in the header of every request, we are storing them in cookies, and the browser sends them with every request.

With cookies we solved a major issue that was limiting us with localstorage, i.e., sharing authentication across sub-domains. Cookies have a domain attribute that allows them to be scoped to be available on certain domains or sub-domains. So we set the domain for our cookies as devfolio.co making it accessible on all of our subdomains.

We also marked the cookie HTTPOnly, which makes them invisible to JavaScript on the client browser, only allowing them to be set or destroyed by the server. The contents of the cookies are also, signed by the server, rendering them useless if tampered with. These cookies are also marked as secure allowing them to be only transmitted over HTTPS connections.

And finally, we have added a SameSite attribute with the value as strict. This allows the browser to only send the cookies with the request when the user is on a devfolio.co domain thus preventing any CSRF attacks.

Since they are HTTPOnly cookies, they are set by the server when a user logs in or signs up, are sent with every subsequent request automatically, and are destroyed when the user logs out or their token in the cookie isn't valid anymore, causing them to be redirected to the login page.

Hence apart from solving the UX issues, this new system is also slightly more robust and secure.

The challenges

During local development, we faced quite some issues because of the way browser cookies work.

The way we did local development was, spin up the frontend application, which is connected to a development API server (it runs on a .devfolio.co domain and can be deployed via a slackbot).
With localstorage, it worked seamlessly because we only had to attach the JWT to the request headers. But with cookies, this flow broke for us here, as the cookies were not being set during local development since -

  1. There was a domain mismatch (localhost for the frontend app / .devfolio.co for the API server)
  2. The cookies are marked as secure, thus not accessible over the localhost

So we reduced the strictness of our cookies for the local development environment by setting the SameSite attribute to None (allowing the cookies to be sent even when the API server and the URL in the browser are on different domains) and set the Secure attribute to false. The only problem is this doesn't work in modern browsers because "Cookies with SameSite=None must also specify Secure, meaning they require a secure context".

Finally, we ended up spinning up our backend API server locally, which also runs on localhost, thus giving us the ability to use the same attributes for local development that we have for production except for the Secure attribute. Although we see tremendous potential in using docker images as a means of running our backend server locally.

If you are interested in learning more about how these cookies work, then definitely check out SameSite cookies explained by Rowan Merewood.

How do these changes benefit the end-user

The biggest change for the end-user is that they can now login once and then visit any Devfolio subdomain without needing to log in again. This also allowed us to add the same profile dropdown now accessible from all the Devfolio subdomains.

We also have only a single sign-in page now, which handles all the redirection to the appropriate page if the user is not logged in.

The hackathon microsite also has slightly improved UX by allowing one-click apply if you are already logged in and haven't applied to the hackathon, instead of asking you to log in every time you apply to a hackathon.

It also now shows the proper state on the button depending on whether you have applied to a hackathon or not. For instance, since I have already applied to ETHOdyssey, it shows me 'Go to Dashboard' whenever I visit the microsite of this hackathon again.

Ending Notes

That's the piece of how we moved our authentication system to work with cookies for an overall better UX, if there's something you think we could have done better, then please write to us at engineering@devfolio.co.

Have a wonderful day 👋