Receiving the ID token
All domains, credentials, codes, and tokens in these examples are fictional.
Your browser has returned to the printer's sign-in callback, and the printer has matched the response to its pending attempt. It holds an authorization code. It still does not know who you are.
The token request
The printer's backend exchanges the code exactly as it did when connecting your photo account. Nothing in this request is specific to OpenID Connect:
POST /token HTTP/1.1
Host: auth.photos.example
Authorization: Basic cGhvdG8tcHJpbnRlcjpkZW1vLW9ubHktbm90LWEtcmVhbC1zZWNyZXQ=
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=demo-code-12
&redirect_uri=https%3A%2F%2Fprinter.example%2Fsignin%2Fcallback
&code_verifier=btl_training_verifier_for_one_sign_in_only_2026
The photo service makes the same checks as before: the printer's client authentication, the code's binding to this client and redirect URI, its expiry and single use, and the PKCE proof. Exchanging a code for tokens walked through each of them.
What differs is what the code stands for. Because the original request included openid, the photo service recorded the code as belonging to a sign-in, along with the nonce from the request and the time you authenticated. Those are what it needs to write the ID token.
The token response
A successful response looks like this. The ID token's signature is shortened for display, and a real RS256 signature is several hundred characters long:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "demo-access-token-12",
"token_type": "Bearer",
"expires_in": 600,
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InBob3Rvcy1ycy0yMDI2LTA5IiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2F1dGgucGhvdG9zLmV4YW1wbGUiLCJzdWIiOiJ1c2VyLTIwNDgiLCJhdWQiOiJwaG90by1wcmludGVyIiwiZXhwIjoxNzkwODQ1NTAwLCJpYXQiOjE3OTA4NDUyMDAsImF1dGhfdGltZSI6MTc5MDg0NTE4NSwibm9uY2UiOiJkZW1vLW5vbmNlLTMifQ.Rk3v...shortened"
}
The access token, token type, lifetime, and scope are the familiar OAuth fields. This access token was issued for openid profile email, so it does not let the printer read your photos. Its use is at the photo service's UserInfo endpoint, which The UserInfo endpoint describes.
The id_token is a JWT in its compact form: three Base64url-encoded segments separated by dots. Decoding the first segment gives the header, and decoding the second gives the claims, exactly as shown in ID tokens and access tokens:
{"alg":"RS256","kid":"photos-rs-2026-09","typ":"JWT"}
{"iss":"https://auth.photos.example","sub":"user-2048","aud":"photo-printer",
"exp":1790845500,"iat":1790845200,"auth_time":1790845185,"nonce":"demo-nonce-3"}
The line break in the second segment is for display. The third segment is the signature, which the photo service computed over the first two segments exactly as they appear, joined by their dot.
Before trusting it
Decoding those segments needed no key and proved nothing. Anyone can produce text that decodes to "sub":"user-2048". As Protecting credentials and messages put it, reading is not validating. Until the printer has checked the signature with the photo service's key and compared the claims with what it expects, the token is only a statement that someone sent.
One nuance appears in the specification and in some libraries. This token came straight from the photo service's token endpoint, over a TLS connection that the printer's backend opened itself after validating the server's certificate. OpenID Connect allows a relying party in that position to rely on the connection to establish who issued the token, instead of verifying the token's signature, because no one else handled it on the way. This series verifies the signature anyway. It costs little, it gives the printer one validation path for every ID token it accepts, and it stays correct if the token is later stored, passed between components, or presented again. The other checks, including issuer, audience, lifetime, and nonce, are required either way.
Two habits keep the token in its place. The printer keeps the ID token on its backend, out of responses sent to the browser and out of its logs. It also never uses the ID token as its own session: no cookie containing the token, and no API that accepts it as proof of a signed-in customer. Once validated, the token's job is to start a session. Some relying parties keep it on the server afterward, because a later sign-out request can include it as a hint, as RP-initiated logout explains.
Not every sign-in reaches this point. Before working through the validation checks, the printer needs to handle the attempts that end without an ID token at all.