Email Is Not Identity
When a server trusts a caller-supplied email, domain, or identity claim as proof of who you are, anyone can become anyone. Here is the pattern and the fix.
What this is
Email is not identity. An email address, an email domain, a claimed user ID, or a “prove you own this by naming its URL” challenge is a value the caller supplies. It is a claim, not a credential. This pattern is what happens when a server treats one of those caller-supplied values as proof of who the caller is, instead of binding identity to something the server itself verified. The moment a request can say “I am this person” and be believed, anyone can be anyone.
The clearest form is a password reset or login OTP that gets sent to a destination the caller names in the request. The code leaves the building and lands in the attacker’s inbox. But the pattern wears several faces, and they all share one root: the server trusted the envelope instead of checking the signature inside it.
Why it keeps happening
Identity feels solved. Every framework ships a login form, every tutorial wires up a reset flow, and the happy path works on the first try. So the question “what actually proves this caller is who they say they are?” rarely gets asked out loud, because the code already runs.
The trap is that the happy path and the attack path look identical in a demo. When a developer tests their own reset flow, they type their own email, the code arrives, and everything works. Nobody in that moment types a different person’s account with their own inbox as the destination, because that is not how you test your own feature. The gap only appears when someone treats the email field as an input to attack rather than a detail to fill in.
The same pressure that makes teams ship fast, an AI-drafted auth handler that “looks right,” a social-login integration copied from a snippet, an internal tool that assumes everyone hitting it already works here, quietly moves the trust decision to the wrong place. The server ends up trusting the caller to tell the truth about themselves. On the open internet, that is not an authentication system. It is an honor system.
How the attack works
The mechanism is the same across every variant: find the request field that carries identity, and change it.
- Find the flow that acts on identity. A password reset, a login, a “resend my code,” a social-login callback, an internal pre-authorization check.
- Locate the caller-supplied identity value. The email in the reset body. The destination address. The
{id, email}the client posts after a social handshake. The domain the server reads off an address. The server URL a tenant is asked to “prove” it controls. - Substitute the target. Put the victim’s account in one field and, where the flow allows it, the attacker’s own destination in another. Or assert the identity the attacker wants and see if the server verifies it.
- Collect what the server hands back. A reset code delivered to the attacker. A session minted for the asserted email. Staff access granted on a claimed domain. Tenant control granted on a named URL.
There is no exotic tooling here. The exploit is usually a single well-formed request. That is what makes the class dangerous: it is not a race, not a brute force, not a chain of ten steps. It is one request that the server was never told to distrust.
What it looks like in the wild
We have found this across companies that share nothing but the shape of the mistake, which is what makes it a pattern.
-
A property-management SaaS ran a password-reset endpoint that sent the one-time code to whatever email address the caller put in the request, rather than to the address on file for that account. One request, targeting an administrator’s account with the attacker’s own inbox as the destination, produced a valid reset code in the attacker’s hands. That is full takeover of any account, including admin, from an unauthenticated request. Full teardown: The Password Reset That Emailed the Attacker.
-
A customer-support AI startup exposed a social-login mutation that accepted an unsigned
{id, email}object from the client and minted a session for it. There was no verification of a signed token from the identity provider - the server read the email out of the request body and trusted it. Sending any email address returned a valid session for that account. Full teardown: The Social Login That Trusted the Client. -
A healthcare practice-management platform auto-pre-authorized anyone whose email string carried a staff domain. The check was “does this address end in the clinic’s domain,” and the answer was treated as proof of employment. Anyone who could type that domain into the address field inherited the standing of a staff member.
-
A messaging and agent platform treated “I can reach the server URL you named” as proof that a caller owned the corresponding tenant. Naming a URL you can reach is not the same as owning the tenant behind it, and the gap between those two things was the whole vulnerability.
-
A coliving operator trusted a client-supplied email as the tenant’s identity on a request that should have read identity from the authenticated session. The email in the body, not the session, decided whose data came back.
The consistent theme: in every case, a value the caller controlled was allowed to answer the question “who is this?” The server never independently checked.
How to tell if you are exposed
A short self-check for any flow that grants access, sends a code, or returns someone’s data:
- Follow the identity value. For each such endpoint, ask: where does the server learn who the caller is? If the answer is “from a field in the request,” that field is a substitution target.
- Test the reset destination. Trigger a password reset for an account you do not own, and try to steer the code or link to a destination you control. If it arrives, the flow trusts the caller.
- Inspect the social-login callback. Confirm the server verifies a signed token from the provider and reads identity out of that verified token. If it accepts an email or user ID posted by the client, it is trusting the client.
- Check any domain-based access. If staff or tenant privileges hinge on an email domain, treat that as failing until proven otherwise. A domain in a string is not membership in an organization.
How to fix it
- Bind identity to a verified credential. The caller’s identity comes from a session or a token the server issued and validates on every request, never from a value the caller typed. If the request has to say who it is, it is already broken.
- Send codes and links only to a destination on file. A reset code or login OTP goes to the address or phone the server already stored for that account, chosen server-side. Never to a destination supplied in the request.
- Verify signed identity tokens; never trust client-asserted claims. For social login and any federated identity, validate the provider’s signature and read the identity out of the verified token. Discard any identity the client asserts on its own.
- Never treat an email domain as authorization. Employment, tenancy, and role are verified against a directory or an invite the server issued. A domain suffix proves the shape of an address and nothing else.
- Separate “who you claim to be” from “who you are.” Every access decision runs against the verified identity, not the claimed one. If those two can ever differ in your code, an attacker will make them differ.
The takeaway
An email address is something a caller types, not something a server verified. Any system that lets a request assert its own identity, and believes it, can be impersonated with a single request. Bind identity to a credential you issued and validate, and the whole class disappears. This is one of the most common ways we take over accounts, and it is one of the easiest to close.
This pattern shows up again and again in our engagements. Two clean instances are written up in full: The Password Reset That Emailed the Attacker and The Social Login That Trusted the Client.
Frequently asked
What does 'email is not identity' mean in application security?
How does a password reset become an account takeover?
Why is trusting a client-asserted social-login identity dangerous?
Is an email domain enough to prove someone is an employee?
This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.
How it works →
