Tokens trusted without server verification
What it looks like
// app/api/me/route.ts
const raw = req.headers.get('authorization')?.split(' ')[1];
const payload = JSON.parse(atob(raw.split('.')[1])); // decoded, not verified
const user = await db.users.find(payload.sub);
// server-side Supabase:
const { data: { session } } = await supabase.auth.getSession();What happens
Anyone can write their own token naming another user, or an admin, and the server will act as that person.
Where it shows up
Descriptive, not a verdict: builders and stacks whose defaults make this reachable, or that a public incident names. Each hub states the tool's own security features.
Seen in public
On the Base44 app builder, undocumented registration and one-time-password endpoints accepted a non-secret app_id, so anyone could create a verified account on a private app; Wix fixed it within 24 hours.
- 2025-07-09 Reported to Wix
- 2025-07-10 Fix verified
- under 24 hours Time to fix
- 2025-07-29 Public disclosure
Jul 2025 · Wiz Research · all sources
Check it yourself
Uses your builder's dashboard and your browser. No tools to install.
- Sign in, open developer tools (right-click, Inspect), go to the Network tab and pick a request to your own API that carries an Authorization header or a session cookie.
- Copy the token and paste it into a JWT decoder that runs in your browser, or run
echo <token> | cut -d. -f2 | base64 -din a terminal, and read the fields. - In the builder's code view, search the server code for getSession, atob and jwt.decode. Server routes that use these without getUser, getClaims or a verify call are the warning sign.
- If you are comfortable, change the user id in the decoded middle part, re-encode it, replace it in the request with Copy as cURL, and send it with the old signature.
A bad result is any success response to an edited token; a correctly written server answers HTTP 401 because the signature no longer matches.
What we check
We forge tokens with edited claims, an altered signature, the none algorithm and an expired timestamp, and send each to every server route. The report lists the routes that accepted any of them.
Fix direction
Verify every token on the server with the library's verify call: Supabase's getClaims or getUser, Firebase Admin's verifyIdToken, or Clerk's auth() helper, and reject anything that fails. Decoding is not verifying.
Mechanism documented in: JavaScript: getSession (verified 2026-09-21); Verify ID Tokens (verified 2026-09-21).
Covered by
Pre-launch check ($490, 48 hours) and Security sweep ($290, 24 hours)
Both include a re-check after your fixes.
Related patterns
- Authorization enforced only in the browserLogin and permissionsCritical
Anyone who calls the API directly, or edits the page in developer tools, gets the data and actions the screen was hiding.
- Sessions that survive logout and password changesLogin and permissionsHigh
Someone who once had a user's token, on a shared computer or through a leak, keeps access after the user logs out or changes their password.
- Role chosen by the client at signupLogin and permissionsCritical
A visitor who edits the signup request, or updates their own profile, can make themselves an admin.
- Service-role key shipped in the browser bundleSecrets and keysCritical
Anyone who opens the app can copy the key from the downloaded JavaScript and read, change or delete every row in every table.
Questions
what is the difference between getsession and getuser in supabase
Supabase's reference says getSession loads values from the client's storage, and when that storage is request cookies the values may not be authentic, so it advises against using it on the server. getUser asks the Auth server and getClaims checks the token signature on every call. On the server, use one of those two before trusting who the caller is.
does decoding a jwt verify it
No. A JWT is three parts separated by dots, and the middle part is plain text once decoded; anyone can write one. Verification means checking the signature against the issuer's key, plus the expiry and audience, which Firebase's verifyIdToken and Supabase's getClaims do. A route that decodes without verifying will believe whatever the token says, so search your server code for atob and jwt.decode.
how does my server know a firebase token is real
By calling verifyIdToken in the Firebase Admin SDK, which checks the signature with Google's public keys, the expiry, the audience and the issuer, and returns the user id only if all pass. Firebase's docs note that this call does not check revocation unless you ask for it. Any backend that reads the uid from the token body without that call is trusting the client.
Back to the catalogue, or run the self-check for your stack.