skip to content

Token Handling

Minting a signed token, verifying it in every service that receives one, and killing it early through rotation, reuse detection and a jti denylist. Interviewers probe the revocation story hardest.

on this pageshow

explore

questions

25

A partner station posts telemetry with a long-lived API key that never expires — what does your ingest service store for it?

level: juniorimportance: must knowfreq 72%

answer

  1. possession is the whole authority
  2. the server recognises, it does not keep
  3. store a verifier, not the secret
  4. prefix and last four for display
  5. shown once, replaced not recovered

basics

~20 s

Store a one-way digest of the key, never the key itself, alongside a non-secret prefix and last characters for display, the owning station, the key's permissions, and timestamps. The plaintext is shown once at creation and is not recoverable afterwards.

solid answer

~40 s

A long-lived machine key is a bearer secret: whoever holds the string is the station, so the server needs to *recognise* it, not *keep* it. The row holds a one-way digest of the secret, a short non-secret key identifier used to find the row, a display stub (leading segment plus last few characters), the owning station, the permissions bound to this key, and `created_at` / `last_used_at` / `revoked_at`. The plaintext is generated, shown once, and never stored or retrievable — a lost key is replaced, not recovered. An unrecognised key gets `401` with `WWW-Authenticate`; a recognised key that lacks the permission for the call gets `403`.

code

json · 12 lines
json
{
  "key_id": "7f3a91c4",
  "display": "seis_live_7f3a91c4...4KQ2",
  "verifier": "sha256:9d1f0c2b7a...e41",
  "station": "GSN-0142",
  "label": "field logger, vault A",
  "scope": ["readings:write:GSN-0142"],
  "quota_per_minute": 600,
  "created_at": "2026-03-04T08:12:00Z",
  "last_used_at": "2026-09-19T02:41:07Z",
  "revoked_at": null
}

go deeper

for a junior

Recall the one sentence everything follows from: possession of the string is the authority. So the server stores a one-way digest plus non-secret details, shows the key once, and can never give it back.

for a middle

Explain the split between the non-secret identifier used to find the row and the secret part that is digested and compared, and why that split is what keeps verification a single indexed read.

for a senior

Show that you have operated this: the display stub, the label and last-used timestamp are what make a one-time reveal survivable in a real support queue, and the refusal codes are a contract you are consistent about.

for a principal

Frame it as custody. The organisation is choosing not to hold four hundred partner credentials, accepting the support cost of unrecoverable keys in exchange for a table that is worthless when it leaks.

## The credential this leaf is about Four hundred seismograph stations post readings to a telemetry ingest service. Some are national observatories; some are a university department with one part-time technician. None of them has a human at a browser at 03:00 when a tremor arrives, so none of the interactive machinery — a login form, a second factor, a short-lived credential a user refreshes — applies. Each station holds one long string, sends it on every request, and that string is the whole authority. **Possession is authority**: there is nothing else to check. That single sentence drives every decision below. Because the credential is a bearer secret with no expiry and no second proof, the two questions that matter are *what does the server keep* and *how fast can you take it away*. ## What the key row holds | Column | Example | Why it exists | |---|---|---| | key identifier | `7f3a91c4` | A **non-secret** handle. Finds the row in one indexed read, and is safe to put in logs, dashboards and support tickets. | | verifier | a digest of the secret | Lets the server recognise a presented key without being able to produce one. | | display stub | `seis_live_7f3a91…4KQ2` | So an operator can tell two keys apart in a console without the console ever holding a secret. | | owner | station `GSN-0142` | Which partner this key speaks for; every accepted write is attributed to the key, not just the station. | | permissions | `readings:write` on its own series | The authority this key carries, which is narrower than the account behind it. | | timestamps | `created_at`, `last_used_at`, `revoked_at` | Age, evidence of use during a rotation, and the kill switch. | The plaintext appears exactly once, in the response that creates the key. After that the service cannot produce it, and neither can anyone who steals the table. ## Why the plaintext never stays - **A dump of the table would otherwise be four hundred working stations.** With digests it is four hundred useless rows; the attacker still has to find the originals. - **Support cannot be socially engineered into reading a key out**, because support genuinely cannot see it. 'I lost our key, can you read it back to me' has exactly one answer: issue a new one. - **Nobody inside the operator's own organisation can quietly copy one either.** An administrator can create, scope and revoke keys; they cannot exfiltrate an existing one. - **Reversible encryption is not a substitute.** If the ingest service can decrypt the column on demand, so can anything that gets the service's key material, and the property you wanted — that the server does not hold the credential — is gone. ## What 'shown once' costs, and what you give instead The operator has a real problem: three keys in a console and no way to tell which is on the field logger. You solve it with things that are not secret — a label the operator types at creation, the display stub, `created_at`, and above all `last_used_at`, which answers 'is this one still in use?' during a rotation. None of these lets anyone reconstruct the key, and together they make the one-time reveal tolerable. ## What the ingest endpoint does with a presented key 1. Read the credential from the `Authorization` header as a `Bearer` value. 2. Split it into the non-secret identifier and the secret part. 3. Look up the row by identifier — one indexed read, not a scan over every key. 4. Compute the digest of the presented secret and compare it against the stored verifier, using a constant-time comparison. 5. Reject if the row is revoked. 6. Check the key's permissions against the call being made. 7. Apply the key's own quota, and record `last_used_at` and the request against the key identifier. Steps 3 and 4 are why the identifier is stored separately from the verifier: without it you would have to hash the candidate against every row. ## What this model does not give you A bearer secret proves only that the caller has the string. It does not prove the request is fresh, it does not prove the caller is the station rather than someone who read the string out of a log, and it does not bind the credential to the request body. A scheme where the caller **signs** a canonical view of the request instead of sending the secret is a different model with different properties, and it is a different subject. Within the bearer model, the honest answers are: keep the credential off every channel that persists it, keep the permissions narrow, and be able to revoke quickly. Finally, be precise about refusals. A key the server cannot match to any row means *I do not know who you are*: `401`, with `WWW-Authenticate` naming the scheme. A key the server matched, belonging to a live station, that simply may not perform this call means *I know exactly who you are, and no*: `403`.

  • The station operator calls and says they have lost the only copy of the key. What happens?
    They get a new key. The service issues a second key for the same station, the technician deploys it, and the lost one is revoked on a short deadline. Recovery is impossible by construction, and that is the property you paid for — if support could read a key back, so could anyone who convincingly impersonated the operator on the phone.
  • Why store a separate non-secret key identifier at all, rather than looking the key up by its digest?
    You can look up by digest, and it works, but the identifier is what makes the key usable everywhere else: it is safe in logs, in a rate-limit counter, in an audit trail and in a support ticket, while a digest of the live credential is none of those things. It also lets the operator console show which key is which.
  • Does this row need an expiry column?
    Add one, even if the default is 'none'. An optional expiry lets you issue a genuinely temporary key for a contractor or a migration, and it lets a partner opt into a yearly renewal. What you must not do is rely on expiry as your revocation mechanism — a leaked key with eleven months left is live for eleven months.

It is the difference between a hotel keeping a photocopy of your passport and keeping only enough of its details to recognise it when you present it again. The second still identifies you at the desk; the first turns a burgled filing cabinet into four hundred usable passports.

saying these in an interview costs you the question

  • Encrypts the key reversibly so support can read it back to a locked-out partner
  • Stores the key in plain text and argues that database access is already restricted
  • Thinks a machine key must be presented with a username, like a user login
  • Returns 403 for a key string the server cannot match to any stored record
  • Treats a distant expiry date as the revocation story for a leaked key
  • Logs the full credential at the edge because it is 'just an API key, not a password'
open as a page

Your server stores each sensor's long-lived refresh credential in a table — why store a hash of it, and one row per device?

level: juniorimportance: must knowfreq 58%

basics

~20 s

Storing only a digest keeps the table from being a pile of live bearer credentials: a leaked read yields nothing a sensor could present. One row per device keeps revocation targeted, so killing one sensor does not strand the whole fleet.

open as a page

Where a browser client keeps an access token between requests decides which attack wins — which attack goes with which resting place?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A token in script-readable storage loses to script injection; a token in a cookie the browser attaches automatically loses to cross-site forged requests. Neither is safer until you name the threat you are buying down.

open as a page

A verified access token's `scope` claim names three permissions — may the caller now perform all three on your service?

level: juniorimportance: must knowfreq 58%

basics

~20 s

No. A scope records the ceiling the issuer put on what the caller may ask for; it grants nothing on your service. Your own authorization rules still decide, and both the scope check and your rule must pass.

open as a page

You are minting long-lived ingest keys for four hundred partner stations — how many random bits, what leading segment, and which one-way function at rest?

level: middleimportance: must knowfreq 60%

basics

~20 s

At least 128 bits from a cryptographically secure generator; a fixed, recognisable leading segment plus a non-secret key identifier; and a fast cryptographic digest such as SHA-256 as the verifier. A deliberately slow password-hashing function is the wrong tool for a high-entropy machine key.

open as a page

Where should a token issuer's private signing key live, and which component is allowed to mint with it?

level: middleimportance: must knowfreq 58%

basics

~20 s

The private signing key should rest in the smallest place that can still mint — ideally a hardware module or a signing service that returns tokens and never the key — and exactly one component should be permitted to use it.

open as a page

Which columns and index let a refresh-credential table answer "has this one been presented before?" in a single lookup?

level: middleimportance: must knowfreq 47%

basics

~20 s

A unique index on the stored digest makes a presented credential its own lookup key. The row also carries a family identifier for the rotation lineage and a state column; superseded rows must stay until expiry, or a replay looks merely unknown.

open as a page

A scheduler mails technicians a link carrying the access token in the query string — which copies can you never recall?

level: middleimportance: must knowfreq 60%

basics

~20 s

A credential in a URL is copied into access logs at every hop, into the referring-page header on same-origin asset requests, into shared caches, and into history, bookmarks and pasted-link previews. The log copies are the ones nobody can recall.

open as a page

Bench instruments sign each result upload with a signed timestamp — how do you size the skew window, and what replay does it still allow?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Size the window from measured client clock spread plus the longest legitimate retry delay, then accept that it bounds rather than prevents replay: a captured signature works until the signed timestamp ages out. Narrowing it further needs a shared seen-value store with the same lifetime.

open as a page

Sixty turnstile verifiers meet access tokens carrying a `kid` their cached key set lacks, and the issuer answers slowly — what should each verifier do?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Reject those tokens now — an unobtainable key is not evidence of validity — and schedule one bounded refetch instead of one per request. Tokens whose key identifier is already held keep verifying normally, so the outage stays confined to the unknown ones.

open as a page

Why bind an ingest key's permissions and its rate ceiling to the key itself rather than to the partner account behind it?

level: middleimportance: should knowfreq 52%

basics

~20 s

Because a station holds several keys for different jobs. Per-key permissions mean a leak exposes only what that job needed; per-key ceilings stop a runaway backfill starving the live feed; and revoking one key leaves the others running. Effective authority is the key's scope intersected with the account's.

open as a page

A queued canal-gate job runs eight hours after the operator's access token expires — what does its job record carry instead?

level: middleimportance: should knowfreq 48%

basics

~20 s

The job record carries the authorization decision rather than the credential: the subject it acts for, one action on one resource, the rule that allowed it, and a deadline after which the job is dropped rather than run late.

open as a page

Your service publishes a request-signing scheme for bench instruments uploading results — what belongs in the canonical string, and what does an unsigned header mean?

level: middleimportance: should knowfreq 45%

basics

~20 s

A canonical string fixes which bytes both sides sign: method, path, query in a defined order, a closed named set of headers, and a digest of the body as received. Anything outside it is unsigned and an intermediary may rewrite it undetected.

open as a page

A field tablet keeps its refresh credential in the platform's managed key store — what does that buy, and what does it not?

level: middleimportance: should knowfreq 45%

basics

~20 s

A platform-managed key store isolates a credential per application, keeps it encrypted at rest and can gate reads behind a device unlock. It does not protect the credential in use: anything running as that application can still ask for it.

open as a page

Why should the signature algorithms your token verifier accepts be deployment configuration rather than a library default?

level: middleimportance: should knowfreq 41%

basics

~20 s

Because the verifier, not the sender, must decide how a token is checked, and that set has to change on the issuer's rotation schedule across a fleet deployed in waves. An inherited default is invisible in review and can drift between environments.

open as a page

Your verifier must ask the issuer whether each access token is still valid — what do you cache, and for how long?

level: middleimportance: should knowfreq 37%

basics

~20 s

Cache the decision you derived, keyed on a keyed digest of the token rather than the token itself, for the shorter of a configured TTL and the credential's own remaining life. The TTL you pick is exactly the revocation lag you are accepting.

open as a page

A partner's long-lived ingest key turns up in a public repository — when does the last request carrying it actually fail, and how does the replacement reach the station?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Not instantly: the leaked key keeps working for as long as any cached verification answer lives, so the honest number is the worst-case cache lifetime plus propagation unless an invalidation is published. The replacement rides an overlap — issue a second key, let the station cut over, retire the first on a watched deadline.

open as a page

Your canal-gate worker re-derives the operator's authority at 04:00, is denied, and the water is already promised downstream — what should it do?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Neither run it nor drop it: park the job and escalate to someone who holds current authority, with the window's deadline attached. A withdrawn entitlement settles permission, not whether a commitment already made to a third party is void.

open as a page

Your hub ships verification keys in each consumer's configuration file — how do you rotate the signing key without a flag day?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Ship consumers a version that accepts two verification keys, confirm by measurement that they are running it, only then start signing with the new key, and withdraw the old one after the last token it signed has expired.

open as a page

A compromised sensor identity must lose every credential it holds — what does that kill write, and how big does the suppression list get?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Revoke every refresh row for that subject, then write one suppression entry keyed by subject and a cut instant so already-minted short-lived tokens are rejected. The list stays small: each entry expires with the tokens it suppresses.

open as a page

A server-side token handler holds the access token and gives the operations browser only a session reference — what does injected script get now?

level: seniorimportance: should knowfreq 42%

basics

~20 s

It converts a credential theft into a session ride. Injected script can still act as the user while the page is open, but there is no portable token to carry away and replay later from elsewhere. The cost is a stateful hop in the request path.

open as a page

Which facts are safe to freeze into a short-lived access token at mint, and which must be looked up per request?

level: principalimportance: should knowfreq 46%

basics

~20 s

Freeze facts that change rarely and are cheap to be wrong about for one token lifetime; look up per request any fact whose being wrong is expensive or irreversible. The token lifetime is the staleness budget, and someone must sign off that number.

open as a page

What revocation-lag number do you sign for on a 40,000-sensor fleet, and which part of it can this service actually shorten?

level: principalimportance: should knowfreq 30%

basics

~20 s

Sign the worst case, not the write. Revocation lag is a minted token's remaining lifetime plus the time a suppression entry needs to reach every verifier; only the second term belongs to this service, and shortening it buys a dependency.

open as a page

What does a published request-signing contract owe several hundred instrument sites you cannot upgrade in one night?

level: principalimportance: should knowfreq 28%

basics

~20 s

Versioned coverage rules, two signing secrets accepted at once so rotation is rolling rather than a cutover, a secret scoped per purpose, an explicit at-least-once delivery promise with an idempotency key, diagnosable rejections, and published test vectors.

open as a page

Should credential custody be one model across an operations browser and field tablets, or one model per client type?

level: principalimportance: should knowfreq 35%

basics

~20 s

Let custody diverge at the edge and converge at the server. The two clients face genuinely different threat surfaces, so one model serves one of them badly — but two models are only affordable if the server side stays a single credential format, verification path and revocation story.

open as a page