Why this post
Searching for answers when dealing with web authentication can be challenging.
I researched this topic some time ago, and one thing I noticed is that there are a lot of useful resources and informations, but they're spread across Stackoverflow answers, blog posts, online documentation, Reddit posts, and so on.
Some posts compare bearer token VS cookies, some other explain how cookies might be more secure than tokens, some others go into session authentication, others deal with cookie attributes and CORS and so on. This requires you to jump from page to page and might lead to lose some useful informations regarding security practices or caveats, just because you did not read that Stackoverflow answer that for some reason is not marked as the correct one!
What I think is missing is a guide that starts from the beginning, explains what are cookies and what are their pros, while also considering some cons and difficulties when implementing them (CORS? Third party cookies? Partitioned what?).
So this is the first post of a serie that I called "Webauth Wizardry", that I hope will help other developers to understand and build a system that provides authentication via http cookies.
In general, I'll try to explain things as I went throught them, by trying solutions and finding which one has the most advantages (at least in my opinion), and to note down what disadvantages you might have with cookies, and eventually some solutions.
This serie will cover the following topics:
- Brief comparison of bearer token VS cookie auth
- Cookie fundamentals: lifetime, attributes, sending rules
- Basic web authentication: how to authenticate users and keep them authenticated
- Sign in with email/pw: I'll start with a brief explanation, but this topic might be expanded later
- Sign in with OpenID Connect: Build on top of Oauth2, OIDC allows an application to authenticate users via third party services (called providers) like Sign in with Google. This topic has some key consideration and caveats, especially regarding requests coming from different origin and some difficulties that will happen when managing our cookies
"I like your funny words magic man"
Since we're programmers and we like code, I published two repository on my Github account:
- The finished Webauth Wizardry code, consisting in a main part under
src
folder, along with a testing setup. I also published this as an npm package, in case someone wants to reuse it (some work is still needed) - A FE application, something as minimal as possible to demonstrate how to integrate all of this (and also how unobtrusive the cookie solution is on FE)
Blue pill or red pill?
First thing first, let's state something that is not always clear:
Tokens and cookies serve different purposes and they're not mutually exclusive
Questions like "Do I need to use tokens or cookies in my application?" might be misleading:
Tokens represent someone
Tokens are strings that represent a user (or an application) in some way. They might be just an identifier that corresponds to some user data saved on DB (like a mapping token => user), or they might contain that same data themselves as encoded JSON web tokens (jwts). A signin mechanism is applied when the jwt is created (aka issued) to ensure that the token cannot be modified by someone else (or at least, without invalidating the sign and thus invalidating the token).
Since this token identifies a user and grants him permission to access protected resources, this is called an Access Token
(AT).
A never expiring AT is not a good idea:
- You want to control if a user is still allowed to access a resource, for example after logout of when banning a user
- Someone stealing your AT might be able to operate on your behalf forever on that application
For these reasons, an AT is usually short-lived (expiration might be set even after 10 minutes), and is paired with a so-called Refresh Token
(RT).
Refresh tokens do not directly identify a user, but rather allow to exchange an expired (or expiring) AT with a new one. This operation is called "token refresh
", and when succeded returns both a new AT and a new RT, invalidating the provided ones. This also means that RT are single use only.
Access Token might be passed by Authorization Bearer or Cookie
Now we need a way to pass the AT (and RT if needed) back and forth from our BE and FE.
We can use:
-
Authorization: Bearer MY_TOKEN
HTTP header - Custom cookie
Which one to use? They both have pros and cons:
- Authorization bearer are more flexible than cookies, as that they can be passed without much browser restrictions. Also, they can be set only on request that actually need them. On the other hand, they must be manually handled by the FE, which needs to keep track of both AT and RT, place the AT on the header on every request, handle manually refreshing the token before it expires, and save those tokens somewhere. In addition, since they're available to client side Javascript, they might be stolen by an attacker that manages to execute some custom code on the victim's browser.
- Cookies are more "transparent" to client, meaning that when handled correctly, they cannot be accessed client side, so their entire handling occurs on BE. On the other hand, browsers enforce more strict policies on cookies, they're sent on every request (even if not needed) and they're subsceptible to other forms of attacks, like CSRF. Thus they require a precise configuration to be used correctly for authentication purposes.
Session cookies
The combination of simple (non encoded) tokens and cookies might be called the "session authentication", or "session cookies". This is sometimes confused with simple cookies, but as seen before, cookies does not exclude jwt tokens.
Session cookies are just cookies containing a token that identifies the user. The user's data is then recovered from the token on the server side, which might store data in-memory or on a fast DB, for example Redis might be a good option.
I'll take the red pill
After considering all the possibilities, I choose to go with jwt tokens, to prevent retrieving user info from a db on every request, and cookies instead of Bearer Token, in order to eliminate all possible handling from the FE.
Let's see how deep does the rabbit hole go.
Ending considerations
Feel free to comment for any question, error correction, or just to say hi. Also, you're welcome to visit my Github profile and open some issues or pr!
This is one of my first posts, hope you liked it, and thanks for reading.
Other resources (not sponsored :))
- See this Stackoverflow Answer
- Also this Stackoverflow Question
Used in this article
- Cover image designed by Freepik
- Morpheus's choice image generated with Imgflip
Top comments (0)