skip to content

In REST Assured, what does auth().preemptive().oauth2(token) change on a request that auth().oauth2(token) does not?

level: seniorimportance: should knowfreq 34%

answer

  1. same bytes, different moment
  2. preemptive calls none() first
  3. scheme versus ordinary header
  4. the wipe clears auth filters too
  5. instance preemptive offers oauth2

basics

~20 s

On the wire, nothing: both send Authorization: Bearer plus the token. The preemptive form calls auth().none() first, clearing the scheme, its auth filters and any Authorization header, then sets the header immediately; the plain form defers it until send time.

solid answer

~40 s

The bytes on the wire are identical - the difference is timing and side effects. `given().auth().preemptive()` returns an instance of `PreemptiveAuthSpec`, whose `oauth2` implementation first calls `auth().none()`, and that installs `ExplicitNoAuthScheme`, strips every `AuthFilter` from the specification and removes any existing `Authorization` header; it then sets `Authorization` as an ordinary header using `"Bearer " + token`. `auth().oauth2(token)` instead sets the specification's `authenticationScheme` to a `PreemptiveOAuth2HeaderScheme` and writes nothing at all until the request is sent. So the preemptive form is the stronger reset of an inherited `RestAssured.authentication`, and it is the only one whose header is visible on the specification beforehand - a filter inspecting a harvest-log request sees the header in one form and the scheme in the other. Mind the surface too: the static `RestAssured.preemptive()` offers `basic(...)` only.

code

java · 20 lines
java
import io.restassured.http.ContentType;

import static io.restassured.RestAssured.given;

public class HarvestLogPreemptiveToken {

    // preemptive().oauth2 calls auth().none() first, then sets Authorization as a normal header,
    // so the header exists on the specification before the request is ever sent.
    void recordPick(String accessToken) {
        given()
                .auth().preemptive().oauth2(accessToken)
                .header("X-Harvest-Client", "nightly-suite")
                .contentType(ContentType.JSON)
                .body("{\"blockCode\":\"NORTH-SLOPE-7\",\"varietal\":\"pinot noir\",\"tonnage\":4.2}")
        .when()
                .post("https://harvest-log.example.com/v1/harvests")
        .then()
                .statusCode(201);
    }
}

go deeper

for a junior

It is enough to know both spellings exist and produce the same Authorization: Bearer header. Pick one and use it consistently rather than mixing them within a suite.

for a middle

Explain the mechanics: preemptive().oauth2 sets an ordinary header after calling auth().none(), while auth().oauth2 records a PreemptiveOAuth2HeaderScheme that writes the header at send time. Name both classes.

for a senior

Use the difference diagnostically. Say why a globally configured credential vanishes for one test, why a filter sees the header in one form only, and how you would decide which spelling a shared base class should use.

for a principal

Own the convention across teams. Two interchangeable spellings with different side effects on inherited state are a standards problem, so settle one, document why, and make credential wiring reviewable rather than folkloric.

## Same header, different moment Both `given().auth().oauth2(accessToken)` and `given().auth().preemptive().oauth2(accessToken)` make the vineyard harvest-log service see one identical line: `Authorization: Bearer <token>`. The project wiki even says the two forms do the same thing, and on the wire that is true. The difference is *when* the header comes into existence and *what else the call touches on the way*, and that is what shows up when you are debugging a suite rather than reading a tutorial. ## What `preemptive().oauth2(token)` does, step by step `given().auth().preemptive()` returns `io.restassured.specification.PreemptiveAuthSpec`, an interface declaring exactly two methods: `basic(String, String)` and `oauth2(String)`. The implementation of `oauth2` runs two statements: 1. It calls `auth().none()` on the request specification first. That is not a formality — `none()` installs an `ExplicitNoAuthScheme`, removes every `AuthFilter` from the specification's filter list, and removes any existing `Authorization` header. 2. It then sets `Authorization` as an ordinary header, using the value produced by `new PreemptiveOAuth2HeaderScheme(accessToken).generateAuthToken()` — that is, `"Bearer " + token`. So after the call the specification carries no OAuth-ish authentication scheme at all. It carries a header, indistinguishable from one you had written by hand, plus a deliberate wipe of whatever authentication was configured before. ## What `auth().oauth2(token)` does instead The non-preemptive call sets a single field: the specification's `authenticationScheme` becomes a `PreemptiveOAuth2HeaderScheme` holding the token. No header is created at that moment. When the request is finally sent, the scheme's `authenticate(...)` hook writes the header onto the outgoing request. Nothing is removed from the filter list and no previously set `Authorization` header is deleted — the scheme is simply replaced. REST Assured's own integration test pins both halves of this. One case asserts that after `auth().preemptive().oauth2(accessToken)` the request specification's headers already contain `Bearer accessToken`; the other asserts that after `auth().oauth2(accessToken)` the specification's authentication scheme is a `PreemptiveOAuth2HeaderScheme` carrying that token. ## Where the credential lives | | `auth().oauth2(token)` | `auth().preemptive().oauth2(token)` | |---|---|---| | header on the spec before sending | no | yes | | authentication scheme on the spec | `PreemptiveOAuth2HeaderScheme` | `ExplicitNoAuthScheme` | | existing `Authorization` header | left alone | removed first | | auth filters already added | left alone | removed first | | bytes on the wire | `Authorization: Bearer <token>` | `Authorization: Bearer <token>` | ## Consequences in a real suite - Anything that reads the specification before the request goes out — a filter, or a query of the built specification — sees the header only in the preemptive form. In the other form it sees the scheme and no header, which is a confusing five minutes if you did not expect it. - The preemptive form is the stronger reset. If a base class set `RestAssured.authentication` to a suite-wide credential and one harvest-log test needs a different token, `preemptive().oauth2(...)` clears the inherited scheme and its filters as well as replacing the value. - Conversely, the preemptive form is the wrong tool if you deliberately want the inherited authentication to remain in place, because the wipe is unconditional. - Because the preemptive form leaves an ordinary header behind, a later `auth().none()` in the same chain removes it, while the `Authorization` header written at send time by the scheme never exists on the specification to be inspected or removed. - Neither form needs the optional `scribejava-apis` artifact; both are plain string concatenation. ## Which `preemptive()` are you on? This is the mis-attribution that catches people. The instance surface, `given().auth().preemptive()`, is a `PreemptiveAuthSpec` and offers `basic` **and** `oauth2`. The static `RestAssured.preemptive()` returns `io.restassured.authentication.PreemptiveAuthProvider`, which offers `basic(...)` only — there is no static `preemptive().oauth2(...)`. And `preemptive().digest(...)` exists on neither. A sentence such as "the preemptive spec offers basic, digest and oauth2" is made entirely of real tokens and is still false, so name the surface every time you say it. Two more facts belong with that warning: - `preemptive()` on the instance surface is reached only through `auth()`, so the full spelling is always `given().auth().preemptive().oauth2(token)`. - Both `preemptive().oauth2(token)` and `auth().oauth2(token)` end at the same `generateAuthToken()` concatenation, so neither is "more secure" than the other; the credential is the same string in the same header either way. ## Which one to reach for For a harvest-log suite, either is defensible and consistency matters more than the choice: - Prefer `auth().preemptive().oauth2(token)` when you want the credential visible on the specification and any inherited authentication cleared for that request. - Prefer `auth().oauth2(token)` when you want the token to travel as an authentication scheme that a later merge or a global default can reason about as authentication rather than as a header. - Prefer `auth().none()` over simply omitting the call when a test means to observe the harvest-log service's unauthenticated behaviour, because a global scheme would otherwise apply. Pick one of the two spellings for the ordinary case, write it down, and stop mixing them across a suite — the wire result is the same, but the state you leave on the specification is not.

  • Why does the preemptive oauth2 implementation call auth().none() before setting the header?
    To guarantee the header it is about to write is the only credential on the request. `none()` installs `ExplicitNoAuthScheme`, removes every `AuthFilter` the specification had collected and deletes any existing `Authorization` header, so an inherited `RestAssured.authentication` or an earlier auth call cannot compete with or overwrite the token you just supplied.
  • Does the static RestAssured.preemptive() also offer oauth2?
    No. The instance surface `given().auth().preemptive()` returns `io.restassured.specification.PreemptiveAuthSpec`, which declares `basic(String, String)` and `oauth2(String)`. The static `RestAssured.preemptive()` returns `io.restassured.authentication.PreemptiveAuthProvider`, which declares `basic(...)` only. And `preemptive().digest(...)` exists on neither surface.

One form writes the address on the envelope as you seal it; the other pins a note to the envelope telling the mail room to write it on the way out. The letter arrives looking the same either way.

saying these in an interview costs you the question

  • Says the two forms send different wire headers
  • Thinks preemptive() adds a signature to the token
  • Unaware that preemptive oauth2 wipes existing auth
  • Claims the static preemptive() offers oauth2 too
  • Expects auth().oauth2 to expose a header before sending