skip to content

In REST Assured, why does RestAssured.oauth2(token, OAuthSignature.HEADER) install a different scheme than auth().oauth2(token, HEADER)?

level: middleimportance: should knowfreq 31%

answer

  1. two surfaces, one method name
  2. only the instance call branches
  3. the static always builds OAuth2Scheme
  4. one-argument statics behave as expected

basics

~20 s

The instance method branches on the signature and the static factory does not. auth().oauth2(token, HEADER) installs PreemptiveOAuth2HeaderScheme and writes a plain Bearer header, while the two-argument static RestAssured.oauth2 unconditionally builds OAuth2Scheme, routing the request through the optional scribejava signing path.

solid answer

~50 s

Both surfaces publish an `oauth2(String, OAuthSignature)` method with identical parameters, and only one of them looks at the argument. `AuthenticationSpecificationImpl.oauth2` branches: `HEADER` installs a `PreemptiveOAuth2HeaderScheme` that just writes `"Bearer " + token`, and anything else installs an `OAuth2Scheme`. The static `RestAssured.oauth2(token, signature)` has no branch at all - it always constructs an `OAuth2Scheme` and merely stores the signature on it. So assigning `RestAssured.authentication = RestAssured.oauth2(token, OAuthSignature.HEADER)` puts your whole harvest-log suite on the scribejava-backed signing interceptor, whose artifact is declared optional and is probably absent, and the breakage surfaces at request time rather than at the assignment. The one-argument static `RestAssured.oauth2(token)` does return `PreemptiveOAuth2HeaderScheme`, so preferring the one-argument spellings avoids the trap. Nothing at the call site reveals which surface you are on, which is why a static import makes the two look identical in a diff.

code

java · 21 lines
java
import io.restassured.RestAssured;
import io.restassured.authentication.AuthenticationScheme;
import io.restassured.authentication.OAuthSignature;

public class HarvestLogGlobalToken {

    static void wireSuiteWideToken(String accessToken) {
        RestAssured.baseURI = "https://harvest-log.example.com";

        // One argument -> PreemptiveOAuth2HeaderScheme: a plain Authorization: Bearer header.
        AuthenticationScheme plainHeader = RestAssured.oauth2(accessToken);

        // Two arguments -> OAuth2Scheme, even though HEADER was asked for.
        AuthenticationScheme signingPath = RestAssured.oauth2(accessToken, OAuthSignature.HEADER);

        RestAssured.authentication = plainHeader;

        System.out.println(plainHeader.getClass().getSimpleName());   // PreemptiveOAuth2HeaderScheme
        System.out.println(signingPath.getClass().getSimpleName());   // OAuth2Scheme
    }
}

go deeper

for a junior

You are unlikely to meet this yet. Take away the habit that matters: prefer the one-argument oauth2 call on both the static and the per-request surface, and you never hit the divergence.

for a middle

Be able to say which surface branches. The instance oauth2 checks the signature; the static two-argument factory does not and always returns OAuth2Scheme. Name both scheme classes when you explain it.

for a senior

Treat it as a diagnosis story: a suite-wide auth assignment that looks identical to the working per-request call, failing at request time because an optional artifact is missing. Say how you would pin the behaviour with a type assertion.

for a principal

Push for one spelling across the codebase and a review rule that rejects the two-argument static. The wider point is that same-named calls on two surfaces need a written convention, not case-by-case judgement.

## The same name on two different surfaces REST Assured exposes authentication twice. There is a per-request surface, `given().auth()`, backed by `io.restassured.specification.AuthenticationSpecification`, and there is a set of static factory methods on `io.restassured.RestAssured` that return an `AuthenticationScheme` object you can assign to the `RestAssured.authentication` static or hand to a `RequestSpecBuilder.setAuth(...)`. The two surfaces carry methods with the same names and the same parameter lists, which is what makes the divergence so easy to miss: nothing in the call site tells you which one you are on except whether you wrote `given().auth().` in front of it. ## What the instance method does `AuthenticationSpecificationImpl.oauth2(accessToken, signature)` contains a branch: - `signature == OAuthSignature.HEADER` sets the request's `authenticationScheme` to a `PreemptiveOAuth2HeaderScheme`, whose whole job is to write `"Bearer " + accessToken` into the `Authorization` header. - anything else — meaning `QUERY_STRING`, since the enum has exactly two constants — sets an `OAuth2Scheme` carrying both the token and the signature. The one-argument `oauth2(accessToken)` simply calls that method with `HEADER`, so `given().auth().oauth2(token)` and `given().auth().oauth2(token, OAuthSignature.HEADER)` are identical, and both give you the plain header writer. ## What the static factory does `RestAssured.oauth2(String accessToken, OAuthSignature signature)` has **no branch**. It constructs an `OAuth2Scheme`, sets the access token, sets the signature and returns it. The signature you pass is stored on the scheme rather than used to pick one. So: ```java // per request: PreemptiveOAuth2HeaderScheme given().auth().oauth2(token, OAuthSignature.HEADER); // suite-wide: OAuth2Scheme, despite the identical arguments RestAssured.authentication = RestAssured.oauth2(token, OAuthSignature.HEADER); ``` The one-argument static is the exception that proves the rule: `RestAssured.oauth2(String accessToken)` builds a `PreemptiveOAuth2HeaderScheme` directly, matching the instance call. Only the two-argument static diverges. | call | scheme built | |---|---| | `given().auth().oauth2(token)` | `PreemptiveOAuth2HeaderScheme` | | `given().auth().oauth2(token, HEADER)` | `PreemptiveOAuth2HeaderScheme` | | `given().auth().oauth2(token, QUERY_STRING)` | `OAuth2Scheme` | | `RestAssured.oauth2(token)` | `PreemptiveOAuth2HeaderScheme` | | `RestAssured.oauth2(token, HEADER)` | `OAuth2Scheme` | | `RestAssured.oauth2(token, QUERY_STRING)` | `OAuth2Scheme` | ## Why the divergence bites Nothing about the divergence is visible at the call site, and the two schemes do not fail in the same way, so the symptoms are indirect: - `OAuth2Scheme` delegates to REST Assured's internal auth config, which registers a signing interceptor built on **scribejava**. That artifact, `com.github.scribejava:scribejava-apis`, is declared optional in REST Assured's POM, so a suite that only ever used plain bearer tokens will not have it and the failure appears the first time a request executes rather than when the static is assigned. - The request no longer takes the straight-concatenation path, so what reaches the vineyard harvest-log service is produced by the signer rather than by `"Bearer " + accessToken`. - Because the assignment is a static, one line in a base class or a suite-level setup changes the behaviour of every test, and the stack trace points at the request, not at the assignment. - Copying a per-request call into a setup method looks like a pure refactor and is not. ## Telling the two surfaces apart at a glance There is no compiler help here, so the reading habit has to do the work. Three cues separate them: - A call that starts with `given()` or with a `RequestSpecification` variable is on the instance surface and returns a `RequestSpecification` you keep chaining. - A call written as a bare `oauth2(...)` after a static import, or as `RestAssured.oauth2(...)`, is a factory: it returns an `AuthenticationScheme` object and chains nothing. - If the result is being assigned — to `RestAssured.authentication`, to a local variable, or into `RequestSpecBuilder.setAuth(...)` — you are on the static surface by definition. The same reading applies to the neighbouring pair: `given().auth().preemptive()` yields a `PreemptiveAuthSpec` offering `basic` and `oauth2`, while the static `RestAssured.preemptive()` yields a `PreemptiveAuthProvider` offering `basic` only. Static imports make both pairs look identical in a diff, which is precisely how the divergence survives code review. ## How to stay out of trouble 1. Prefer the one-argument spelling everywhere: `given().auth().oauth2(token)` per request, `RestAssured.authentication = RestAssured.oauth2(token)` for the suite. Neither can diverge, because neither takes a signature. 2. Pass a signature only when you actually want `QUERY_STRING`, and then pass it on the instance call, where the branch exists and the intent is unambiguous. 3. If you must hold a scheme object — for a `RequestSpecBuilder.setAuth(...)`, say — assert its type once in a small test. `RestAssured.oauth2(token)` returning `PreemptiveOAuth2HeaderScheme` is a one-line check that pins the behaviour you rely on. 4. Say which surface you mean whenever you write this down for teammates. "The static two-argument `oauth2`" and "`auth().oauth2` with a signature" are different calls, and a sentence that omits the distinction is made of true words and is still wrong. ## The one-line answer The instance method branches on the signature and the static factory does not: the two-argument `RestAssured.oauth2(token, OAuthSignature.HEADER)` always builds an `OAuth2Scheme`, so it routes the request through the optional scribejava signing path, while `given().auth().oauth2(token, HEADER)` installs the plain `PreemptiveOAuth2HeaderScheme`. Use the one-argument forms and the trap disappears.

  • Does the one-argument static RestAssured.oauth2(token) show the same divergence?
    No, and that is the useful asymmetry. `RestAssured.oauth2(String accessToken)` builds a `PreemptiveOAuth2HeaderScheme` directly, exactly matching `given().auth().oauth2(token)`. Only the two-argument static, which takes an `OAuthSignature`, unconditionally constructs `OAuth2Scheme`. Sticking to the one-argument spelling on both surfaces removes the hazard.
  • What symptom would this divergence produce in a harvest-log suite that previously passed?
    The request stops taking the plain-concatenation path and is handed to the signing interceptor instead. Because `com.github.scribejava:scribejava-apis` is an optional dependency that a bearer-only suite is unlikely to have, the breakage appears when a request executes rather than where the static was assigned, which makes the stack trace point at the wrong line.

saying these in an interview costs you the question

  • Assumes both surfaces share one implementation
  • Thinks passing HEADER always means a plain header
  • Cannot say which oauth2 call they mean
  • Blames the server when a global token assignment breaks
  • Believes scribejava is bundled with rest-assured