Authentication
Basic Auth, OAuth2, API Keys, Bearer tokens
Authentication Overview
API authentication verifies the identity of the client making requests. Different APIs use different authentication mechanisms based on security requirements.
Basic Authentication
Basic Auth sends credentials as Base64-encoded username:password in the Authorization header.
Using Rest Assured’s Built-in Support
@Test
public void basicAuthTest() {
given()
.auth().basic("username", "password")
.when()
.get("https://api.example.com/protected")
.then()
.statusCode(200);
}
Preemptive Basic Auth
Sends credentials in the first request without waiting for 401 challenge:
@Test
public void preemptiveBasicAuth() {
given()
.auth().preemptive().basic("username", "password")
.when()
.get("https://api.example.com/protected")
.then()
.statusCode(200);
}
Manual Basic Auth Header
@Test
public void manualBasicAuth() {
String credentials = "username:password";
String encodedAuth = Base64.getEncoder()
.encodeToString(credentials.getBytes());
given()
.header("Authorization", "Basic " + encodedAuth)
.when()
.get("https://api.example.com/protected")
.then()
.statusCode(200);
}
Practical Example: Postman Echo
@Test
public void testPostmanEchoBasicAuth() {
given()
.auth().preemptive().basic("postman", "password")
.when()
.get("https://postman-echo.com/basic-auth")
.then()
.statusCode(200)
.body("authenticated", equalTo(true));
}
API Key Authentication
API keys are simple tokens that identify the calling application. They can be sent as headers or query parameters.
API Key in Header
@Test
public void apiKeyInHeader() {
String apiKey = "your-api-key-here";
given()
.header("X-API-Key", apiKey)
// Or common variations:
// .header("Api-Key", apiKey)
// .header("Authorization", "ApiKey " + apiKey)
.when()
.get("https://api.example.com/data")
.then()
.statusCode(200);
}
API Key in Query Parameter
@Test
public void apiKeyInQueryParam() {
String apiKey = "your-api-key-here";
given()
.queryParam("api_key", apiKey)
// Or: .queryParam("apikey", apiKey)
.when()
.get("https://api.example.com/data")
.then()
.statusCode(200);
}
Real-World Example: Weather API
public class WeatherApiTest {
private static final String API_KEY = System.getenv("WEATHER_API_KEY");
private static final String BASE_URI = "https://api.openweathermap.org";
@Test
public void getCurrentWeather() {
given()
.baseUri(BASE_URI)
.queryParam("q", "London")
.queryParam("appid", API_KEY)
.queryParam("units", "metric")
.when()
.get("/data/2.5/weather")
.then()
.statusCode(200)
.body("name", equalTo("London"))
.body("main.temp", notNullValue());
}
}
Bearer Token / JWT
Bearer tokens are sent in the Authorization header with the “Bearer” prefix. JWT (JSON Web Tokens) are a common format.
Using Bearer Token
@Test
public void bearerTokenAuth() {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
given()
.auth().oauth2(token) // Shorthand for Bearer token
.when()
.get("https://api.example.com/user/profile")
.then()
.statusCode(200);
}
// Equivalent manual approach
@Test
public void manualBearerToken() {
String token = "your-jwt-token";
given()
.header("Authorization", "Bearer " + token)
.when()
.get("https://api.example.com/user/profile")
.then()
.statusCode(200);
}
Login and Use Token
public class TokenAuthTest {
private String authToken;
@BeforeClass
public void login() {
// Step 1: Login and get token
authToken = given()
.contentType(ContentType.JSON)
.body(Map.of(
"email", "eve.holt@reqres.in",
"password", "cityslicka"
))
.when()
.post("https://reqres.in/api/login")
.then()
.statusCode(200)
.extract().path("token");
System.out.println("Token: " + authToken);
}
@Test
public void accessProtectedResource() {
given()
.auth().oauth2(authToken)
.when()
.get("https://api.example.com/protected")
.then()
.statusCode(200);
}
}
Reusable Request Spec with Token
public class AuthenticatedApiTest {
private static RequestSpecification authSpec;
@BeforeClass
public static void setupAuth() {
String token = getAuthToken();
authSpec = new RequestSpecBuilder()
.setBaseUri("https://api.example.com")
.addHeader("Authorization", "Bearer " + token)
.setContentType(ContentType.JSON)
.build();
}
private static String getAuthToken() {
return given()
.contentType(ContentType.JSON)
.body(Map.of("username", "testuser", "password", "secret"))
.when()
.post("https://api.example.com/auth/login")
.then()
.extract().path("accessToken");
}
@Test
public void testGetUserProfile() {
given()
.spec(authSpec)
.when()
.get("/user/profile")
.then()
.statusCode(200)
.body("username", equalTo("testuser"));
}
@Test
public void testUpdateProfile() {
given()
.spec(authSpec)
.body(Map.of("displayName", "Test User"))
.when()
.patch("/user/profile")
.then()
.statusCode(200);
}
}
OAuth 2.0
OAuth 2.0 is an authorization framework that enables secure delegated access. Common flows include Client Credentials and Authorization Code.
Client Credentials Flow
Used for machine-to-machine authentication (no user interaction):
public class OAuth2ClientCredentialsTest {
private static final String TOKEN_URL = "https://auth.example.com/oauth/token";
private static final String CLIENT_ID = System.getenv("CLIENT_ID");
private static final String CLIENT_SECRET = System.getenv("CLIENT_SECRET");
private String accessToken;
@BeforeClass
public void getAccessToken() {
accessToken = given()
.contentType(ContentType.URLENC)
.formParam("grant_type", "client_credentials")
.formParam("client_id", CLIENT_ID)
.formParam("client_secret", CLIENT_SECRET)
.formParam("scope", "read write")
.when()
.post(TOKEN_URL)
.then()
.statusCode(200)
.extract().path("access_token");
}
@Test
public void testApiWithOAuthToken() {
given()
.auth().oauth2(accessToken)
.when()
.get("https://api.example.com/resources")
.then()
.statusCode(200);
}
}
OAuth 2.0 with Basic Auth for Token
Some APIs require client credentials as Basic Auth when requesting tokens:
@BeforeClass
public void getTokenWithBasicAuth() {
String clientId = "your-client-id";
String clientSecret = "your-client-secret";
accessToken = given()
.auth().preemptive().basic(clientId, clientSecret)
.contentType(ContentType.URLENC)
.formParam("grant_type", "client_credentials")
.when()
.post("https://auth.example.com/token")
.then()
.statusCode(200)
.extract().path("access_token");
}
Token Refresh Pattern
public class TokenManager {
private String accessToken;
private String refreshToken;
private long expiresAt;
public String getValidToken() {
if (isTokenExpired()) {
refreshAccessToken();
}
return accessToken;
}
private boolean isTokenExpired() {
return System.currentTimeMillis() >= expiresAt - 60000; // 1 min buffer
}
private void refreshAccessToken() {
Response response = given()
.contentType(ContentType.URLENC)
.formParam("grant_type", "refresh_token")
.formParam("refresh_token", refreshToken)
.formParam("client_id", "your-client-id")
.when()
.post("https://auth.example.com/token")
.then()
.statusCode(200)
.extract().response();
accessToken = response.path("access_token");
refreshToken = response.path("refresh_token");
int expiresIn = response.path("expires_in");
expiresAt = System.currentTimeMillis() + (expiresIn * 1000L);
}
}
Digest Authentication
Digest auth is more secure than Basic auth as it doesn’t send the password in plain text.
@Test
public void digestAuthTest() {
given()
.auth().digest("username", "password")
.when()
.get("https://api.example.com/digest-protected")
.then()
.statusCode(200);
}
// Practical example with Postman Echo
@Test
public void postmanEchoDigestAuth() {
given()
.auth().digest("postman", "password")
.when()
.get("https://postman-echo.com/digest-auth")
.then()
.statusCode(200)
.body("authenticated", equalTo(true));
}
Custom Authentication
HMAC Signature Authentication
Some APIs require signed requests using HMAC:
public class HmacAuthTest {
private static final String API_KEY = "your-api-key";
private static final String SECRET_KEY = "your-secret-key";
@Test
public void hmacSignedRequest() {
String timestamp = String.valueOf(System.currentTimeMillis());
String method = "GET";
String path = "/api/account";
// Create signature
String signatureData = timestamp + method + path;
String signature = generateHmacSignature(signatureData, SECRET_KEY);
given()
.header("X-API-Key", API_KEY)
.header("X-Timestamp", timestamp)
.header("X-Signature", signature)
.when()
.get("https://api.example.com" + path)
.then()
.statusCode(200);
}
private String generateHmacSignature(String data, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(keySpec);
byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("Failed to generate HMAC", e);
}
}
}
Request Filter for Auth
Add authentication to all requests using filters:
public class AuthFilter implements Filter {
private final String apiKey;
public AuthFilter(String apiKey) {
this.apiKey = apiKey;
}
@Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec,
FilterContext ctx) {
// Add auth header to every request
requestSpec.header("X-API-Key", apiKey);
return ctx.next(requestSpec, responseSpec);
}
}
// Usage
@Test
public void testWithAuthFilter() {
given()
.filter(new AuthFilter("my-api-key"))
.when()
.get("https://api.example.com/data")
.then()
.statusCode(200);
}
Testing Auth Scenarios
Test Unauthorized Access
@Test
public void testUnauthorizedWithoutToken() {
given()
// No auth provided
.when()
.get("https://api.example.com/protected")
.then()
.statusCode(401)
.body("error", equalTo("Unauthorized"));
}
@Test
public void testUnauthorizedWithInvalidToken() {
given()
.auth().oauth2("invalid-token")
.when()
.get("https://api.example.com/protected")
.then()
.statusCode(401);
}
@Test
public void testExpiredToken() {
String expiredToken = "eyJ...expired...";
given()
.auth().oauth2(expiredToken)
.when()
.get("https://api.example.com/protected")
.then()
.statusCode(401)
.body("error", containsString("expired"));
}
Test Forbidden Access (Authorization)
@Test
public void testForbiddenWithInsufficientPermissions() {
String userToken = getUserToken(); // Regular user token
given()
.auth().oauth2(userToken)
.when()
.get("https://api.example.com/admin/users") // Admin-only endpoint
.then()
.statusCode(403)
.body("error", containsString("Forbidden"));
}
@Test
public void testAdminAccess() {
String adminToken = getAdminToken();
given()
.auth().oauth2(adminToken)
.when()
.get("https://api.example.com/admin/users")
.then()
.statusCode(200);
}
Complete Auth Test Suite
public class AuthenticationTests {
@Test(groups = "auth")
public void testSuccessfulLogin() {
given()
.contentType(ContentType.JSON)
.body(Map.of(
"email", "eve.holt@reqres.in",
"password", "cityslicka"
))
.when()
.post("https://reqres.in/api/login")
.then()
.statusCode(200)
.body("token", notNullValue())
.body("token", matchesPattern("[A-Za-z0-9]+"));
}
@Test(groups = "auth")
public void testLoginWithMissingPassword() {
given()
.contentType(ContentType.JSON)
.body(Map.of("email", "eve.holt@reqres.in"))
.when()
.post("https://reqres.in/api/login")
.then()
.statusCode(400)
.body("error", equalTo("Missing password"));
}
@Test(groups = "auth")
public void testLoginWithInvalidCredentials() {
given()
.contentType(ContentType.JSON)
.body(Map.of(
"email", "invalid@example.com",
"password", "wrongpassword"
))
.when()
.post("https://reqres.in/api/login")
.then()
.statusCode(400)
.body("error", notNullValue());
}
}