We've all seen the debates about localStorage vs. cookies when it comes to handling client-side JWTs. You may choose to store your JWTs in one or the other depending on which article you read. But what does an XSS attack actually look like?
The Open Web Application Security Project (OWASP) defines XSS as:
Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites.
I'm going to use a simple error page that users are redirected to if they encounter an general error. I've seen this used many times (hopefully a little better than what I'm about to show!)
Note: Let's assume that our site authenticates users via JWT and stores them in
Here's our beautiful error page:
message parameters to display in the page like so:
The code that handles displaying the message looks like:
const params = new URL(document.location).searchParams const errorCode = parseInt(params.get("code")) const errorMessage = params.get("message") document.getElementById("error-code").innerHTML ="Error code: " + errorCode document.getElementById("error-message").innerHTML = errorMessage
Can you spot the mistake? 😏
We are getting the error message from the URL and placing it into our document HTML... 🤔
Uh-oh! This confirms to the attacker that this page is vulnerable to an attack called Reflected XSS.
With some creativity, it's not a huge leap to get the contents of your local storage (which includes your JWT) and send it off to the attacker... bye bye token!
Once the attacker has your token, it's trivial to reveal all information stored in that token. They are just base64 encoded objects.
The main issue with our code is that we are getting the message string from the URL and inserting it directly into our document HTML. Instead, we should:
- Sanitise anything that could come from the user (including URL parameters).
A good tip is Don't store anything in the JWT you wouldn't already consider public. This way, even if your site happens to be vulnerable to XSS, the attacker isn't gaining any private information.
There is nothing wrong with storing JWTs in
localStorage. The issue is with poor coding practices that have the potential to expose your site and users to attack.
Granted, this was a simple (and contrived) example of reflected XSS, but there are other DOM-based attacks your app may be vulnerable to.
It's fun to break things you're working on and see if you can patch any vulnerabilities before they make it out!
Here are some good places to learn more:
Have fun! Thanks for reading! 😃