skip to content

What does an OAuth 2.0 client send to an RFC 7009 revocation endpoint, and what does a 200 answer mean?

level: juniorimportance: should knowfreq 38%

answer

  1. a back-channel POST, not a redirect
  2. form-encoded, one credential per call
  3. only the client it was issued to
  4. success carries no body at all
  5. the invalid-token case is deliberate

basics

~20 s

A revocation request is a form-encoded POST carrying the token parameter, an optional token_type_hint, and the client's own credentials. HTTP 200 means the token is no longer usable, and is also the answer when the submitted value was never valid.

solid answer

~50 s

RFC 7009 defines one back-channel call: the client POSTs `application/x-www-form-urlencoded` parameters to the authorization server's revocation endpoint. `token` is REQUIRED and carries the credential itself — the access or refresh token string, not an identifier for it — and `token_type_hint` is OPTIONAL. A confidential client authenticates exactly as it does at the token endpoint, and the server also checks that the token was issued to *that* client before touching it. Success is HTTP `200` with an empty body: there is nothing to parse. The rule candidates miss is that `200` is also the answer when the value presented was never a valid token. That is deliberate — the caller's goal, that this string no longer works, already holds, and an error would let anyone test strings for validity. Real errors are reserved for requests the server cannot accept: `invalid_request`, `invalid_client`, `unsupported_token_type`.

code

http · 8 lines
http
POST /oauth2/revoke HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token

HTTP/1.1 200 OK

go deeper

for a junior

Recall the shape: a form-encoded POST with a token parameter, the client's own credentials, and an empty 200 back. Knowing that an already-dead token also gets 200 is the detail that shows you read the specification rather than a tutorial.

for a middle

Explain why the uniform 200 exists — the client cannot act on the difference, and a distinct error would turn the endpoint into a validity oracle — and name the three error codes that do occur.

for a senior

Show that you check ownership as well as authentication, and that you treat 200 as confirmation the issuer recorded the revocation rather than proof that every node and every resource server has stopped honouring the credential.

for a principal

Frame revocation as a containment control with a measurable lag and a defined blast radius per call, and judge whether your token lifetimes make that lag acceptable rather than assuming the endpoint is an instant kill switch.

## What the endpoint is for An OAuth 2.0 authorization server hands a client two kinds of credential: an **access token**, which the client presents to a resource server, and often a **refresh token**, which the client keeps in order to obtain further access tokens. Both are valid until they expire. That is fine until something changes before expiry — a user withdraws consent in an application's own settings screen, a device is handed to someone else, a client discovers it logged a token where it should not have. **RFC 7009** exists for exactly that moment: it defines a revocation endpoint the client calls to say that a credential it holds is finished. Note who the caller is. Revocation is written for **the client that was issued the token**, not for a resource server and not for an administrator. The server authenticates the caller and then verifies the token really belongs to it; a request naming another client's token is refused rather than answered with the ordinary success. ## The request - An HTTP `POST` over TLS, on the back channel — server to server, never a browser redirect. - Body encoded as `application/x-www-form-urlencoded`. - **`token`** — REQUIRED. The credential string itself. - **`token_type_hint`** — OPTIONAL, with the registered values `access_token` and `refresh_token`. It is a hint about where to look, not an instruction about what to accept. - **Client authentication** — a confidential client authenticates with the method it uses at the token endpoint, for example `client_secret_basic`; a public client identifies itself with `client_id`. One call revokes one credential. There is no batch form and no parameter that says "everything you ever issued me"; what reaches further than the single token is the cascade RFC 7009 defines between a refresh token and the access tokens of the same authorization grant. ## The response, and the success that surprises people A successful revocation is HTTP `200` with an **empty body**. There is no JSON document describing what was destroyed, and asking for one misunderstands the exchange. The specification then makes a choice that looks wrong until you see the reasoning: the server answers `200` **also when the client submitted an invalid token** — a string that expired long ago, was already revoked, or was never issued at all. Two reasons sit behind it: 1. **The client cannot act on the error.** Whether the string was killed just now or was already dead, the client's state afterwards is identical: that value does not work. An error code would give it nothing to do. 2. **An error would be an oracle.** If an unknown string produced a different answer from a real one, anyone able to reach the endpoint could test strings and learn which are live. | condition | what the authorization server returns | |---|---| | the token was found and revoked | `200`, empty body | | the value was expired, already revoked, or never issued | `200`, empty body | | the request is malformed or repeats a parameter | error `invalid_request` | | client authentication fails | error `invalid_client` | | the server does not revoke tokens of the type presented | error `unsupported_token_type` | | the token belongs to a different client | refused with an error, not `200` | ## What a 200 does not promise It promises that the authorization server has recorded the revocation. It does not promise that every part of a distributed deployment has already agreed, so a short propagation delay is honest to mention. And it says nothing about which *other* credentials went with it: that is the separate question of the cascade, where revoking a refresh token reaches the access tokens of the same grant and revoking an access token need not reach anything else. ## Getting it right - Send the token value, not a token identifier, and send it in the body rather than in a query string. - Authenticate the call. An unauthenticated revocation endpoint would let a stranger who has seen a token string end a user's session at will. - Do not write error handling around the `200`. Treat the call as fire-and-confirm: on `200`, discard your copy of the credential. - Treat the choice of *which* credential to revoke as the real design decision — it, not the wire format, is what determines how much access ends.

  • Who is allowed to revoke a given OAuth 2.0 token?
    Only the client the token was issued to. The authorization server authenticates a confidential client and then verifies that the presented token belongs to that client; a request naming someone else's token is refused with an error rather than answered with the ordinary empty `200`.
  • Why does a successful revocation return no body?
    Because there is nothing a client could act on. Either the string is now unusable or it never was, and both leave the caller in the same state. A descriptive body would also reveal whether an arbitrary string had been a real token, which is precisely what the uniform `200` avoids.

saying these in an interview costs you the question

  • Expects a JSON body describing what the revocation destroyed.
  • Treats an unknown or expired token as an error case.
  • Thinks any client holding the token string may revoke it.
  • Confuses the revocation endpoint with the introspection endpoint.
  • Believes a confidential client need not authenticate to revoke.