Home/Journal/Teardown
TeardownCriticalauthenticationsocial-loginoauth

The Social Login That Trusted the Client

A support-AI startup minted sessions from unsigned Google login claims, letting an attacker log in as any email. Here is how we found it and how to close it.

Rahul Dharan··8 min read·a customer-support AI startupAuthentication bypass in social login (CWE-287/345)

The setup

The target was a business-to-business customer-support AI startup: the kind of company whose product sits inside other companies’ help desks, reading support conversations and drafting or sending replies. That means the platform holds a lot of other people’s customer data - support tickets, contact records, the back-and-forth of real users - all behind whatever the login flow protects.

When a product’s entire value is holding other companies’ conversations, the sign-in flow is the whole perimeter. So that is where we looked. We were interested in two things: how the platform decided who you were when you logged in, and how it named the accounts once you were in.

Recon

Working black-box, with no account, we studied the sign-in flow and the object-ID scheme the application used. Two details stood out early.

First, the platform offered social login - “sign in with Google” - and the client-side flow made a GraphQL mutation to complete it. GraphQL introspection was left on, which meant the schema described itself: we could read the exact shape of that login mutation and everything around it, which mapped the surface for us without guesswork.

Second, the identifiers the application used for users were sequential integers. Accounts were numbered in order. That is a small thing on its own, and a large thing in combination with the right second bug.

The hypothesis

Social login is a common place for a specific mistake. The safe design is that the identity provider - Google - hands the client a signed token, the client passes that token to the server, and the server verifies the signature against Google’s public keys before trusting anything inside it. The signature is what makes the email trustworthy.

The unsafe version skips the verification. The client sends the server a bundle of identity claims - a Google ID, an email, a name - and the server trusts them because they arrived from the login flow, without checking that they came inside a token Google actually signed. If a handler does that, the email is just a string the client chose. Anyone can put any email in it.

So the hypothesis was: if this social-login handler trusts client-asserted identity claims rather than a verified signed token, it is an authentication bypass - log in as anyone. And with sequential user IDs, one bypass does not reach one account. It reaches the whole userbase, because every account is trivially enumerable.

What we tried

Introspection gave us the mutation’s exact input shape, so we could see precisely what the handler expected. The question was whether it verified a signed Google ID token, or whether it took the identity fields at face value.

We sent the Google-login mutation with an unsigned payload of identity claims - a Google ID, an email we controlled the target of, and a name - and no verifiable signed token from Google. The server accepted it and minted a valid session token bound to that email.

To confirm this was a real bypass and not an artifact, we did it against an account we were authorized to target and then used the resulting session to perform a cryptographically-verified read on that account - proving end-to-end that the session was genuine and carried that identity’s access, not a dead token. The bypass was real: assert an email, receive a working session for it.

There was a third finding sitting right next to this one. New users on the platform defaulted to an admin role. So the accounts an attacker could step into were not only any user - fresh accounts arrived with elevated privileges by default.

What we deliberately did not do is walk real users. We proved the bypass once, against an authorized target account, and stopped. The proof that any email is reachable does not require reaching real customers’ accounts, and doing so would be crossing from demonstration into intrusion. One verified account was the whole proof.

What we found

The finding had three parts that compound:

-> mutation googleLogin(input: {
     googleId: "<any-value>",
     email: "<target-email>",
     name: "<any-value>"
   })            # no signed Google ID token, nothing verified

<- { token: "<valid-session-for-target-email>" }

The mechanism is one sentence: the server treated an email the client typed as proof of who the client was. In authentication, the email is the claim, not the credential. The signed token is the credential, and it was never checked.

Why it happens

This is not carelessness so much as a convenient shortcut that looks correct in a demo. Social-login libraries hand you a decoded object with the user’s email and name already sitting in it. It is genuinely tempting to read the email straight out of that object and issue a session, because it works perfectly for every honest user - the email is right, the login succeeds, the flow ships.

The verification step is invisible in the happy path. Signature checking only matters against an attacker who forges the claims, and there is no attacker in the demo. Under deadline pressure, “the email is right there, just use it” wins over “receive the signed token, verify it against the provider’s keys, then use the email inside.” The bug is convenience over verification, and it survives because nothing in normal use ever reveals it.

For developers

If your product supports social login, these controls close this class of bug:

  1. Verify the provider’s signed token server-side. Receive the identity provider’s signed ID token and verify its signature, issuer, audience, and expiry against the provider’s published public keys before you trust anything in it. Only then read the email out of the verified token. This is the entire fix and it is non-negotiable.
  2. Never trust client-asserted identity fields. An email, name, or provider ID that arrives as a plain field the client set is a request, not a fact. Treat it as untrusted input. The only trustworthy version of the email is the one inside a token you verified.
  3. Do not default new accounts to admin. New users should be created with the least privilege the product allows, and elevation should be an explicit, audited action. A permissive default turns any account-creation or account-access flaw into an instant privilege escalation.
  4. Use unguessable identifiers. Sequential integer IDs make your whole userbase enumerable and turn any single-account flaw into a mass-exposure flaw. Random, unguessable IDs (UUIDs or similar) mean a bug that reaches one account does not automatically reach all of them.

And one detection step: audit your login handler for any path that issues a session from client-supplied identity fields without a verified signature in between. If you cannot point to the exact line that checks the token’s signature, assume it is missing.

The takeaway

An email address is an identity claim, not a credential. The only thing that makes a social login trustworthy is the identity provider’s signature, verified on your server - skip that check and the email becomes a field anyone can fill in with anyone else’s address. Pair that with sequential IDs that make every account discoverable and an admin-by-default role, and a single trusting login handler becomes full, privileged access to the entire userbase. Verify the token, never the claim.

This teardown is one instance of a pattern we see repeatedly - systems that treat an email address as proof of identity. We wrote up the general case, and how to defend against it, in Email Is Not Identity.

Frequently asked

What is a social-login authentication bypass?
It is a flaw where the sign-in handler trusts identity claims the client sends (an email, a name, a provider ID) instead of verifying a signed token from the identity provider. Because those claims are unsigned, an attacker can assert any email and be issued a valid session for it.
How should Google or social login be verified server-side?
The server must receive the provider's signed ID token and verify its signature, issuer, audience, and expiry against the provider's public keys. Only after that verification should it trust the email inside. Client-asserted fields like email or name must never be trusted on their own.
Why do sequential user IDs make an auth bug worse?
Sequential integer IDs let an attacker enumerate every account by counting. Combined with an authentication bypass that works on any known email, predictable IDs turn a single-account flaw into full-userbase exposure, because every target is trivially discoverable.

This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.

How it works →