📄 SKILL.md

← Vault

name: lovable-vps-oauth-debug

description: Debug OAuth login loops on Lovable/TanStack Start deployed to VPS — exchangeCodeForSession, .env overwrite, JWT secret rotation

category: devops


Lovable/TanStack Start VPS OAuth Debug

Problem

Google OAuth login on TanStack Start app deployed to VPS loops back to login screen without errors.

Root Causes Found (trial & error)

1. Lovable broker OAuth is dead on VPS

lovable.auth.signInWithOAuth() opens broker.lovable.dev/~oauth/... which returns 404 on VPS.

Fix: Replace with native Supabase signInWithOAuth():

`tsx

await supabase.auth.signInWithOAuth({

provider: "google",

options: { redirectTo: ${window.location.origin}/auth, queryParams: { prompt: "select_account" } }

});

`

2. getSession() alone does NOT exchange OAuth code

When Google redirects back to /auth?code=XXX, getSession() returns no session because the code hasn't been exchanged yet.

Fix: In the /auth route's useEffect, detect ?code= and call exchangeCodeForSession(code) FIRST:

`tsx

useEffect(() => {

const url = new URL(window.location.href);

const code = url.searchParams.get("code");

if (code) {

window.history.replaceState({}, "", url.pathname); // clean URL

supabase.auth.exchangeCodeForSession(code).then(({ data, error }) => {

if (data.session) navigate({ to: "/" });

});

return;

}

supabase.auth.getSession().then(({ data }) => {

if (data.session) navigate({ to: "/" });

});

}, [navigate]);

`

3. Vite rebuilds .env from vite.env.* source files

After npm run build, Vite regenerates .env from vite.env.* files. Any direct edits to .env are LOST.

Fix: Edit the source files (vite.env.production, vite.env.development) or the build will overwrite .env.

4. Anon key returning "Invalid API key" = JWT secret was rotated

If PostgREST returns {"message":"Invalid API key","hint":"Double check your Supabase anon or service_role API key."}, the project's JWT secret was regenerated after the keys were generated.

Fix: Get fresh keys from Supabase Dashboard → Project Settings → API. If the dashboard itself is inaccessible due to broken auth, use the management API to regenerate:

`bash

curl -s -X POST "https://api.supabase.com/v1/projects/{ref}/dashboard/generate-link" \

-H "Authorization: Bearer {SBP_MANAGEMENT_TOKEN}"

`

Auth Flow

1. User clicks "Entrar com Google" → signInWithOAuth() → Google OAuth

2. Google redirects to https://YOURDOMAIN/auth?code=XXX&state=YYY

3. useEffect catches ?code=, calls exchangeCodeForSession(code)

4. Supabase validates code + state + redirect_uri

5. Session stored in localStorage, navigate({ to: "/" })

Key Files