DEV Community

Kamran Khan
Kamran Khan

Posted on

My /me endpoint is my refresh button: restoring the session after every page reload

I store the Sanctum token in localStorage. That survives a page reload. React state does not. So on every reload my app knows it might have a session, but it knows nothing about the user — no name for the sidebar, no role for the route guards, nothing.

My fix is boring on purpose: one endpoint, GET /api/auth/me, and one useEffect that runs before anything else.

useEffect(() => {
  const stored = localStorage.getItem('saas_token');
  if (!stored) {
    setLoading(false);
    return;
  }
  client
    .get('/auth/me')
    .then((res) => {
      setUser(res.data.user);
      setToken(stored);
    })
    .catch(() => {
      // Token is dead (revoked or forged) — throw it away.
      localStorage.removeItem('saas_token');
      setUser(null);
      setToken(null);
    })
    .finally(() => setLoading(false));
}, []);
Enter fullscreen mode Exit fullscreen mode

Three decisions hiding in there:

1. No token, no request. If localStorage is empty we skip the network call entirely and drop straight out of the loading state. A logged-out visitor never pays for a request that can only fail.

2. The endpoint returns the same UserResource as login. me() responds with ['user' => new UserResource($request->user())] — the exact same user shape the frontend got at login. There is no second code path for "logged in via reload".

3. A failed restore cleans house. A 401 on /me means the token is revoked or forged. We delete it from localStorage instead of keeping a zombie token that would fail every subsequent request and confuse the 401 interceptor. The user lands on the login page with a clean slate, not a broken session.

The loading flag matters more than it looks. My protected route waits on it — a spinner gate I wrote about earlier — so a refresh never flashes the login page for a user who is about to be restored. Restore first, route second.

Small, unglamorous, and every SPA with token auth needs it. This pattern lives in the Laravel + React SaaS Starter Kit I maintain, where the auth context, the /me endpoint and the route guards ship wired together: https://kamranofficial.gumroad.com/l/mhekoig

Top comments (0)