saving authentication token in cookie (Django Rest Framework + React)

I forgot my old answer was still up on the internet getting some traction... and it's partly wrong, as I realized later.

Updated Answer:

I thought about storing the token in a cookie, but that doesn't seem very safe.

It's safe enough, if done correctly. Yes, the cookie will still be readable by anyone having physical access to the system. More on this below.

Let's make one thing clear, It is absolutely necessary to store some data on client-side (browser in this case), and send this data with API calls to authenticate users. Let's call this data "token".

When you send this token in API calls, anyone having physical access to the system can view it. Also, there's no point of encrypting it because...

  1. You'll need to decrypt it before sending, which makes the unencrypted string readable.
  2. You'll need to store the decryption key on client-side as well, which brings us back to the same problem.

What are the options? Honestly, let it be. Most websites work like this (almost). Unless the website holds some very sensitive information. In that case, look into time-based passwords, hardware-based passwords, bio-metrics maybe? These methods mostly passes on the responsibility of safely keeping the keys to user.

You can of course, make it more secure. Here are some tips:

  1. Remove token from server after a certain period of time/inactivity.
  2. Update token on random requests, and invalidate previous ones.
  3. Allow users to view active sessions, and removing them.
  4. Bind it to user IP, or something hard-to-replicate. Every time user logs in with different IP, ask for password.

With that in mind, let's talk about storing the token.

We just need to make sure other websites, malicious scripts, and software cannot access the stored token. (The user can always read the token, as mentioned above.)

You can either store it in cookies or localStorage. Both work fine, but localStore is designed to store larger data. Cookies can store upto 4096 bytes of data - which is enough for storing token. Cookies also help when working on SSR (server-side rendering). Though, it can get tricky to handle cookies in React. (Tip: Try next.js, it has built-in support for cookies and SSR with React.)

You can also specify expiration time in cookies, if that helps.

TL;DR: Using Cookies is perfectly fine. Just use it properly.

Thanks to @ShayanSalehian for pointing this out: LocalStorage is subject to XSS and cookie is subject to CSRF. So I think using cookies + CSRF is the most secure way even in TokenAuthentication for storing tokens on client...


Old Answer (Spoiler alert, it's partly wrong.):

This is something I had to deal with as well. Lost a few nights' sleep in the process.

Disclaimer: I'm not any expert in security. Just a bit obsessed (read: paranoid).

Short version (to answer your question): I finally ended up using window.localStorage to store the token. Though I'm not convinced myself that it's the best thing to do, but it's not just about the "storing" part - read long version to understand more.

Long version: First, let's clarify a few things. React is more like a mobile app than a webpage/website. I'm not talking about React Native - I mean React.js.

Why am I saying it's more like a mobile app than a website? Traditional websites usually use session based authentication, for which browsers/servers are typically prepared. It's a no brainer and seamless task apparently.

In a mobile app (or client-side standalone app), you need to maintain some sort of token to essentially tell the server "Hey, it's me! I visited a while ago. Here's my identity card. Let me in, please?". The problem is, it's hard to keep the token secure at client end. Android itself didn't provide any secure way to store authentication token until Android v4.3. That too wasn't secure enough, so they introduced hardware-backed keystore a while ago. This is the reason why some apps didn't (and still don't) work with rooted devices. Read more about this here: https://stackoverflow.com/a/19669719/3341737

Compared to a React/standalone web app, Google (somewhat) controls Android client-end. It's comparatively easier for them to implement hardware-based keystore. In case of web app, there are tons of browsers, with hundreds of versions and whatnot.

Coming back to window.localStorage. Similar to Cookies, localStorage is isolated for each domain. Since it's a newer API, it's designed in a better way than good old Cookies.

There's no point in encrypting the key (you may obfuscate it though), as you'll need to store the decryption key somewhere locally as well. So if someone can get access to the token, they can access decryption key as well.

Second aspect of this issue (and why "storing" isn't the only problem) is - from whom do you really want to protect the token?

  1. Man in the middle? Use SSL.
  2. Other websites? They can't access localStorage of your domain.
  3. Some person? True. They can get token easily if they have physical access to the PC. Having physical access practically makes them the user (you want to protect the token from the user?). Considering that the person has physical access, you cannot protect the token even if you somehow securely store it.

Why not? Because you'll need to send the token with each request - and data sent with every request is available in browser network inspector. So regardless where and how you store the token, it can be stolen by someone with physical access to PC.

Why not Cookie? Two reasons (1 actually):

  1. Cookie is sent with every request by default. You really don't need this because you only need to send token during API calls (not page loads). Also, since you're using DRF (applicable to any RESTful API backend) you probably need to send token is a specific way to server - which require custom method any way.
  2. window.localStorage is a neat little API to work with (just window.localStorage.setItem('key', 'value')). Also, the maximum size restriction is much higher as compared to Cookies.

Thus, window.localStorage seems a feasible option to me. Enlighten me if you have a better solution.

That being said, this does not mean you can't improve the security. Here are a few suggestions:

  1. Invalidate/delete the token from DB after a certain period of time or after a certain period of inactivity.
  2. Send updated token (also invalidate previous one) with requests randomly enough, and replace stored token with new one.
  3. Give user access to delete their account's saved tokens along with last activity of each token.
  4. If you're really (really) concerned, bind token with user IP (or something else which can't be cloned to another system). If user logs in from a new device/location/browser use 2FA.