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());
    }
}