Secure JWT authentication against both XSS and XSRF (Vue.js + Django REST)

Modern Single Page Applications and Progressive Web Apps use JWT tokens for authentication. A security concern remains: Where should the token be stored? Lots of manuals recommend using LocalStorage. That’s a huge risk because it is vulnerable to XSS. Some recommend using cookies, however, they are susceptible to XSRF. The solution? Double submit cookies. Let’s review the fundamentals and see how to implement it in Vue and DRF.

JWT

How can you verify that a request comes from the real user and not an attacker impersonating him/her?

The JSON Web Token solution fits modern development paradigms because it is stateless, signed, and has an universal data format. You get it upon login, and send it along any request that requires authentication.

Once you login, the JWT token must be stored somewhere for the FrontEnd to use it. LocalStorage is many times suggested, but since it is a JavaScript API, it is open to XXS attacks. Now, onto proper implementation.

 

 

 

XSS attack

Cross Site Scripting can happen if you fail to sanitize the data inputs and outputs, but also if content in a CDN or a dependency is compromised. If someone managed to insert this code in your page, they would be able to execute anything they wanted from JavaScript:

<img src="http://url.to.file.which/not.exist" onerror=alert(document.cookie);>

This example just reads the cookies, but they could send them back to the attacker’s server too. LocalStorage can be read just as easily from the same injected code, so if the authentication or other sensible data was stored there, it could be easily grabbed.

 

 

If cookies can be read in XSS and LocalStorage can be read too, what can I do?

Secure and HttpOnly cookies can not be read from JavaScript, and can only be transported through https, so that the content can not be sniffed. Therefore, we will use this kind of cookies to store the JWT token. That way, in case of XSS, the JWT token can not be read by the malicious script.

 

Code snippet (DRF):

If you are using Django DRF, you can enable this behavior by using REST framework JWT Auth and adding the ‘JWT_AUTH_COOKIE’ setting:

JWT_AUTH = {
  ...
  'JWT_AUTH_COOKIE': 'jwt_auth_token',
  ...
}

 

 

 

CSRF attack

This kind of attack can be relatively easily executed. If a malicious website www.badguys.com has a link or a hidden form pointing to your site like this:

http://yoursite.com/transfer.do?acct=BadGuy&amount=100000

When the user is tricked into sending that request, the authentication cookies of yoursite.com will also be sent attached to the request, so the request will be validated and 100.000 funds will be transferred to BadGuy.

 

When I noticed this risk, I googled around and found that I was not the only one who had realized:

How can we prevent this?

By using a csrf token. This token will be required by the backend in order to validate any request. As explained before, in an XSRF attack, the JWT token cookie we had set will be attached, partially validating the request. Now, to complete the validation, we need something that can not be accessed in an XSRF. Here, cookies come to help again.

Cookies can only be read from the domain they were set, badguys.com can not read cookies from yoursite.comWe have to store a csrf token in a cookie that belongs to yoursite.com, and include the token value in the headers of every request that you make. That way, XSRF attacks will fail since they can not complete the request because they can not read the csrf token value.

 

 

Fixing the vulnerability 

Django has a very useful csrf middleware. Due to the reasons explained above, it should be employed when storing the JWT token in a cookie, so as to not let your application exposed to XSRF.

I have modified the django-rest-framework-jwt package and submitted a pull request. Hopefully when you are reading this it will be already merged. Otherwise, you can clone my repository and use that meanwhile:

    bmpenuelas/django-rest-framework-jwt

 

Now, all that you have to do is enable the ‘CSFR_COOKIE’ setting:

JWT_AUTH = {
  ...
  'JWT_AUTH_COOKIE': 'jwt_auth_token',
  'CSFR_COOKIE': True,
}

 

Code snippet (Vue):

In Vue, you can create an Axios interceptor that will automatically add the csrf token value from the cookie to every request:

axiosInstance.interceptors.request.use(
  (config) => {
    config.headers = {
    ...config.headers,
    'X-CSRFToken': getCookie('csrftoken')
  }

  config.withCredentials = true

  return config
  },
  (error) => Promise.reject(error)
)

 

 

 

Footnote

This approach will prevent the authentication itself, or session, from being stolen, and will avoid CSRF. Still, XSS is a huge security hole. Extreme care must be taken to ensure that your application is protected against XSS, because once an attacker gets to run JavaScript in your domain, pretty much anything can be done as if it was done by the real user.

 

References:

 


https://twitter.com/GinsideG/status/988157049019289600