at_hash (Access Token Hash) Claim in an ID Token
at_hash is the base64url encoded left half of a hash of the access token. Checking it proves the access token you hold was issued alongside this ID token, rather than swapped in from somewhere else.
At a glance
- Claim
- at_hash (Access Token hash value)
- Defined in
- OpenID Connect Core 1.0 section 3.1.3.6
- Value type
- Base64url encoded string, no padding
- Required
- Required when an ID token is returned from the authorization endpoint together with an access token. Optional from the token endpoint.
- How it is computed
- Hash the ASCII octets of the access token with the hash of the ID token's alg, take the left half of the digest, base64url encode it.
- In the decoder
- Shown under Custom claims as the raw string. The tool verifies the token signature, not this hash, since it never sees the access token.
In detail
The hash function is not fixed by the claim: it follows the ID token's own alg. An ID token signed with RS256 uses SHA-256, one signed with ES512 uses SHA-512. Take the digest, keep the left half, so 128 bits of a SHA-256 digest, and base64url encode that half with no padding. The right half is discarded, which is why the value is shorter than a full digest would suggest.
The problem it solves is substitution. In flows where the access token travels through the browser, an attacker who can influence the response might pair a legitimate ID token with an access token belonging to someone else. Recomputing at_hash from the access token you actually received and comparing detects that swap, because the hash was fixed at issue time inside the signed ID token.
OpenID Connect requires the claim when both an ID token and an access token come back from the authorization endpoint, which is the case where the check matters most. When the tokens come from the token endpoint over a direct back channel connection, the specification treats the transport as adequate and providers may leave the claim out, so its absence there is not a defect.
The decoder shows at_hash under Custom claims as the raw base64url string. It cannot check it, and that limit is inherent rather than an omission: verifying it needs the access token as well, and you are pasting one token here. What the tool does verify, in your browser, is the ID token's own signature, which is the check that makes the at_hash value trustworthy in the first place.
Examples
// ID token header fixes the hash to use
{ "alg": "RS256", "typ": "JWT" }
// digest = SHA-256(ascii_octets_of_access_token)
// at_hash = base64url(digest[0 .. 15]) // left 128 bits of 256
{ "at_hash": "xsZZrUssMXjLxaJZFT9NDA" }Sample value only. A real at_hash depends entirely on the access token it was computed from.
Things that catch people out
- Hashing the whole digest instead of the left half. The claim is deliberately truncated, so a full digest never matches.
- Hardcoding SHA-256. The hash follows the ID token's alg, so an ES384 token needs SHA-384.
- Hashing a decoded or re-encoded form of the access token. The input is the ASCII octets of the token string exactly as received.
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 →c_hash (Code Hash) Claim in an ID Token
The c_hash claim binds an ID token to the authorization code returned with it. Where hybrid flows need it, how it is computed, and what the decoder shows.
nonce Claim in an OpenID Connect ID Token
The nonce claim binds an ID token to one authentication request. How the value round trips, why it must be compared, 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.
sid (Session ID) Claim in a JWT
The sid claim identifies the authentication session at the provider. How single logout uses it, why it is not the user identifier, and decoder behaviour.
scope Claim in a JWT Access Token
The scope claim carries the granted OAuth scopes inside an access token. Why it is one space delimited string, how to check it, and what the decoder shows.
client_id Claim in a JWT Access Token
The client_id claim names the OAuth client an access token was issued to. How it differs from sub and azp, where it is required, and decoder behaviour.
Tools for this
- JWT Decoder Free online JWT decoder - decode and inspect JSON Web Tokens without sending them to a server
- Hash Generator Free online hash generator - generate MD5, SHA-1, SHA-256 hashes from any input text