Two Auth Bugs With the Same Root Cause: The Secondary Path Was Never Updated
TLDR: A sensitive credential showed up in server access logs as a URL query parameter. A user sign-out left the server-side session actlive for up to an hour. Both issues were in secondary code paths—, a backup proxy and a sign-out function—, that were never updated when the main versions were improvprimary implementations were hardened.
Bug 1: Sensitive Credential in URL Query Parameter
Symptom
A polling endpoint was being called every 5 seconds during a sign-in handshake. The request URL contained ?secret=Y. That URL was written to server access logs on every poll request.
Why It Happens
URL query parameters are part of the request path. They show up in server access logs, CDN logs, load balancer logs, and browser history. Any credential in a URL query parameter ends up in at least four places before an attacker even tries anything.
The right way to send credentials is with the Authorization: Bearer <token> request header. Headers are not included in standard access logs.
The Fix
// wrong: credential in URL
const secret = url.searchParams.get('secret');
target.searchParams.set('secret', secret);
// correct: credential in header const secret = request.headers.get('Authorization')?.replace(/^Bearer\s+/i, '').trim(); return fetch(target.toString(), { headers: { Authorization: Bearer ${secret} }, });
The main proxy for this endpoint was already set up correctly. The backup proxy, though, was copied from an older version before the header-based method was used. It relied on the query parameter approach everywhere.
Guard
Before deploying any proxy or forwarding function that handles a secret or token, search the code for searchParams.set, ?secret=, or ?token=. If a credential shows up in the URL anywhere in the request chain, it will end up in the logs.
Lesson
Secondary endpoints must go through the same security review as primary ones. Fixing the main path does not mean every path is fixed.
Bug 2: Logout Did Not Clear the Server-Side Session Cookie
Symptom
After clicking Sign Out, the UI showed you were signed out and localStorage was empty. But if you sent a request to the authenticated API endpoint with the old session cookie, you still got a successful response for up to an hour.
Why It Happens
An HttpOnly cookie is hidden from JavaScript on purpose, and the browser makes sure of this. That’s the security feature. As a result, you can’t read, write, or delete an HttpOnly cookie from JavaScript. If you only clear localStorage on the client side, the HttpOnly cookie stays there.
The only way to expire an HttpOnly cookie is to have the server set Max-Age=0 on it. This means you need to make an HTTP request to the endpoint that manages the cookie.
The sign-out function cleared all Supabase localStorage keys and called the identity provider's sign-out. It felt complete because the client-side state was empty. But the server-side session was still live.
The Fix
function signOut() {
// Clear the server-side cookie first. This is not optional.
fetch('/api/auth/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'clear' }),
}).catch(() => {});
// Then clear client-side state identityProvider.auth.signOut().catch(() => {}); clearLocalStorageTokens(); resetAuthState(); }
The server replies with Set-Cookie: gx_session=; Max-Age=0, which tells the browser to expire the cookie right away.
Guard
Every logout path in any authentication flow must include a server round-trip to clear the HttpOnly cookie. To test: sign in, open browser DevTools, go to Application and then Cookies, click Sign Out, and check that the session cookie is gone.
Lesson
Only the server can clear HttpOnly cookies, not the client. Logout is not finished until Max-Age=0 is set. If your sign-out function only clears localStorage and the identity provider client, your sessions are still active.