Always use supabase.auth.getUser() on the server side to verify identity. getSession() reads the JWT from cookies and decodes it without verifying the signature against the auth server — so if an attacker tampers with the token (changing the user ID, role, or email), your server-side code will trust the forged claims as legitimate. This is a complete authentication bypass: the attacker can impersonate any user, escalate privileges, or access data they were never authorized to see.
Why This Matters
getSession() reads the JWT from cookies without re-verifying the token with the auth server. An attacker can forge or tamper with the JWT payload (e.g., changing the user ID or role) and your server will trust it. This is a critical authentication bypass on the server side.
supabase.auth.getSession() reads the JWT from the browser's cookies and decodes it without re-verifying the token with the Supabase Auth server. This means:
An attacker can modify the JWT payload in their cookies
Your server-side code will trust the forged claims (user ID, role, email)
Authorization checks based on session.user are completely bypassable
This is not a minor issue — it is a full authentication bypass on the server side.
The rule
On the server side (Server Components, Server Actions, API Routes, middleware), always use getUser() instead of getSession() when you need to verify who the user is.
getUser() sends the token to the Supabase Auth server for verification, confirming it has not been tampered with or revoked.
Bad example
// Server Action — INSECUREexport async function deleteAccount() { const supabase = await createClient(); const { data: { session } } = await supabase.auth.getSession(); if (!session) throw new Error("Not authenticated"); // DANGER: session.user.id could be forged! await supabase.from("profiles").delete().eq("id", session.user.id);}
Good example
// Server Action — SECUREexport async function deleteAccount() { const supabase = await createClient(); const { data: { user }, error } = await supabase.auth.getUser(); if (error || !user) throw new Error("Not authenticated"); // user.id is verified by the auth server await supabase.from("profiles").delete().eq("id", user.id);}
When getSession() is acceptable
Client-side only — for optimistic UI updates where the data is not trusted for authorization decisions
Reading non-sensitive display data — showing the user's name or avatar in the navigation bar
Never for authorization decisions, data mutations, or access control on the server