API Authentication

API authentication is how an API decides who sent a request. There is no login screen between a client and an API, so the client attaches a credential to every request — an API key, a user-id and password (Basic) or a token (Bearer) — and the server checks it first. A mobile app calling GET /v1/orders/9021 sends Authorization: Bearer mF_9.B5f-4.1JqM; the API answers 200 for a valid token and 401 for a missing or invalid one.

This lesson covers what the three common schemes send, where a credential may travel and how to test that an API refuses everything else. What a known caller may do — authorization — comes later in the module.

Why it matters

An endpoint that skips the credential check hands its data or actions to anyone who can reach it. Normal use never shows the gap, because real clients always send a valid token; only a test that deliberately sends a missing, wrong or expired credential finds it. The OWASP API Security Top 10 (2023) lists Broken Authentication as API2.

Credentials also leak quietly: a key in a URL lands in access logs, a Basic header in a screenshot is a readable password, and a token that survives logout stays useful to whoever copied it. Knowing how each scheme works keeps the test team from becoming the leak.

API keys, Basic and Bearer: what each one sends

Most APIs use one of three schemes, and the contract says which. In an OpenAPI document, securitySchemes shows either type: apiKey with the header (or query parameter) that carries the key, or type: http with scheme: basic or scheme: bearer, which use the standard Authorization header.

All three travel in a request header and none of them protects itself: whoever copies the header can replay the request, so every scheme depends on HTTPS.

  • API key: a long random string issued to a client application, sent in a header the API chooses, such as X-API-Key. No single standard defines it. A key identifies the calling application rather than a person — OWASP says API keys should authenticate API clients, not users — and it usually stays valid until it is rotated (replaced with a new key) or revoked.
  • Basic authentication (RFC 7617): the client joins the user-id and password with a colon, encodes the result in Base64 and sends it after Basic. Base64 is an encoding, not encryption: the value below decodes to qa.tester:S4mple-pass. The password goes with every request and works until it is changed; the RFC does not consider the scheme secure without TLS.
  • Bearer token (RFC 6750): the client first obtains a token, for example from a login endpoint or an OAuth 2.0 authorization server, and sends it after Bearer. Any party in possession of a bearer token can use it, so the RFC recommends short-lived tokens (one hour or less) restricted to the API they are meant for. Many bearer tokens are JWTs, covered later in this module.
API key, Basic and Bearer all travel in a request header, but a copied key works until it is revoked, a Basic value decodes to the password, and a bearer token works until it expires.

API keyNo single standard

A long random string issued to a client application, in a header the API names

X-API-Key: demo_7Hq2LmZ9pR4tKx

Identifies:
The calling application, not a person
If someone copies it:
It works for them until the key is rotated or revoked
Challenge with a 401:
No standard one: the contract decides
First negative tests:
Missing, revoked, sent in the query string

Basic authenticationRFC 7617

user-id:password, Base64-encoded, sent after Basic

Authorization: Basic cWEudGVzdGVyOlM0bXBsZS1wYXNz

Identifies:
The user whose user-id and password it carries
If someone copies it:
Decoding gives the password, usable until it is changed
Challenge with a 401:
WWW-Authenticate: Basic realm="api"
First negative tests:
Wrong password, unknown user, one error message for both

Bearer tokenRFC 6750

A token obtained first and sent after Bearer; whoever holds it can use it

Authorization: Bearer mF_9.B5f-4.1JqM

Identifies:
The user or client it was issued to
If someone copies it:
It works for them until it expires or is revoked
Challenge with a 401:
WWW-Authenticate: Bearer realm="orders"
First negative tests:
Missing, one character changed, expired, used after logout

Example values are invented for this lesson. Anyone who sees the request can replay any of the three, so each one needs HTTPS.

Text version
API key:  X-API-Key: demo_7Hq2LmZ9pR4tKx
Basic:    Authorization: Basic cWEudGVzdGVyOlM0bXBsZS1wYXNz
Bearer:   Authorization: Bearer mF_9.B5f-4.1JqM

Try it: Base64 Decoder Try it: Base64 Encoder

Where credentials travel: headers, never URLs

A credential in a URL is copied to places nobody protects: server and proxy access logs, monitoring tools, browser history and every ticket where someone pastes the link. RFC 9110 warns that many servers, proxies and browsers log or display the target URI, so sensitive information does not belong in it, and forbids senders to put user:password@ into http and https URLs; RFC 6750 says a bearer token SHOULD NOT be sent as a query parameter unless there is no other way; OWASP lists tokens and passwords in the URL as broken authentication.

The rule for a tester: the credential travels in a header, over HTTPS, and is never echoed in an error message or written to a log. If the API also accepts it in the query string, report that as a risk even though it "works".

# Leaks: the key is part of the URL and is written to access logs
GET /v1/products?api_key=demo_7Hq2LmZ9pR4tKx HTTP/1.1
Host: api.shop.test

# Expected: the key travels in a header, over HTTPS
GET /v1/products HTTP/1.1
Host: api.shop.test
X-API-Key: demo_7Hq2LmZ9pR4tKx

Try it: API Header Builder Try it: cURL Generator

What to test on every protected endpoint

As the lesson on authentication and authorization showed, RFC 9110 gives a request that lacks valid credentials 401 with a WWW-Authenticate challenge, and a request the server understood but refuses 403. For an API, the challenge names the scheme to use, such as WWW-Authenticate: Basic realm="api" or WWW-Authenticate: Bearer realm="orders", where realm names the protected area. API keys have no standard challenge, so the contract decides. A 403 for a known caller is an authorization decision, which has its own lesson; the cases below test authentication only.

  • Missing: no credential → 401 with a challenge.
  • Malformed or wrong scheme: an empty value, Bearer with no token, Basic sent to a Bearer-only API → 401, never 500. A contract that follows RFC 6750 may instead answer a malformed Bearer header with 400 and error="invalid_request".
  • Invalid: a wrong password, an unknown user, a key or token with one character changed → 401.
  • Expired or revoked: a token past its expiry, a revoked key, a token used after logout when the contract says logout revokes it → 401.
  • Wrong place: the credential in the query string or under another header name → refused.
  • Every endpoint and method: a protected GET proves nothing about the DELETE on the same path, or about older API versions that are still deployed.
  • Safe errors: one message for an unknown user and a wrong password, no stack traces, and the credential never echoed back.

Try it: API Authentication Tester

Worked example: an orders endpoint with Bearer tokens

The contract: "GET and DELETE /v1/orders/{id} require Authorization: Bearer <token>. Tokens expire 30 minutes after they are issued and are revoked at logout. A missing or invalid token returns 401." With test user A, write each case and its expected answer before you send anything:

Rows 5 and 6 need timing and a logout. Row 5 applies 2-value boundary value analysis to the 30-minute lifetime: 30 minutes is the first expired value, and a token at 29 minutes, the last valid value, must still return 200. If the server adds a grace period for clock differences, the contract should state it. Row 9 also checks state: a 401 that deleted the order anyway is a defect. User B's token on A's order is an authorization test for a later lesson. The bug report for a failed row names the exact request, the status and headers received and the expected answer, with the credential masked.

#  Request to /v1/orders/9021 (A's order)    Expected
1  GET, valid token of user A                200, order JSON
2  GET, no Authorization header              401, WWW-Authenticate: Bearer realm="orders"
3  GET, Authorization: Bearer (empty value)  401
4  GET, token with last character changed    401, error="invalid_token" in the challenge
5  GET, token issued 30 minutes ago          401, error="invalid_token" in the challenge
6  GET, token of A after A logged out        401
7  GET, Authorization: Basic (A's password)  401, challenge names Bearer
8  GET, valid token in ?access_token=        401; if accepted, report it as a risk
9  DELETE, no Authorization header           401, and order 9021 still exists

Try it: API Authentication Tester Try it: cURL Generator

Common mistakes

  • Testing only the happy path with a valid token and assuming the endpoint is protected.
  • Treating Base64 as protection: a Basic header in a log or a screenshot is a readable password.
  • Accepting any 4xx: 401 and 403 mean different things, and a 401 must carry WWW-Authenticate.
  • Checking GET only, and forgetting the write methods and older API versions.
  • Pasting real tokens, keys or passwords into bug reports, chat or shared collections instead of masking them.
  • Never testing expiry and revocation, so a token that still works after logout goes unnoticed.

The QA perspective

Authentication questions belong in the contract review: which scheme, which header, how long a token lives, what logout does, what a 401 looks like. Ask the developer when the OpenAPI document leaves any of these open, and agree which test accounts and keys QA owns in each environment.

Record the credential type and the account used in each test, never the secret, and keep test keys in the team's secret store or in environment variables. Report an endpoint that accepts a request without a valid credential as a security defect, usually critical or blocker. Run authentication tests only on systems you own or have written permission to test.

Practice exercise

Review an authentication test log

Your task

A teammate ran authentication tests against the orders API from the worked example, with the same contract, and saved the log below.

Decide for each row whether the API behaved correctly, and for every defect state what was expected and how severe it is.

Material
Orders API, test environment, endpoint /v1/orders/9021
#  Method and credential                   Status  Observed
1  GET, valid token of user A              200     order JSON
2  GET, no Authorization header            401     WWW-Authenticate: Bearer realm="orders"
3  GET, token with last character changed  200     order JSON
4  GET, token of A after A logged out      200     order JSON
5  GET, Authorization: Basic               500     stack trace in the body
      cWEubGVhZDpXaW50ZXItVGVhLTQy
6  GET, ?access_token=<valid token of A>   200     order JSON
7  DELETE, no Authorization header         204     empty body
8  GET, token issued 35 minutes ago        401     no WWW-Authenticate header
Steps
  1. Write the expected result for each row from the contract and RFC 9110.
  2. Mark each row as correct or as a defect.
  3. Decode the value in row 5 with a Base64 decoder and note what it means for the log itself.
  4. Order the defects by severity and write a bug title for the most severe one.
Show the model answer
Model answer

Rows 1 and 2 are correct.

Row 3, critical: expected 401 with error="invalid_token", but a modified token was accepted, so tokens are not validated and a made-up token may work. Row 4, major: expected 401, but logout does not revoke the token, against the contract.

Row 5, major: expected 401 with WWW-Authenticate: Bearer, not a 500 that leaks a stack trace. The value decodes to qa.lead:Winter-Tea-42, so the log itself exposes a test password: mask credentials in logs and reports, and have that password changed.

Row 6, major: expected 401, but the token is accepted in the URL, where access logs keep it. The contract names the header only and RFC 6750 says SHOULD NOT, so report it as a security defect or as a risk for the team to decide.

Row 7 is the most severe, blocker or critical: an anonymous DELETE succeeded. Confirm that order 9021 is gone, then report "DELETE /v1/orders/{id} deletes the order without an Authorization header and returns 204 instead of 401".

Row 8: the refusal is right, but RFC 9110 requires WWW-Authenticate on every 401, so this is a minor defect. Suggested order: 7, 3, 4, 6, 5, 8.

Frequently asked questions

Is Basic authentication still acceptable for APIs?

Only over HTTPS, and mostly for server-to-server calls or internal tools. The password is sent with every request and is only Base64-encoded, so one leaked request or log line exposes it. Many teams prefer tokens or keys that can be revoked without changing a person's password.

Should a missing credential return 401 or 403?

401, as the lesson on authentication and authorization explains. For a Bearer API, RFC 6750 also separates the two kinds of 401: a request that sent no credential should get a challenge without an error code, and a request whose token is expired, revoked or invalid should get error="invalid_token" in the challenge. So a test for an expired token checks the challenge, not just the status.

Is an API key enough to protect user data?

Usually not on its own. A key identifies the calling application, not the person using it, and it lives for a long time. The OWASP API Security Top 10 (2023) says API keys should authenticate API clients, not users; user data needs user authentication, typically with a token.

What is the difference between a bearer token and a JWT?

"Bearer" describes how a token is used: whoever holds it can present it. JWT is a format for the token's content. A bearer token can be a JWT or a random string that only the server can look up. The JWT guide explains the format.

References

Practise with Mutqan tools

Test Bearer, Basic, API-key and custom-header authentication against an endpoint and compare authorised vs unauthorised responses.

API Testing

Decode Base64 or Base64url strings to text, with automatic padding repair.

Encoding

Encode text or files to Base64 (standard or URL-safe) in your browser.

Encoding

Build request headers for REST APIs (auth, content negotiation, tracing) and export to cURL, fetch, axios or HTTP.

Web

Build cURL commands from method, URL, headers, body and auth options.

Web

Go deeper

Your progress on this topic

Part of the QA Roadmap