API Authentication
API Authentication: Basic, API Key, OAuth 2.0 & JWT
Nearly every API you test is protected, so authentication is the first thing to get right in any API suite — and one of the most valuable areas to test, because authentication bugs are security bugs. This guide explains the common methods (Basic auth, API keys, bearer tokens, OAuth 2.0 and JWT), how to use each in Postman and REST Assured, and the checks every tester should run.
Authentication vs Authorization
| Authentication | Authorization | |
|---|---|---|
| Question | Who are you? | What are you allowed to do? |
| Checks | Identity (password, token, key) | Permissions (roles, ownership) |
| Order | First | After authentication |
| Failure code | 401 Unauthorized | 403 Forbidden |
| Banking example | Logging in | Can this user view the account, transfer money, open admin pages? |
The Methods at a Glance
| Method | What's sent | Typical use |
|---|---|---|
| Basic auth | Authorization: Basic base64(user:password) |
Internal tools, simple services, test environments |
| API key | A key in a header (e.g. x-api-key) |
Server-to-server calls, public APIs with usage limits |
| Bearer token | Authorization: Bearer <token> |
Most modern APIs — the token usually comes from a login or OAuth flow |
| OAuth 2.0 | A framework for obtaining access tokens | Delegated access, "Sign in with…", service-to-service |
| JWT | A token format, usually sent as a bearer token | Stateless APIs, microservices, SSO |
They overlap: a common setup is OAuth 2.0 issuing a JWT access token that's sent as a Bearer token.
Basic Authentication
The client sends username:password encoded in Base64. For asha:Secret@1:
Authorization: Basic YXNoYTpTZWNyZXRAMQ==
Base64 is encoding, not encryption — anyone who sees the header can decode it instantly — so Basic auth is only acceptable over HTTPS.
// REST Assured
given().auth().preemptive().basic("asha", System.getenv("API_PASSWORD"))
.when().get("/account")
.then().statusCode(200);
preemptive() sends the header on the first request; without it, REST Assured waits for a 401 challenge first.
In Postman: Authorization → Basic Auth, and Postman builds the header for you.
API Key Authentication
A unique key identifies the calling application:
given().header("x-api-key", System.getenv("API_KEY"))
.when().get("/products")
.then().statusCode(200);
- Send keys in headers, not query parameters — URLs end up in logs, browser history and analytics.
- An API key usually identifies an application, not a user, so it's often combined with user authentication.
- Keys should be rotatable, scoped and rate-limited. They're not automatically "more secure" than Basic auth — a leaked key is as bad as a leaked password.
Bearer Tokens
The client logs in once, receives a token, and sends it with every request. In tests, fetch the token once and reuse it:
String token = given().contentType(ContentType.JSON)
.body(Map.of("username", "asha", "password", System.getenv("API_PASSWORD")))
.when().post("/auth/login")
.then().statusCode(200)
.extract().path("accessToken");
given().auth().oauth2(token) // adds "Authorization: Bearer <token>"
.when().get("/orders")
.then().statusCode(200);
In Postman, save it in the login request's Tests tab:
pm.environment.set("token", pm.response.json().accessToken)
Then set the collection's authorization to Bearer Token with {{token}}.
More: Postman Authentication & Tokens.
OAuth 2.0
OAuth 2.0 is an authorization framework: instead of giving an app your password, an authorization server issues the app an access token with limited scope and lifetime (often with a refresh token to get a new one).
| Grant type | Used when |
|---|---|
| Authorization Code (+ PKCE) | A user signs in through a browser — web and mobile apps |
| Client Credentials | Service-to-service calls with no user — the easiest to automate in tests |
| Refresh Token | Getting a new access token without logging in again |
// Client credentials: get a token from the authorisation server
String accessToken = given()
.formParam("grant_type", "client_credentials")
.formParam("client_id", System.getenv("CLIENT_ID"))
.formParam("client_secret", System.getenv("CLIENT_SECRET"))
.formParam("scope", "orders.read")
.when().post("https://auth.example.com/oauth/token")
.then().statusCode(200)
.extract().path("access_token");
"Sign in with Google" uses OpenID Connect, an identity layer built on OAuth 2.0. For browser-based flows in UI tests, testers usually get tokens through an API or a test identity provider rather than automating the third-party login page.
In Postman, Authorization → OAuth 2.0 → Get New Access Token handles the flow.
JWT (JSON Web Token)
A JWT has three Base64URL-encoded parts separated by dots — header.payload.signature:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMDEiLCJuYW1lIjoiQXNoYSIsInJvbGUiOiJ0ZXN0ZXIiLCJpYXQiOjE3OTAwMDAwMDAsImV4cCI6MTc5MDAwMDkwMH0
.ZPk0pMT0jIXeVoC-cekSTs-5fTJzNpbEToxDYBxcYhw
Decoding the middle part gives the payload (the claims):
{ "sub": "101", "name": "Asha", "role": "tester", "iat": 1790000000, "exp": 1790000900 }
- Signed, not encrypted — anyone can read the payload, so it must never contain passwords or sensitive data. The signature only proves it hasn't been changed.
- Stateless — the server validates the signature and expiry instead of looking up a session.
exp−iat= 900 seconds here: a 15-minute access token, renewed with a refresh token.
What to Test: Security Checklist ⭐
| Test | Expected |
|---|---|
| No token / no credentials | 401 |
| Invalid or malformed token | 401 |
| Expired token | 401 (then refresh works) |
| Token with a tampered payload (e.g. role changed to admin) | 401 — signature check fails |
Token with "alg": "none" or a different algorithm |
401 |
| Valid user, wrong role (e.g. admin-only endpoint) | 403 |
User A requests user B's data (/orders/{B's id}) |
403 or 404 — a leak here is a serious bug (broken object-level authorization) |
| Logged-out or revoked token | 401 |
| Credentials or tokens in URLs, logs or error messages | Never present |
| HTTP (not HTTPS) | Rejected or redirected |
| Repeated failed logins | Rate limiting or lockout (429) |
given().when().get("/orders").then().statusCode(401); // no token
given().auth().oauth2("not-a-real-token").when().get("/orders").then().statusCode(401); // invalid
given().auth().oauth2(userToken).when().get("/admin/users").then().statusCode(403); // wrong role
Design full positive and negative sets for protected endpoints in the API Testing Scenario Lab.
More: REST Assured Authentication.
Best Practices
- HTTPS for every authenticated request.
- Short-lived access tokens with refresh tokens.
- Never put credentials or tokens in URLs.
- In test code, read secrets from environment variables or CI secrets — never commit them.
- Fetch a token once per suite (or per user role) and reuse it, rather than logging in before every test.
From Real Projects
My API testing experience covers CRUD operations, HTTP methods, JSON path, and validating JSON and XML data. On Testsigma, a platform that itself automates API tests, understanding requests, responses and status codes was part of understanding the product. Whatever tool you use, validate the status code, the response structure and the values — not just that the call returned something. Never hard-code tokens in tests; request or read them at run time.
📚 Official documentation: MDN: HTTP · ISTQB Glossary of testing terms
FAQs
What are the main types of API authentication?
Basic auth, API keys, bearer tokens, OAuth 2.0 (to obtain tokens) and JWT (a token format).
Is Basic authentication secure?
Only over HTTPS — Base64 is encoding, not encryption, so the credentials are readable if intercepted.
What is the difference between OAuth 2.0 and JWT?
OAuth 2.0 is a framework for issuing access tokens; JWT is a format a token can take. OAuth access tokens are often JWTs.
Can anyone read a JWT?
Yes — the payload is only Base64URL-encoded. The signature prevents tampering, not reading, so JWTs must not carry sensitive data.
401 vs 403?
401: not authenticated (missing, invalid or expired credentials). 403: authenticated but not allowed.
How do you handle authentication in automated API tests?
Get a token once through the login or OAuth endpoint, store it (a variable or a shared request specification), and add negative tests for missing, invalid, expired and wrong-role tokens.