I'm trying to understand the best way to setup JWT auth for a React/Node project but everything I'm reading is contradicting and I'm really having a hard time to understand it.
For the client I'm using a fresh create-react-app with Axios for API calls.
For the server, I'm building a REST API using this boilerplate: https://github.com/hagopj13/node-express-boilerplate. It already has JWT auth built in and calls to /v1/auth/register returns your access and refresh token.
What I'm having trouble with is figuring out where to store these tokens? Localstorage is the 'easy' option, and makes sense to me, but everywhere I'm reading is advising against this because of XSS and CSRF vulnerabilities. The recommended alternative is an HTTP only cookie. This makes some sense to me, I'd have to just modify the server to return the token as a secure cookie and include it in the credentials with further calls. Thought it gets a little tricky testing localhost and with samesite considerations since my API and frontend will be hosted separately. Finally, I've heard you can store the access token in your react state and your refresh token in localstorage. Not sure how this one is secure, I think the idea is only the refreshtoken is vulnerable to XSS but that seems worse to me.
So if someone could ELI5 the best way for me to go about this, I'd very much appreciate it!
I had the exact same confusion when I first implemented JWT auth.
A simple way to think about it is:
Access token → short-lived (minutes / ~1 hour)
Refresh token → longer-lived
For storage, a lot of teams avoid localStorage because of XSS risk. A pretty common pattern is:
• store the refresh token in an http-only cookie
• keep the access token in memory (React state)
• refresh it when it expires
That way the browser automatically sends the cookie, and the access token isn't permanently stored in the client.
JWT auth becomes much easier to reason about once you separate those two roles (access vs refresh).
It can feel overcomplicated at first, but you're actually already on the right track.
It's the approach I took (http-only cookie rather than localstorage) for the reasons you gave.
You can use http://www.passportjs.org/packages/passport-jwt-cookiecombo/ to help out.
Generally you're on the right track. However you don't need to include the credentials in api calls (like you do when dealing with localstorage), because it will be sent with each call (as it's a cookie).
When developing locally I disable 'secure' (when doing res.cookie in node) as I didn't want to spend any more time on it.
If you do want to go this route secure in development, it's probably a bit more googling, but ultimately nothing to do nodejs. I speculate the approach is to put nginx in front of it (locally installed or via docker) with some cert keys (self signed maybe?). TBD! :)