In this portfolio, I use localStorage for one small decision. Remembering whether someone chose the light or dark theme.
localStorage.setItem('theme', theme);If that value disappears, the website still works. If someone changes it in their browser, nothing important changes. It does not identify anyone, grant permissions or provide access to an account.
That detail helps me decide when localStorage makes sense. I do not see it as a browser safe or something that should always be avoided. It is persistent storage that JavaScript can access within the same origin. That works well for a visual preference. A session or secret brings a completely different risk.
The short answer is this. localStorage is for data that can live in the browser without assuming authentication or authorisation. It should not hold passwords, session identifiers, access tokens, refresh tokens or private information you would not want every script on the page to read.
A website theme is not a session
The value used by my portfolio can only be light or dark. The code reads it on page load and applies the matching theme.
const theme = localStorage.getItem('theme');
if (theme === 'light' || theme === 'dark') {
document.documentElement.dataset.theme = theme;
}It is deliberately limited. Even if someone changes that value in DevTools, the outcome is a colour change. No server trusts it to make a sensitive decision.
A session is the opposite. An access token may let someone read data, change an account or act as another person. If it is in localStorage, any JavaScript running on that origin can try to read it.
The key name does not need to be obvious for this to matter. Encoding the value or hiding it behind a function does not help either. The browser exposes the information to page JavaScript because that is how the API works.
XSS is the scenario that changes the conversation
The main concern is not someone opening their own browser console and seeing their own data. People can always alter what lives in their own browser.
The serious problem starts with XSS. An injected script running inside your origin has the same access to localStorage as your legitimate JavaScript. It can read values, send them elsewhere or change data your interface later tries to use.
That connects two decisions that are sometimes studied separately. XSS may begin as a rendering problem, but the damage grows if it finds a ready-to-use token in browser storage.
In XSS explained for frontend developers, I wrote about the difference between displaying data as text and parsing it as HTML. This question comes next. If something can run code on my page, what information can it access?
A CSP does not solve this by itself. A content policy can limit certain scripts and remains a useful layer. It does not turn a token that JavaScript can access into a safe value.
What I would store and what I would avoid
The boundary is not only about whether a value “looks important”. It depends on what someone can do with it and what happens if it is copied, changed or lost.
| Data | Does localStorage make sense? |
Why |
|---|---|---|
| Light or dark theme | Yes | It is a local preference with no access attached. |
| Cookie notice state | Yes | It remembers an interface choice. |
| Last open tab | Yes | It improves continuity without changing permissions. |
| Session token or JWT | No | JavaScript can read it if XSS exists. |
| Refresh token | No | It can extend or renew a compromised session. |
| Password or PIN | No | Browser storage is not where credentials should persist. |
| Personal or payment data | No | It adds exposure and should not depend on client-side security. |
| User role or product price | Not as a source of truth | A user can change it in their browser. |
The final row is not only about confidentiality. A role or price may not be secret, but the server cannot trust a value received from the client. The browser can suggest an action. The server must check permissions, prices and business rules.
sessionStorage does not fix the security issue
sessionStorage has a shorter lifetime. It is normally cleared when the tab or page session closes. That can be useful when a value only needs to survive one specific interaction.
It is still available to JavaScript on the same origin. Moving from localStorage to sessionStorage reduces persistence. It does not stop XSS from reading the value while the page is open.
I would use it when the data lifecycle calls for it, such as a temporary navigation preference. I would not use it to protect a sensitive token.
Cookies solve a different part of the problem
When an application needs a session, cookies can send an identifier to the server without page JavaScript needing to read it. With HttpOnly, scripts cannot access that cookie. With Secure, it only travels over HTTPS.
That reduces the risk of directly stealing a session through XSS, although it does not remove XSS or every authentication problem. Cookies also bring SameSite, CSRF and the configuration of each flow into the picture.
In CSRF explained with forms and cookies, I cover that other side. Moving a token out of localStorage is not a “secure mode” button. It changes the threat model and requires other decisions to be reviewed.
The right choice depends on the architecture. I would avoid storing a token in localStorage simply because reading it from fetch is convenient.
Reviewing a project without panicking
If I inherit a frontend or return to a project after months away, I would start by searching for localStorage.setItem and sessionStorage.setItem.
Then I would look at three things
- What value is actually stored.
- What someone can do with that value.
- What would happen if a script from the same origin could read or change it.
A theme, language or component state needs a different response from a session. Separating those cases avoids two common mistakes. Storing secrets for convenience and treating every Web Storage use as a security alarm.
I would also review what happens when those values are read. Browser storage can be changed, come from an earlier version of the app or have an unexpected format. Checking for light and dark before applying the theme is a small example of the right idea. Client-side data is not a source of truth.
Why this feels like a frontend decision
It can sound like a backend concern because it involves sessions. The decision often begins in frontend work when someone needs to persist a value and localStorage is close at hand.
Knowing what remains accessible to JavaScript helps avoid turning a convenient solution into security debt. It also makes you distinguish between saving something to improve the experience and saving something the system uses to trust a person.
The portfolio theme is a useful reference for me because it is a small, verifiable use case. It does not prove an entire application is secure. It does keep the right question in front of me. What happens if anyone who can run JavaScript on my origin can read or change this value?