wingaught
wingaught

Reputation: 31

Persisting user state in sveltekit

I'm trying to hook up a Strapi backend to a SvelteKit frontend, and stuck on how to persist user login state so that everything doesn't just reset on refresh, or when navigating to a new page. I've tried:

  1. Storing the jwt and user object issued by Strapi in localStorage and initializing the Svelte store with it. Seemed like I was getting close, but a) I couldn't do export const user = writable(localStorage.user) because that code was running in the browser, and I couldn't wrap it in an if (browser) {...} because import and export can only appear at the top level. Also tried a function in hooks.js to read the contents of localStorage and update the store, but it seems that functions getting called from there run on the server, even if it's the same function that works to access localStorage on login... and plus b) from what I gather, storing jwt's in localStorage is insecure.
  2. Storing the jwt and user object in an http only cookie. Cookies and http headers seem really confusing, and I had a hard time manipulating them to store the jwt and put it in each header. But I think what really stumped me was essentially the same SSR issue of never knowing essentially how to successfully interface between the client and server. I.e. if (browser) {...} never seemed to work, or I couldn't get it to, anyway. (Happy to provide more code details on what I tried here if needed. It's a mess, but it's saved in git.)

I know this is a thing every app that has users needs to do, so I'm sure there's a way to do it in SvelteKit. But I can't find anything online that explains it, and I can't figure it out from the official docs either.

So am I missing something easy? (Probably.) Or is there a tricky way to do this?

Upvotes: 3

Views: 5451

Answers (1)

nstuyvesant
nstuyvesant

Reputation: 1516

For a SvelteKit SPA authenticating to Strapi, here's the happy path flow I would use:

  1. SvelteKit page /routes/login.svelte collects the username (identifier) and password and a button's on:click handler posts those values via fetch to your own /routes/auth.js:post() endpoint. Why? Because now your server's endpoint is handling the login on behalf of the user so you can set an httpOnly cookie in the response.
  2. In your auth.js endpoint post() method, you will want to do a few things:
    • Use fetch to post identifier and password to Strapi authentication (http://localhost:1337/auth/local) to get the JWT (response.body.jwt)
    • Use fetch to get user info from Strapi (http://localhost:1337/users/me). In the get, add a header called 'Authentication' with a value of 'Bearer ' + the JWT you just received from Strapi in the previous step
    • Return the user info from the response from Strapi and pass it back to your client in the response.body
    • Set a header httpOnly cookie with the value of the JWT.
  3. Back on the client in the last part of the login button's on:click handler, take the user info from res.json() and put it in a writeable store called user or in the SvelteKit session store...
import { session } from '$app/stores'
...

const loginClickHandler = async () => {
  ...
  const fromEndpoint = await res.json()
  session.set({ user: fromEndpoint.user })
}

At this point, you have the user information persisted client-side and the JWT as an httpOnly cookie that can't be read or modified by client-side JavaScript code. Each request you make to your own server's pages or endpoints will send the JWT cookie along.

If you want to logout, call an endpoint on your server (/auth/logout) that sets the existing jwt cookie to have an Expires based on the current date/time:

response.headers['Set-Cookie'] = `jwt=; Path=/; HttpOnly; Expires=${new Date().toUTCString()}`

You would also want to clear the user object in your store (or the session store).

The main takeaway for the above example is that your client would never directly talk to Strapi. Strapi's API would be called only by your SvelteKit server's endpoints. The httpOnly jwt cookie representing your session with Strapi would be included in every request to your server's endpoints to use/validate with Strapi's API (or delete if expired or the user logged out).

There are lots of other approaches but I prefer this one for security reasons.

Upvotes: 5

Related Questions