Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

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.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2The printer decodes the middle segment of the ID token and sees "sub":"user-2048". What does that establish?

QUESTION 2 OF 2The access token in the response was issued for openid profile email. What can the printer use it for?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity