With `response_type=code id_token`, what arrives on the redirect, and what does the ID token's `c_hash` bind it to?
answer
- two artefacts, one channel
- a binding between them, not secrecy
- the signed one commits to the other
- recompute from the code, compare exactly
- case-sensitive string in the id_token
basics
~20 sA code and an id_token arrive together on the redirect, which is the Hybrid Flow. Because both travelled the front channel, the id_token must carry c_hash, a value derived from that code, so the client can prove the two belong to one response.
solid answer
~50 s`code id_token` is a hybrid response type: the authorization response carries **both** an authorization code and an `id_token`, delivered together through the user agent. That lets the relying party learn who signed in immediately, before the back-channel exchange completes — and it creates the problem the `c_hash` claim exists to solve. Two artefacts that arrive over a tamperable channel could have come from two different places, so an ID token issued from the authorization endpoint alongside a code MUST carry `c_hash`, a value derived from that code using the hash the ID token's signature algorithm implies. The relying party recomputes it from the code it actually received and compares the two as case-sensitive strings. A mismatch means the code was not the one the provider issued with that ID token, and the response is rejected.
code
http · 4 linesHTTP/1.1 302 Found
Location: https://board.pilotage.example/cb#
code=SplxlOBeZQQYbYS6WxSbIA
&id_token=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lkcC5w...go deeper
Recognise the shape rather than the detail: a hybrid response type brings back a code and an ID token at once, and there is a claim inside the ID token whose job is to tie the two together.
Explain the direction of the binding — the signed ID token commits to the code, not the reverse — and say why that direction is what makes a swap detectable at all.
Show the operational half: recompute and reject, never recompute and log; compare as case-sensitive strings; and be able to say what a hybrid response type buys that justifies the extra check in the first place.
The call worth owning is whether early identity on the redirect is worth an extra binding your relying parties must all implement correctly. A check that only some integrations perform gives an estate the complexity with none of the assurance.
## What a hybrid response type returns There are three hybrid values — `code id_token`, `code token` and `code id_token token` — and they share one shape: **a code plus at least one token, all delivered in the same authorization response**. With `code id_token` the provider redirects the user agent back carrying both an authorization code and a complete, signed `id_token`, encoded in the fragment component of the redirect URI. The attraction is latency and sequencing. The relying party knows who signed in the moment the redirect lands, without waiting on a token-endpoint round trip, and it can decide immediately whether this is a user it will admit at all. The code is still there to be exchanged for an access token, and the provider will issue a second `id_token` on that leg. ## The problem two artefacts on one channel create The front channel runs through software the relying party does not control. Anything delivered on it has been handled by the user agent and whatever is running inside it. With `response_type=code` that is tolerable, because the only thing on that channel is a single-use handle. With `code id_token` there are two independent artefacts on it, and independence is the flaw: without a binding, an attacker who can influence the redirect could pair a genuine `id_token` for a user with a code obtained elsewhere. The relying party would validate the ID token successfully, believe the right person signed in, and then exchange a code that does not belong to them. ## What `c_hash` is `c_hash` is a claim in the `id_token`. Its value is derived from the authorization code that was issued in the same response: the code's characters are hashed with the hash function implied by the algorithm the ID token was signed with, and a fixed portion of that digest is encoded into the claim's string value. Three properties matter more than the arithmetic: - it is **carried in the ID token**, which is signed — so an attacker who swaps the code cannot adjust `c_hash` to match without the provider's signing key; - it is derived **from the code**, not the other way round — the ID token commits to the code, so the client can check a code it holds against a commitment it can verify; - the comparison is over a **case-sensitive string**, so a client that lowercases, trims or normalises the value before comparing will produce false mismatches, or worse, a check it quietly disables. `c_hash` does not hide the code and does not stop it being read. It stops a **substituted** code being accepted. ## How a relying party uses it 1. Take the `id_token` from the authorization response and validate it as an ID token in the usual way. 2. Take the authorization code from the same response and recompute the hash value from it, using the hash that the ID token's signature algorithm implies. 3. Compare the result with the `c_hash` claim as case-sensitive strings. 4. On a mismatch, reject the whole response and start over; do not exchange the code. Step 4 is the one that gets dropped. A relying party that computes `c_hash` and logs a warning has built a detector, not a control. ## Which response types actually carry it | `response_type` | ID token from the authorization endpoint? | `c_hash` in that ID token | |---|---|---| | `code` | no | not applicable | | `id_token` | yes, with no code beside it | not applicable | | `id_token token` | yes, with no code beside it | not applicable | | `code id_token` | yes, beside a code | required | | `code token` | no | not applicable | | `code id_token token` | yes, beside a code | required | The rule is mechanical: the claim is required exactly when an ID token is issued **from the authorization endpoint together with a code**. `code token` returns a code but no ID token there, so there is nothing to carry the claim. ## When a hybrid response type is worth it It buys one thing — identity before the exchange — and charges for it in complexity: an extra binding to compute and compare, a second `id_token` to reconcile from the token-endpoint leg, and an identity artefact on the front channel that plain `code` would have kept off it. Take it when something genuinely has to happen on the strength of the redirect alone. Otherwise the plain code response type is fewer moving parts and one fewer check to get wrong.
- Does the ID token returned from the token endpoint in a hybrid flow also carry `c_hash`?It need not. The claim exists because an ID token and a code arrived together over a channel the relying party does not control. An ID token minted on the token-endpoint leg was fetched over a direct connection in exchange for that very code, so the binding is already implicit in how it was obtained.
- Why does `code token` not carry a `c_hash`?Because no ID token is issued from the authorization endpoint under that response type — an access token and a code come back, and there is no signed identity statement on that leg to carry the claim. The requirement attaches to an ID token issued alongside a code, which is the case only for `code id_token` and `code id_token token`.
- A relying party compares `c_hash` after uppercasing both sides and sees intermittent failures. What is wrong?The comparison must be exact and case-sensitive. The claim's value is an encoded string in which case is significant, so normalising it destroys the comparison: some values survive the transform and some do not, which is exactly the intermittent pattern described. Compare the raw strings, and treat any inequality as a rejection.
saying these in an interview costs you the question
- Says c_hash is a hash of the ID token rather than of the code.
- Treats c_hash as optional when a code and id_token return together.
- Claims a hybrid response type skips the token endpoint entirely.
- Compares c_hash case-insensitively or trims it before comparing.
- Believes c_hash keeps the authorization code secret on the redirect.
- Logs a c_hash mismatch as a warning and exchanges the code anyway.