email_verified Claim in an OpenID Connect ID Token
email_verified is a boolean saying whether the issuer took steps to verify the email address at the time it issued the token. It is the claim that decides whether an email address is safe to link an account on.
At a glance
- Claim
- email_verified
- Defined in
- OpenID Connect Core 1.0 section 5.1
- Value type
- JSON boolean, true or false, not a string
- Required
- Optional. Returned with the email scope when the provider supports it.
- Sample value
- true
- In the decoder
- Shown under Custom claims with its decoded JSON value, so a boolean true is visibly different from the string true.
In detail
The specification is careful about what the boolean asserts: true means the issuer took affirmative steps to ensure the address was controlled by the end user at the time of verification, and that it may not still be. It is a statement about the past made by the issuer, not a live check, and how rigorous the verification was is entirely up to that issuer.
The attack it prevents is straightforward. If an application links accounts by email address alone, anyone who can persuade a provider to issue a token containing somebody else's address takes over the matching local account. Some providers historically allowed unverified addresses to be set freely on self service signups, which turned an unchecked link into an account takeover with no exploit code involved.
So the rule is simple to state: never link, merge or elevate an account on an email address unless email_verified is true in the token you are holding right now, from an issuer you decided in advance to trust for that address. A previously stored value is not enough, since the address may have changed since, and a claim from an unexpected issuer is not evidence about a domain it does not control.
There is also a decoding trap. The claim is defined as a JSON boolean, but some deployments emit the string true instead, and in a language with loose truthiness both look positive while only one is correct. The decoder renders the payload as parsed JSON with syntax highlighting, so a real boolean and a quoted string are visibly different, which makes this a quick thing to check.
Examples
// Correct: a JSON boolean
{ "email": "jordan.reyes@example.com", "email_verified": true }
// Non conforming: a string that many checks accept anyway
{ "email": "jordan.reyes@example.com", "email_verified": "true" }A strict equality check against true rejects the second. A truthiness check accepts both, including the string false.
Things that catch people out
- Testing truthiness rather than equality. The string false is truthy in several languages, so it passes a check that only asks whether the value is set.
- Trusting a verified address from an issuer that has no authority over the domain. Decide which issuers you trust for which domains in advance.
- Treating a stored verified flag as permanent. The claim describes the address at verification time, and the address can change afterwards.
Try it: JWT Decoder
Paste a token to decode the header and payload, read the claims and expiry, and verify the signature in your browser. Nothing is uploaded.
JWT Decoder
LOCAL · Browser-OnlyDecode and inspect JSON Web Tokens without sending them to a server
Related JWT claims and algorithms
All jwt claims and algorithms →email Claim in an OpenID Connect ID Token
The email claim carries the end user's email address. Why it must not be used as an account key, what scope returns it, and what the decoder shows.
sub (Subject) Claim in a JWT
The sub claim says who the token is about. Why it is only unique within one issuer, what pairwise subjects change, and how the decoder shows it.
acr (Authentication Context Class Reference) Claim
The acr claim states how strongly the user was authenticated. Why its values are provider specific, what the value 0 means, and how the decoder shows it.
preferred_username Claim in an ID Token
The preferred_username claim carries a shorthand name for the user. Why OpenID Connect says it is neither unique nor stable, and how the decoder shows it.
HS256: HMAC with SHA-256 in a JWT
HS256 signs a JWT with a shared secret using HMAC and SHA-256. Key sizing, the algorithm confusion attack, and verifying one in your browser.
HS384: HMAC with SHA-384 in a JWT
HS384 signs a JWT with a shared secret using HMAC and SHA-384. How it differs from HS256, why SHA-384 is a truncated SHA-512, and verification support.
Tools for this
- JWT Decoder Free online JWT decoder - decode and inspect JSON Web Tokens without sending them to a server