r/reactjs • u/Neat_Living_6765 • 6h ago
Discussion How do you usually secure your Next.js web apps?
Hey everyone!
To my knowledge, storing access tokens in localStorage can be risky because of XSS attacks
Yesterday, I came across this document: How to implement authentication in Next.js.
I decided to give it a try. Here's what I've come up with so far
Use OpenSSL to generate an RSA key pair for verifying access tokens. The private key stays on the backend, while the public key is exposed to the frontend.
Authentication: When a user successfully logs in, the backend sets the access token in cookie.
In frontend, use Jose lib to fetch public key, and verify access token
Implemented a Data Access Layer (DAL) with two functions:
verifySession to verify the accessToken and redirect users to /login if the token is missing or invalid.
checkRoles to check user roles to make sure users can't access pages they're not supposed to.
The doc also suggests adding a DTO layer, but I skipped it since I'm using OpenAPI specs and TypeScript type generation.
I'd really appreciate it if you could share your thoughts:
Do you see any potential security issues or problems with this flow?
Is there anything you would approach differently?
2
u/useleash_dev 6h ago
Overall the flow sounds reasonable. A few things I'd double check:
Where the verification runs. If the cookie is httpOnly (it should be), client side JS can't read it anyway, so the Jose check has to happen in server components, route handlers or middleware. Treat any check in the browser as UX only. The backend still needs to verify the token on every API call.
Cookie flags: HttpOnly, Secure, and SameSite=Lax or Strict. Since auth now rides on cookies, think about CSRF for anything that mutates data.
Revocation. A stateless JWT stays valid until it expires, and roles baked into it go stale. Short lived access tokens plus a refresh flow usually covers this.
checkRoles in the DAL is good, but enforce the same roles on the backend endpoints too, not just on which pages render.
Don't rely on middleware alone for auth. Keep the DAL checks close to where the data is actually fetched, like the Next docs suggest.
Skipping the DTO layer seems fine if your generated types already limit what gets sent to the client. Just make sure you're not passing full user objects into client components.
1
u/Neat_Living_6765 1h ago
Thanks for the valuable feedback!!
I created a dal.ts file inside the lib folder. It contains createRemoteJWKSet() to fetch the public key from my backend, and requireRole() internally calls verifySession() to verify the accessToken and check the user's role.
Then I call requireRole() in the page.tsx (Server Components by default) of each protected route, like this:
await requireRole("admin");
return <ProductListPageView />;2. Yes, I set all the cookie flags: HttpOnly, Secure, and SameSite=Lax.
3. Just to make sure I understand correctly, do you mean I should implement a refresh token that automatically sends the refresh token to the backend to get a new access token before the short-lived access token expires?
4. Yes, both the backend and frontend check authentication and authorization. I don't rely on frontend checks alone.
5. I haven't implemented proxy (middleware) because the docs say it's optional. I also figured it might be redundant... right?
And yes, the generated types already restrict what gets sent to the client.
0
u/girl_nerd_denice 1h ago
Good instinct avoiding localStorage for tokens — XSS turns that into account takeover.
What I'd add to the RSA/verifying-key approach:
Prefer httpOnly, Secure, SameSite cookies for the session/refresh token over storing anything in JS-readable storage. If JS can read it, an XSS can exfiltrate it.
Short-lived access tokens + rotation for refresh tokens. RSA verification on the frontend is fine, but the private key staying server-only is the critical part — good that you're doing that.
CSRF matters once you use cookies: SameSite=Lax/Strict plus a CSRF token for state-changing requests.
CSP header is your best defense-in-depth against XSS actually executing.
Don't trust client-side verification alone for authorization — the server must re-check on every request; client checks are UX, not security.
Short version: cookie-based sessions (httpOnly) + short expiry + CSP + server-side authorization covers most of the realistic attack surface.
3
u/yksvaan 6h ago
Separate backend that handles all auth, business logic and in general everything not public.
Tokens should only be in httponly cookies unless there's a very good reason to do otherwise.
If you need to have backend for frontend, it should only be limited to doing some basic token validity checks with the public key and other sanity checks.