API tests

Authentication in API tests: keys, tokens, OAuth and expiry

The first API test often works with a token copied manually from the browser. An hour later it expires, CI has no user session and the entire automation run stops before its first business check. Authentication cannot remain a one-off setup step that somehow gets a token. It is a tested capability with its own scenarios, secrets and lifecycle.

This article focuses entirely on the API perspective: how a client proves its identity, which permissions a credential carries and what happens during expiry or rotation. Screens, redirects and form behaviour are covered separately in testing login, 2FA and OAuth through the UI.

Distinguish authentication from authorisation first

Authentication answers who or what is calling the API. Authorisation decides what that client may do. A valid token does not therefore grant access to every endpoint.

A test should distinguish at least these states:

Under HTTP Semantics in RFC 9110, a 401 Unauthorized response requires authentication credentials and includes a WWW-Authenticate challenge; 403 Forbidden means the server understood the request but refuses to fulfil it. A particular API contract may intentionally hide whether a resource exists or limit error detail, so test the agreed behaviour rather than one universal expectation for every system.

Map every way of obtaining access

One API may use several mechanisms at the same time:

For every mechanism, create a matrix showing who issues the credential, its intended recipient, where it is sent, lifetime, scopes, refresh, rotation and revocation. Without this map, tests use one administrator token for everything and never verify genuine permission boundaries.

OAuth is an authorisation framework, not one specific kind of login. RFC 6749 defines roles, access tokens, scopes, expiry and refresh tokens. Current security guidance is provided by OAuth 2.0 Security Best Current Practice, RFC 9700. Test design should follow the flow used by the particular client rather than one universal script.

Store this matrix with the API version and test-environment configuration. When the issuer, audience or permitted flow changes, it then shows which scenarios need updating and which expectations must not change silently.

Test an API key as an identity with a lifecycle

An API key is usually simpler than an OAuth token, but it is not merely a random header value. It may identify an application, customer or environment and carry rate limits or an allowed operation set.

A basic scenario set includes:

Do not send a key in a URL where the contract supports a safer header. URLs can enter history, proxy logs and analytics. Test code should contain the name of a secret, not its value. The credential is injected from the protected store for that environment at runtime.

Test rotation without affecting other users of the test environment. Do not disable a shared key used by another team. A dedicated test client or controlled administration API with isolated scope is safer.

A bearer token is not just a longer API key

Anyone who obtains a bearer token can use it; possession is the basis for the call. RFC 6750 describes its HTTP use and security threats. Test logs, screenshots and reports must therefore not retain the complete token.

With a JWT, inspecting claims is useful, but the test should not trust a token just because it can decode it. Base64 decoding is not signature verification. The API should verify the signature and permitted algorithm, issuer, audience, time claims and, according to the contract, scope and other constraints.

Negative scenarios can use a token with:

Create these tokens only through a test identity provider or test signing keys. The production provider’s private signing key must not be available to the test suite.

Expiry should be a test, not an occasional incident

A common automation mistake is obtaining one token at startup and sharing it with every test regardless of lifetime. A short local run passes; a parallel or hour-long suite begins returning 401 intermittently.

The test client should know the expiry time and apply a safety margin. If a token expires in 30 seconds, it should not begin a long operation. It should not rely only on the local clock either; a small clock skew can exist between client and server, handled according to the contract’s tolerance.

Verify at least:

  1. a call just before the agreed expiry boundary;
  2. a call after expiry without refresh;
  3. automatic acquisition of a new token and retry of only a safe operation;
  4. behaviour after revocation of a token that has not yet expired, where revocation is supported;
  5. a long-running test in which the token expires naturally;
  6. parallel requests discovering the need for refresh at the same time.

The last case can create a refresh storm: twenty workers simultaneously request a new token. A refresh lock and process-level shared cache let one worker refresh while the others use the result. The cache must be separated by identity, audience and scope, or a test may receive another scenario’s permission.

A refresh token needs its own negative scenarios

A refresh token usually has a different sensitivity and lifetime from an access token. The authorisation server may issue a new refresh token on every use and invalidate the previous one. The test must store the new value atomically, or a parallel process can overwrite the current token with an old one.

Verify:

Do not automatically retry every business request after 401. For a non-idempotent operation such as creating a payment, the server may have performed the action while the response was lost. The client needs an idempotency key or a subsequent state check, not a blind retry.

Test the OAuth flow separately from the business API

If a test fails while obtaining a token, it has said nothing about the order endpoint. Separate the layers:

For service-to-service communication, a client credentials test does not use a person’s username and password. Public applications use PKCE with the authorization code flow according to system design. Do not copy old Resource Owner Password Credentials tutorials into new automation; RFC 9700 says this grant must not be used.

A token-endpoint test should verify status, response structure, token type, lifetime and allowed scopes without writing values into the report. Active negative attempts in production should be limited by a security agreement; most such scenarios belong in an isolated environment.

Scopes, roles and data ownership form a matrix

One administrator token creates convenient but weak tests. Every important endpoint needs at least an allowed and a denied identity. For object-level authorisation, “ordinary user” is not enough: user A should read their own order, not user B’s order.

A practical matrix has endpoints as rows and identities or permissions as columns. Each intersection specifies expected read, write and any field masking. A reduced risk-based matrix is better than running every role against everything combinatorially.

For an error response, do more than check status. Verify that it does not disclose sensitive detail, reveal a foreign resource beyond the contract or change data. Authorisation must also apply to bulk endpoints, exports and nested resources, not only one GET /orders/{id}.

Mock identity provider or real service

A mock provides fast deterministic scenarios: expiry can move without waiting, arbitrary scopes can be created and errors simulated. It does not verify real TLS, client configuration, the supported flow, key rotation or an external provider’s behaviour.

A combination is therefore useful. Most business tests use a fast test issuer or prepared tokens with controlled claims. A smaller integration suite regularly obtains a token from the genuinely configured provider in a non-production tenant. The distinction between a mock, sandbox and real service applies directly here.

Secrets and reports are part of the test

A client secret, API key and refresh token do not belong in the repository, test data or a command-line argument visible in a process list. CI should read the secret from protected storage, restrict it to the necessary environment and mask output.

Masking the exact value is not enough if a log prints the entire Authorization header, a URL containing a key or the token endpoint’s JSON response. An allowlist of permitted headers and fields is safer than an endless list of forbidden names. For diagnosis, retain a hash or last four identifier characters, issuer, audience, scope and expiry—not the credential.

Test accounts should have minimum permissions, a clear owner, automated rotation and a safe recovery process. A shared “admin test” account weakens authorisation checks and complicates audit.

What a reliable authentication layer delivers

Business tests stop failing randomly on expired tokens without ignoring security boundaries. The team can distinguish a failed identity provider, credential acquisition, scope and business API function. Key rotation can be rehearsed before a production change, and reports provide diagnostics without leaking secrets.

Authentication is only one layer. Status codes, schema, idempotency, error states and business rules belong to the broader strategy for what to test in an API.

Next step

For one API, write down the identity, credential, scope, expiry and refresh matrix. Create one central test client that obtains tokens safely, caches them with a margin and redacts logs. Add the first three negative scenarios: missing credential, a valid identity without permission and an expired token. This separates access failures from business failures and prepares the suite for long and parallel runs.

Related topics

You might also be interested in

You can detect errors in the business logic more quickly directly through the API

Functional, integration and contract testing of REST, SOAP or GraphQL interfaces with the possibility of connection to the CI/CD pipeline.