/lovable-issues

Fixing Token Expiration and Session Management in Lovable

Discover why session expiration occurs in Lovable and learn best practices for managing token expiry, session state, and secure token handling.

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free No-Code consultation

Why Session Expiration Occurs Without Proper Handling in Lovable

Session expiration happens because authentication artifacts (tokens, cookies, server sessions) are time-limited and your app often doesn't have the durable storage, correct origin/secret configuration, or server-side alignment in Lovable Preview/Cloud to keep them valid — so tokens simply become invalid or inaccessible and the client sees a logged-out state.

 

Why this happens (detailed)

 

  • Tokens/cookies have finite lifetimes. OAuth access tokens, ID tokens, and session cookies are issued with TTLs by the identity provider. If your app does not renew or re-authenticate before TTL ends, the provider will reject requests.
  • In-memory storage is ephemeral. If you store tokens only in React state or a JS variable, a tab reload or navigation inside Lovable Preview clears them — sessions drop immediately.
  • Preview vs published origins and cookie restrictions. Lovable Preview runs inside an iframe/origin that can change SameSite/third-party cookie behavior. Cookies that work in a local dev server or a published domain may be blocked in Preview, making session cookies unavailable.
  • Missing or misconfigured Secrets/Env in Lovable Cloud. If the app is running with no or wrong client secrets (common when you forget to set them in Lovable’s Secrets UI or when Preview uses different values), providers may issue short-lived tokens or reject refresh attempts — sessions appear to expire.
  • No background refresh/renewal task. Many apps rely on background refresh or server-side refresh endpoints to keep sessions alive. In Lovable, if you haven't implemented a page-visible refresh or server endpoint, tokens will reach expiry naturally.
  • Server-side session mismatch or immediate token revocation. If your backend validates tokens or tracks sessions and the backend state differs (e.g., you redeployed or rotated keys), tokens are rejected even if not expired.
  • Clock skew and provider revocation. If the runtime clock or provider revokes tokens (security rotation), tokens stop working earlier than expected; clients that assume long-lived validity get logged out.
  • CORS/CSP and failed silent-reconnect calls. Silent token refresh often requires a call to an auth endpoint. If CORS or CSP blocks that call (or the endpoint isn’t reachable from Preview), the refresh never runs and tokens expire.
  • Differences between local dev and Lovable. Locally you might run a dev server, use a CLI for migrations/secrets, or rely on browser flags. Lovable’s environment (no terminal, different origin, Secrets UI) exposes gaps where session persistence assumptions break.

 

Lovable-ready prompts (add only explanatory docs/UI — no token-handling code)

 

// Prompt 1: Create an explanation doc describing why sessions expire in Lovable
Create file docs/session-expiration.md with the following content:

# Why sessions expire in Lovable

Explain that tokens and cookies are time-limited, that Preview can change cookie behavior, that Secrets must be configured in Lovable Cloud, and list the common causes:
- Token TTL and provider-side revocation
- In-memory storage vs durable storage
- SameSite / third-party cookie issues in Preview
- Missing/wrong Secrets or environment variables
- Server-side session mismatches and CORS/CSP blocking silent refresh
- Clock skew

Do not add refresh or token-handling code here — this is documentation only.

 

// Prompt 2: Add a non-invasive UI notice component that links to the doc
Create file src/components/SessionExpirationNotice.tsx with a small React component that renders a short user-facing explanation and a link to /docs/session-expiration.md.

Then update src/App.tsx to import and render <SessionExpirationNotice /> in the top-level layout (for example, inside the main header or app shell), so users in Preview see the explanation when they are logged out.

Component text should explain why sessions can expire in Preview/Cloud and point to the new docs page. Do not implement any token-refresh logic here.

 

Still stuck?
Copy this prompt into ChatGPT and get a clear, personalized explanation.

This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.

AI AI Prompt

How to Handle Token Expiry and Session State in Lovable

Implement a small token manager + fetch wrapper + React session context so Lovable can automatically refresh before expiry, retry failed calls, and surface session state. Use Lovable Secrets UI to store API base URL. Treat this as a client-driven refresh flow (tokens in memory/localStorage); if you need HttpOnly cookies or server rotation, ask to add a server endpoint later via GitHub sync.

 

Lovable prompt — add token manager, API client, and session context

 

Paste this into Lovable chat. Ask Lovable to create or update the listed files with the exact code; these files use localStorage for refresh tokens and schedule silent refreshes. Adjust names if your app uses a different structure.

  • Create file src/lib/tokenManager.ts with the following content (create/update):
// token storage and refresh scheduling
const STORAGE_KEY = 'app_auth';

type Tokens = {
  accessToken: string | null;
  refreshToken: string | null;
  expiresAt: number | null; // epoch ms
};

let refreshTimer: number | null = null;

function load(): Tokens {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    return raw ? JSON.parse(raw) : { accessToken: null, refreshToken: null, expiresAt: null };
  } catch {
    return { accessToken: null, refreshToken: null, expiresAt: null };
  }
}

function save(t: Tokens) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(t));
}

export function setTokens({ accessToken, refreshToken, expiresInMs }: { accessToken: string; refreshToken: string; expiresInMs: number; }) {
  const expiresAt = Date.now() + expiresInMs;
  save({ accessToken, refreshToken, expiresAt });
  scheduleRefresh();
}

export function clearTokens() {
  localStorage.removeItem(STORAGE_KEY);
  if (refreshTimer) { clearTimeout(refreshTimer); refreshTimer = null; }
}

export function getAccessToken() {
  return load().accessToken;
}

export async function refreshTokens(): Promise<boolean> {
  const s = load();
  if (!s.refreshToken) return false;
  try {
    const res = await fetch((process.env.NEXT_PUBLIC_API_BASE || '') + '/auth/refresh', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken: s.refreshToken }),
    });
    if (!res.ok) return false;
    const { accessToken, refreshToken, expiresInMs } = await res.json();
    setTokens({ accessToken, refreshToken, expiresInMs });
    return true;
  } catch {
    return false;
  }
}

function scheduleRefresh() {
  const s = load();
  if (!s.expiresAt || !s.refreshToken) return;
  const msUntilExpire = s.expiresAt - Date.now();
  const refreshBefore = Math.max(5000, Math.floor(msUntilExpire * 0.2)); // refresh at 20% before expiry or 5s
  if (refreshTimer) clearTimeout(refreshTimer);
  refreshTimer = window.setTimeout(() => { refreshTokens(); }, Math.max(1000, msUntilExpire - refreshBefore));
}

// call on app load
if (typeof window !== 'undefined') {
  scheduleRefresh();
}

 

  • Create file src/lib/apiClient.ts with this fetch wrapper (attach tokens, refresh on 401):
// api fetch wrapper that retries once after refresh
import { getAccessToken, refreshTokens } from './tokenManager';

export async function apiFetch(input: RequestInfo, init: RequestInit = {}) {
  const token = getAccessToken();
  const headers = new Headers(init.headers || {});
  if (token) headers.set('Authorization', `Bearer ${token}`);
  headers.set('Content-Type', headers.get('Content-Type') || 'application/json');

  let res = await fetch(input, { ...init, headers });
  if (res.status === 401) {
    const ok = await refreshTokens();
    if (ok) {
      const t = getAccessToken();
      if (t) headers.set('Authorization', `Bearer ${t}`);
      res = await fetch(input, { ...init, headers });
    }
  }
  return res;
}

 

  • Create file src/context/SessionContext.tsx to expose session state and helpers:
// React session provider and hook
import React, { createContext, useContext, useEffect, useState } from 'react';
import { setTokens, clearTokens, refreshTokens, getAccessToken } from '../lib/tokenManager';
import { apiFetch } from '../lib/apiClient';

const SessionContext = createContext<any>(null);

export function SessionProvider({ children }: { children: React.ReactNode; }) {
  const [user, setUser] = useState<any>(null);
  const [loading, setLoading] = useState(true);

  async function loadUser() {
    const token = getAccessToken();
    if (!token) { setUser(null); setLoading(false); return; }
    try {
      const res = await apiFetch((process.env.NEXT_PUBLIC_API_BASE || '') + '/me');
      if (res.ok) {
        setUser(await res.json());
      } else {
        setUser(null);
      }
    } catch {
      setUser(null);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => { loadUser(); }, []);

  const signIn = async ({ accessToken, refreshToken, expiresInMs }: any) => {
    setTokens({ accessToken, refreshToken, expiresInMs });
    await loadUser();
  };

  const signOut = () => {
    clearTokens();
    setUser(null);
  };

  const value = { user, loading, signIn, signOut, refreshTokens };

  return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>;
}

export const useSession = () => useContext(SessionContext);

 

  • Update file src/pages/\_app.tsx (or your root app file) to wrap app with SessionProvider: add inside the component return ...

 

Lovable prompt — add environment secret

 

Open Lovable Cloud Secrets UI and set a secret:

  • Key: NEXT_PUBLIC_API\_BASE
  • Value: https://api.yourdomain.com (or your backend base URL)

 

Use Preview to exercise login, then simulate short expiry by setting expiresInMs to a small value on signIn. The apiFetch wrapper will refresh automatically before expiry and retry on 401. If you need server-side rotation with HttpOnly cookies, tell me and I’ll produce a GitHub sync plan because that requires backend code/dependency installs outside Lovable.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Best Practices for Managing Tokens and Sessions in Lovable

The short answer: keep all sensitive tokens out of client code, store them in Lovable Cloud Secrets, move token logic into server-side code (server endpoints or serverless functions), expose only minimal session checks to the browser (via HttpOnly cookies or small-safe session flags), and use Lovable’s Preview/Publish + GitHub sync for any changes that require deeper platform access.

 

Immediate best-practice checklist for Lovable

 

  • Never hard-code tokens in client files. Put them in Lovable Cloud Secrets.
  • Do token logic on the server-side (server endpoints/serverless functions inside repo). Client should only call those endpoints.
  • Use HttpOnly cookies or server-set session flags so the browser JS cannot directly read secret tokens.
  • Use Lovable Secrets UI to add environment variables and reference them from server-side code (process.env names).
  • Test with Preview and only Publish when you confirm the server endpoints read Lovable Secrets correctly.
  • Export to GitHub if you need platform-specific server configuration or run server tooling locally (outside Lovable).

 

Prompts to paste into Lovable chat (copy-paste each to implement)

 

// Prompt A: Create a server-side session endpoint that uses Lovable Secrets
// Create file src/server/session.ts with this code and wire it into the project's server routing.
// If your framework uses pages/api or /api routes, create pages/api/session.ts instead.
// This endpoint must read process.env.SESSION_SECRET (created in Lovable Secrets) and never expose raw tokens.
export async function handler(req, res) {
  // // Server-side: use process.env.SESSION_SECRET (set via Lovable Secrets UI)
  const secret = process.env.SESSION_SECRET;
  // // Use secret to validate or sign session info - keep raw tokens off the client
  // // Return only a safe session flag or set an HttpOnly cookie
  res.setHeader('Set-Cookie', `session=1; HttpOnly; Path=/; SameSite=Lax`);
  res.json({ ok: true, authenticated: true });
}

 

// Prompt B: Move client code to call the server endpoint and keep no secret reads in client files
// Create src/lib/authClient.ts and update components to call /api/session for auth checks.
// Do not persist raw tokens in localStorage/sessionStorage.
export async function checkSession() {
  const resp = await fetch('/api/session', { credentials: 'include' });
  return resp.json();
}
// // Update UI components to call checkSession() on mount and rely on the returned authenticated flag.

 

// Prompt C: Add Secrets in Lovable Cloud
// Instruct the user to open Lovable Cloud > Secrets (UI) and add:
// - SESSION_SECRET  (set to your signing key or API token value)
// - OPTIONAL_API_KEY (if you must talk to an upstream service from server-side)
// Note: do not add secrets as client-side env variables. Use only from server code (process.env.*).

 

// Prompt D: Preview and publish
// After applying code changes and adding Secrets, run Preview in Lovable to validate endpoints read secrets.
// If everything passes, click Publish to deploy the changes.

 

Notes and constraints

 

  • No terminal in Lovable: if your change requires running build scripts or adding server platform config that Lovable cannot do, export/sync to GitHub and perform those steps locally or in CI (label: outside Lovable — terminal required).
  • Secrets naming: match the process.env names used in server files exactly to the keys you create in Lovable Secrets UI.
  • Preview often: Preview validates that server code can access Secrets before you Publish.


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.