API Authentication
The examples below start with the three most common strategies: Bearer token, Basic auth, and OAuth2 client credentials. ApiAuth also provides API-key header/query, HMAC, OAuth2 password, and a limited digest placeholder.
Bearer Token
ApiClient.get("/api/me")
.auth(ApiAuth.bearerToken("my-secret-token"))
.send();
Basic Auth
ApiClient.get("/api/admin")
.auth(ApiAuth.basicAuth("admin", "password"))
.send();
OAuth2 — Client Credentials
Token is fetched automatically on first use and cached until expiry. No manual token refresh needed.
ApiClient.setGlobalAuth(ApiAuth.oauth2(
"https://auth.example.com/token",
System.getenv("CLIENT_ID"),
System.getenv("CLIENT_SECRET")
));
The framework sends a POST with grant_type=client_credentials and caches the returned access_token until it expires (using the expires_in field from the response).
Additional ApiAuth Factories
ApiAuth.apiKey("X-Api-Key", System.getenv("API_KEY"));
ApiAuth.apiKeyQuery("api_key", System.getenv("API_KEY"));
ApiAuth.hmac(System.getenv("API_KEY"), System.getenv("API_SECRET"), "HmacSHA256");
ApiAuth.oauth2Password(tokenUrl, clientId, clientSecret, username, password);
ApiAuth.digest(username, password) only emits a Basic-like payload with a Digest prefix; it does not implement the RFC challenge/response flow. Use a custom interceptor when full Digest authentication is required. The HMAC factory signs the API-key value and adds X-Api-Key and X-Signature; it is not a general request-body signing scheme.
Global Auth — Set for Each Test
Set auth for each test in @BeforeMethod; subsequent requests in that test include it. Global auth is thread-local and is cleared at test cleanup. It is not suite-wide; parallel tests must each install their own auth. No .auth() call is needed on each request.
import io.testfly.test.BaseApiTest;
import io.testfly.client.ApiAuth;
import io.testfly.client.ApiClient;
import io.testfly.client.ApiResponse;
import java.util.Map;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class UserApiTest extends BaseApiTest {
@BeforeMethod
public void authenticate() {
ApiResponse login = ApiClient.post("/api/auth/login")
.body(Map.of("username", "admin", "password", "pass"))
.send();
ApiClient.setGlobalAuth(ApiAuth.bearerToken(login.json("$.token")));
}
@Test
public void getUsers() {
// Token applied automatically — no .auth() needed
ApiClient.get("/api/users").send().assertStatus(200);
}
}
The framework clears global auth automatically after each test, so tests don't bleed into each other.
To clear it manually:
ApiClient.clearGlobalAuth();
@UseAuth — Config-Based Auth Strategies
Define named auth strategies in testfly.yml and apply them per test or per class with @UseAuth.
Configuration
api:
auth:
adminToken:
type: bearer
token: ${ADMIN_TOKEN} # resolved from environment variable
basicUser:
type: basic
username: user
password: ${USER_PASSWORD}
serviceAccount:
type: oauth2
tokenUrl: https://auth.example.com/token
clientId: ${CLIENT_ID}
clientSecret: ${CLIENT_SECRET}
Token values support ${ENV_VAR} interpolation — resolved from environment variables or system properties at runtime.
Usage
@Test
@UseAuth("adminToken")
public void createUser() {
apiClient().post("/api/users")
.body(Map.of("name", "Alice"))
.send()
.assertStatus(201);
}
Apply to an entire class:
@UseAuth("serviceAccount")
public class OrderApiTest extends BaseApiTest {
@Test
public void listOrders() {
ApiClient.get("/api/orders").send().assertStatus(200);
}
@Test
public void createOrder() {
ApiClient.post("/api/orders").body(...).send().assertStatus(201);
}
}
Method-level @UseAuth takes precedence over class-level.
Per-Request vs Global Auth
| Approach | Scope | Best for |
|---|---|---|
.auth(ApiAuth.bearerToken(...)) | Single request | One-off calls with different tokens |
ApiClient.setGlobalAuth(...) | All requests on thread | Runtime tokens (login response) |
@UseAuth("name") | Test method or class | Config/env-var based tokens in CI |