Q30 of 40 · REST Assured

How do you handle OAuth 2.0 client credentials flow in REST Assured?

REST AssuredSeniorrest-assuredoauth2authenticationtoken-managementsecurity

Short answer

Short answer: POST to the token endpoint with client_id, client_secret, and grant_type=client_credentials in @BeforeAll. Cache the token and its expiry time; refresh before it expires using a lazy getter. Inject the token into the shared RequestSpecBuilder header so all tests get it automatically.

Detail

REST Assured has no built-in OAuth 2.0 token management — you implement the token fetch and caching yourself:

private static String accessToken;
private static Instant tokenExpiry;

@BeforeAll
static void fetchToken() {
    refreshTokenIfExpired();
}

private static void refreshTokenIfExpired() {
    if (accessToken != null && Instant.now().isBefore(tokenExpiry.minusSeconds(60))) return;

    JsonPath jp = given()
        .contentType("application/x-www-form-urlencoded")
        .formParam("grant_type",    "client_credentials")
        .formParam("client_id",     System.getenv("CLIENT_ID"))
        .formParam("client_secret", System.getenv("CLIENT_SECRET"))
        .formParam("scope",         "api:read api:write")
    .when()
        .post(System.getenv("TOKEN_URL"))
    .then()
        .statusCode(200)
        .extract().jsonPath();

    accessToken = jp.getString("access_token");
    tokenExpiry = Instant.now().plusSeconds(jp.getLong("expires_in"));
}

Injection: rebuild the RequestSpecification after a token refresh, or use a filter that calls refreshTokenIfExpired() before each request and updates the Authorization header dynamically — the filter approach handles token expiry mid-suite without rebuilding specs.

// EXAMPLE

OAuth2Filter.java

public class OAuth2Filter implements Filter {
    private volatile String token;
    private volatile Instant expiry = Instant.EPOCH;

    @Override
    public Response filter(FilterableRequestSpecification req,
                           FilterableResponseSpecification res,
                           FilterContext ctx) {
        if (token == null || Instant.now().isAfter(expiry.minusSeconds(60))) {
            fetchNewToken();
        }
        req.removeHeader("Authorization");
        req.header("Authorization", "Bearer " + token);
        return ctx.next(req, res);
    }

    private synchronized void fetchNewToken() {
        if (token != null && Instant.now().isBefore(expiry.minusSeconds(60))) return;
        JsonPath jp = given()
            .contentType("application/x-www-form-urlencoded")
            .formParam("grant_type",    "client_credentials")
            .formParam("client_id",     System.getenv("CLIENT_ID"))
            .formParam("client_secret", System.getenv("CLIENT_SECRET"))
            .when().post(System.getenv("TOKEN_URL"))
            .then().statusCode(200).extract().jsonPath();
        token  = jp.getString("access_token");
        expiry = Instant.now().plusSeconds(jp.getLong("expires_in"));
    }
}

// Register in RequestSpecBuilder:
new RequestSpecBuilder().addFilter(new OAuth2Filter()).build();

// WHAT INTERVIEWERS LOOK FOR

Building the token fetch as a REST Assured request, caching with expiry (minus a buffer for clock skew), and the filter-based approach for transparent token refresh mid-suite. Thread-safe synchronized refresh is a senior signal.

// COMMON PITFALL

Re-fetching the token on every test method — this adds latency and hammers the auth server. One fetch per suite with a refresh window is the correct pattern.