Skip to content
BuildThenTestedSee packages

Tokens trusted without server verification

Anyone can write their own token naming another user, or an admin, and the server will act as that person. How the app knows who you are and what you are allowed to do. Check it yourself with the steps below; our test adds what a script and a second account can find.

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();
A route that decodes a JWT (JSON Web Token, the signed pass a user carries after login) without checking its signature. Below it, a server-side call to getSession, which Supabase's docs say reads the cookie without re-validating it.

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.

  1. 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.
  2. Copy the token and paste it into a JWT decoder that runs in your browser, or run echo <token> | cut -d. -f2 | base64 -d in a terminal, and read the fields.
  3. 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.
  4. 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.

Compare packages

Related patterns

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.