Errors and denied access
You reach the photo service's approval screen and decide not to connect the printer. That is a valid outcome. The printer should let you return to your photo book without pretending the connection succeeded or repeatedly sending you back for approval.
Other attempts fail because of configuration mistakes, expired codes, or lost network responses. Handling them well starts with identifying where the exchange stopped.
A token request is rejected
An error at the token endpoint comes back directly to the client. For example:
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"error": "invalid_grant"
}
This can cover an expired or previously used code, a code binding mismatch, or a failed PKCE verification. It does not identify one unique cause. invalid_client instead concerns failed client authentication, while invalid_request indicates a malformed request. The client should handle the documented error and HTTP behavior, not assume all failures have the same status or recovery path.
The person using the printer needs a clear next step. The developer needs enough safe diagnostic context to investigate. Record the stage, a local correlation ID, and a safe error category. Do not copy full callback URLs, authorization headers, codes, verifiers, or tokens into logs.
Optional error descriptions are untrusted text. Do not insert them into HTML as markup, and do not automatically follow supplied error links.
When the outcome is uncertain
Suppose the printer sends its token request, but the connection drops before it receives a response. The authorization server might have rejected the request, or it might have consumed the code and issued a token whose response was lost.
A timeout therefore means the client did not obtain a confirmed result. It does not prove that the server did nothing. Replaying a single-use code is not a reliable recovery strategy, and an invalid_grant on a retry would not prove that the first request failed.
For this teaching flow, the printer reports that it could not finish the connection and offers a fresh attempt. It does not silently remove PKCE, change the return address, or keep prompting for consent in a loop. More detailed retry and operational handling belongs in Implementation and operations.
A completed connection is one possible outcome. A denial, a rejected exchange, and an uncertain result each need an honest description and an appropriate next step.