skip to content

How should an automated case at a service interface obtain and refresh its access token?

level: middleimportance: must knowfreq 61%

answer

  1. The case declares an identity, not a token
  2. Credentials come from the runner, not the repo
  3. Cache per identity, refresh before expiry
  4. One retry, and never on a refusal
  5. Auth-negative cases must opt out

basics

~10 s

Fetch the token programmatically from the token-issuing endpoint at setup using credentials injected from the environment, cache it per test identity, and refresh it before it expires. Never commit a hand-copied token.

solid answer

~50 s

The token is part of the harness, not part of the case. At setup I request one from the token-issuing endpoint using credentials supplied by the runner's secret store — never committed, never a real person's login — and cache it keyed by test identity, so a case declares which identity it acts as rather than inheriting an ambient one. I refresh proactively: if the remaining lifetime is under a safety margin, fetch a new token before the call rather than after a failure. A reactive refresh on a single unauthorized response is a reasonable backstop, but it must fire once and never on a forbidden response, which a new token cannot change. Cases that deliberately assert rejection of an absent or expired token must opt out of the refresh. And the token never reaches logs or failure artefacts — redact it at the boundary.

code

pseudocode · 20 lines
pseudocode
tokenCache = map from identity to { value, expiresAt }
MARGIN_SECONDS = 90

function tokenFor(identity):
    entry = tokenCache[identity]
    if entry == null or entry.expiresAt - now() < MARGIN_SECONDS:
        creds = secrets.require("SEATMAP_" + identity + "_CREDENTIALS")
        issued = client.post("/auth/token", creds)
        entry = { value: issued.accessToken, expiresAt: now() + issued.expiresIn }
        tokenCache[identity] = entry
    return entry.value

function call(identity, request):
    request.header("Authorization") = "Bearer " + tokenFor(identity)
    response = client.send(request)
    if response.status == 401 and not request.skipAuthRefresh:
        tokenCache.remove(identity)
        request.header("Authorization") = "Bearer " + tokenFor(identity)
        response = client.send(request)   # exactly once
    return response

go deeper

for a junior

Be ready to say that the token is fetched by the harness at setup rather than pasted in, that credentials come from the environment, and that a case names the identity it acts as. Knowing that tokens expire during a long run is the main point to carry.

for a middle

Explain the mechanics an interviewer is really probing: caching keyed by identity, a proactive refresh with a safety margin larger than the slowest request, a single reactive retry on an unauthorized response, and why a forbidden response is excluded from that retry.

for a senior

Show that you have debugged this in anger: a cluster of unauthorized failures in the back half of a long run, a refresh stampede across parallel workers, an auth-negative case turned green by the harness repairing it. Talk about redaction and short-lived per-environment identities.

for a principal

Own the policy across teams: which identities exist per environment, who provisions and rotates them, why production credentials never reach a suite, and how identity separation keeps a permission regression from hiding behind a single over-privileged account.

Authentication is infrastructure for a case at a machine interface, and the design goal is that no case ever mentions a token. What a case declares is an identity — "as a gate agent", "as a self-service passenger" — and the harness turns that into a credential on every request. Getting this wrong produces two characteristic disasters: a suite that dies halfway through with a wall of unauthorized failures, and a suite whose green run proves nothing because one over-privileged identity was doing everything. ## Acquire, do not paste A token pasted into a configuration file or a constant is dead on arrival: it expires, it is a secret in version control, and it silently encodes whichever identity the person who pasted it happened to hold. Acquire it by calling the token-issuing endpoint with credentials the runner injects from a secret store or environment. There must be no committed fallback value — a default that quietly works locally is exactly how a real credential ends up in a repository. If the environment does not supply credentials, the suite should fail loudly at setup with a message naming the missing variable, not skip cases or fall back to an anonymous call. ## Cache per identity, not per case Fetching a token per request turns every case into two calls and puts the identity provider on the critical path of the whole run. Fetch once per identity per run and cache it. Keying the cache by identity matters as much as the caching: on an airline seat-map service an 11-person team ran cases for gate agents, ground staff and passengers through a single shared token because it was the one that worked. Half the suite was asserting behaviour the real caller could never reach, and a permission regression stayed invisible for weeks. One token per identity makes the case's caller explicit and makes an accidental privilege change fail somewhere. ## Refresh proactively, with a margin The classic failure is arithmetic: a token that lives 890 seconds against a suite that takes 23 minutes. The last third of the run fails with unauthorized responses, and because the failures cluster, the team reads them as an authorization regression and spends a morning on it. The fix is a proactive refresh — before issuing a request, if the token's remaining lifetime is below a safety margin comfortably larger than the slowest expected request, fetch a new one. The margin exists because a token that is valid when the client checks can expire in flight. A reactive refresh is a useful backstop: on a single unauthorized response, refresh once and retry once. Two rules keep it honest. It must not apply to a forbidden response, because that outcome is about what the identity may do, and a new token for the same identity will not change it — retrying there just hides a real authorization result. And it must be bounded to one attempt, or a genuine authentication defect turns into a slow loop that eventually reports a timeout instead of the actual cause. ## Parallel workers and shared caches When the suite runs in parallel, a shared token cache can stampede: several workers notice the same expiry at the same moment and all call the token endpoint, sometimes tripping the identity provider's own rate limit. Either guard the refresh so one caller performs it while the others wait, or give each worker its own token. Per-worker tokens are simpler and usually cheap; a shared cache is worth the coordination only when issuing a token is expensive or throttled. ## Cases that are about authentication A case asserting that an expired or missing token is rejected is a case the refresh machinery will happily repair into a false pass. Those cases have to opt out of automatic acquisition and refresh, and it is worth making the opt-out explicit in the case name, because a future reader who sees an unauthorized assertion will otherwise assume it is a harness bug. Keep such cases about the authentication mechanism itself — what a given identity is permitted to do, and how that permission grid is captured as an artefact, is a different concern with its own home. ## Hygiene Redact the token everywhere it might surface: request logs, failure artefacts, captured traffic, the report attachment that helpfully includes the headers. Prefer short-lived test identities per environment, created and removed by the environment's own provisioning, over long-lived shared accounts whose password nobody dares rotate. And never point a suite at credentials issued for the production environment, even for a read-only case — the blast radius of a mistaken write is not worth the convenience, and the presence of those credentials in a runner is itself the risk.

  • Why should the automatic retry after a refresh never fire on a forbidden response?
    A forbidden response says the identity is known but not permitted, so a freshly issued token for the same identity carries the same permissions and will be refused again. Retrying there costs a round trip and, worse, hides a genuine authorization regression behind a harness behaviour. Only an unauthorized response — the family that means the credential itself was missing, malformed or expired — is worth a single refresh and retry.
  • How do you keep credentials for several test identities out of the repository?
    The runner injects them as environment variables or pulls them from a secret store at start-up, and the harness fails fast with a named variable when one is absent — no committed default, no local fallback file that happens to work. Identities are per environment and short-lived where the platform allows it, and any value that reaches a log or report attachment is redacted at the boundary rather than trusted not to appear.
  • What breaks when several parallel workers share one cached token?
    They can all observe the same expiry and stampede the token-issuing endpoint, which sometimes trips its rate limit and turns a token problem into a suite-wide outage. It also makes a refresh a shared mutation, so one worker can swap the token mid-request for another. Either serialise the refresh so one caller does it while the rest wait, or give each worker its own token — usually the simpler and cheaper choice.

saying these in an interview costs you the question

  • Pasting a hand-copied token into the suite's configuration
  • Refreshing by re-running the suite when it starts failing
  • Retrying every refusal, including a forbidden response
  • Using one over-privileged identity for the entire suite
  • Logging the token in request or failure output
  • Pointing the suite at production credentials for convenience

context