avatarJen-Hsuan Hsieh (Sean)

Summary

The context discusses the best practices for storing JSON Web Tokens (JWT) in a Single Page Application (SPA), comparing the security and functionality of storing JWT in browser memory, LocalStorage, and HttpOnly Cookies with CSRF tokens.

Abstract

The author of the context, who develops an SPA application using Django/DWF and JWT for authorization, shares their experience and research on the best practices for storing JWT. The author compares three methods for storing JWT: browser memory, LocalStorage, and HttpOnly Cookies with CSRF tokens. While browser memory is the safest place to store JWT, it does not persist after a browser refresh. LocalStorage, although commonly used, is vulnerable to Cross-Site Scripting (XSS) attacks. The author recommends using HttpOnly Cookies with CSRF tokens as the best method for storing JWT, as it provides a balance between security and functionality. The author also discusses the use of CORS whitelist and CSRF tokens to protect against Cross-Site Request Forgery (CSRF) attacks.

Opinions

  • Browser memory is the safest place to store JWT, but it does not persist after a browser refresh.
  • LocalStorage is vulnerable to XSS attacks and should not be used to store JWT.
  • HttpOnly Cookies with CSRF tokens provide a balance between security and functionality for storing JWT.
  • CORS whitelist can help prevent certain types of CSRF attacks, but it cannot prevent internal CSRF attacks.
  • CSRF tokens should be used to protect against CSRF attacks.
  • The author recommends using HttpOnly Cookies with CSRF tokens as the best method for storing JWT.
  • The author suggests setting CORS policy and CSRF token mechanism on the server to ensure security.

Where should we store the JWT for SPA? Memory, Cookie, or LocalStorage?

Introduction

I develop an SPA application on Django/DWF recently. I used JSON Web Token (JWT) to authorize users for login and other operations. It’s Okay for me to create endpoints with JWT secure, exchange JWT with social medias’ access token.

However, the security issue is a critical issue for JWT. Where should we store the JWT? I tried some ways and wrote this note.

It includes the following topics.

  • The safest place: Browser’s Memory
  • Should we store JWT in the LocalStorage?
  • Double tokens policy: HttpOnly Cookie + CSRF token
  • Summary

1. The safest place: Browser’s Memory

Browser’s memory like states is definitely the safest place to save. However, the application couldn’t persist the JWT if the user refresh the browser.

So we still have to consider to store JWT to the cookie or the localStorage.

2. Should we store JWT in the LocalStorage?

It’s common to save JWT in the localStorage and set it in the request header when the APIs authorizes the requests with JWT.

Store JWT in the localStorage / send requests with JWT

// Save JWT in localStorage form the memory
localStorage.setItem("token", token);
// Send the request with JWT
const headers = {
    Authorization: `JWT ${token}`
}
const updateProfileRes = await axios.post('/api/profile/', {
    body: data
},{
    headers: headers
});

However, people don’t recommend to save JWT in the localStorage. The security reason for localStorage is Cross-Site Scripting(XSS). Attackers can use JavaScript to manipulate data in the localStorage.

3. Double tokens policy: HttpOnly Cookie + CSRF token

The HttpOnly tag for Cookie is one of solutions to defend XSS. The HttpOnly tag will restrict users to manipulate the Cookie by JavaScript.

It’s the reason people recommends us to save JWT in the HttpOnly Cookie instead of the localStorage.

Send response with JWT in the Cookie for Django/DRF

// Edit settings.py
JWT_AUTH = {
    ...
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
    'JWT_ALLOW_REFRESH': True,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    'JWT_AUTH_COOKIE': 'token'
}
//Edit views.py
...
response = Response(serializer.data)
response.set_cookie('token', serializer.data['token'], httponly=True)
return response

And we won’t have to do other things in the client-side.

However, the HttpOnly Cookie causes another threat — Cross-site request forgery (CSRF). Attackers can retrieve Cookie by sending requests to the website.

Can we use CORS whitelist to protect Cross-site request forgery (CSRF)?

Can we use CORS whitelist to protect CSRF? That’s review the Same-origin policy.

  1. Urls have the same schema, domain, and post
  2. The cross-domain requests from JavaScript are usually limited like XMLHttpRequest or Fetch API
  3. The cross-domain requests from HTML are usually unlimited like <script>, <img>, <link>, <iframe>, <video>
Copt right@A Layman

CORS does help prevent certain types of CSRF attacks from external sources. But it can’t prevent internal CSRF attacks.

It can restricts users to call endpoints from XMLHttpRequest(AJAX) andFetch. But users still call endpoints in HTML tags(img, script, etc).

Set Cross-Origin Resource Sharing (CORS) in Django/DRF

// Install packages
pip install django-cors-headers
//Edit settings.py
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)
CORS_ORIGIN_WHITELIST = [
    ...
]
CORS_ORIGIN_REGEX_WHITELIST = [
    ...
]
MIDDLEWARE = [
    ...
    'django.middleware.common.CommonMiddleware',
    'corsheaders.middleware.CorsMiddleware'
]

Use CSRF token in Django/DRF

To defend CSRF, we can use the CSRF token with JWT. The server can response with CSRF token in the cookie.

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
         ...
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
         'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication'
   ),
}

The requests without the CSRF token will be rejected.

Summary

I thought there are a few things we have to do with JWT. Suppose that the client-side and the server-side are in the same domain.

  • Store JWT in the HttpOnly Cookie
  • Set CORS policy on the server
  • Set CSRF token mechanism on the server

References

Summary

Thanks for your patient. I am Sean. I work as a software engineer.

This article is my note. Please feel free to give me advice if any mistakes. I am looking forward to your feedback.

Please feel free to clap if this article can help you. Thank you.

You can also subscribe my page on Facebook.

Related topics

How to use the two-way binding in Knout.js and ReactJS?

Learn how to use SignalR to build a chatroom application

My reflection of :

IT & Network:

Database:

Software testing:

Debugging:

DevOps:

Django
Python
Jwt
Software Development
Single Page Applications
Recommended from ReadMedium