In REST Assured, how do auth().oauth2(token) and auth().oauth2(token, OAuthSignature.QUERY_STRING) differ?
answer
- one argument implies HEADER
- the enum has only two constants
- header form concatenates, nothing more
- query form rewrites the URI
- scribejava-apis is optional
basics
~20 sThe default HEADER form installs PreemptiveOAuth2HeaderScheme and writes a plain Authorization: Bearer header. The QUERY_STRING form installs OAuth2Scheme, which signs the request through scribejava and moves the credential into the URI, so it needs the optional scribejava-apis artifact.
solid answer
~40 s`auth().oauth2(accessToken)` is not a separate implementation - it delegates to `oauth2(accessToken, OAuthSignature.HEADER)`, and that two-argument method branches on the signature. With `HEADER` it sets the request's `authenticationScheme` to a `PreemptiveOAuth2HeaderScheme`, whose `generateAuthToken()` is just `"Bearer " + accessToken`: no signing, no challenge round trip, no extra dependency. With `QUERY_STRING` it installs an `OAuth2Scheme` instead, which hands the token to REST Assured's internal auth config; that registers a scribejava-backed Apache HttpClient interceptor which signs the request and replaces the URI, so the credential rides in the query string of the harvest-log URL rather than in a header. `OAuthSignature` has exactly two constants, and only the second path needs `com.github.scribejava:scribejava-apis`, which REST Assured declares as an optional dependency and nothing pulls in transitively. For a harvest-log suite, the default is almost always right.
code
java · 28 linesimport io.restassured.authentication.OAuthSignature;
import static io.restassured.RestAssured.given;
public class HarvestLogSignatureModes {
private static final String HARVESTS = "https://harvest-log.example.com/v1/blocks/NORTH-SLOPE-7/harvests";
// Default HEADER signature -> PreemptiveOAuth2HeaderScheme, plain Authorization: Bearer <token>.
void headerForm(String accessToken) {
given()
.auth().oauth2(accessToken)
.when()
.get(HARVESTS)
.then()
.statusCode(200);
}
// QUERY_STRING signature -> OAuth2Scheme, signed through scribejava-apis on the classpath.
void queryStringForm(String accessToken) {
given()
.auth().oauth2(accessToken, OAuthSignature.QUERY_STRING)
.when()
.get(HARVESTS)
.then()
.statusCode(200);
}
}go deeper
Know that oauth2 takes an optional second argument and that leaving it off gives you the plain header. You are not expected to have used the query-string form in anger.
Explain the branch: HEADER yields PreemptiveOAuth2HeaderScheme, QUERY_STRING yields OAuth2Scheme and a scribejava-backed interceptor that rewrites the URI. Name the optional artifact and say the enum has two constants.
Argue for the default and say why: a credential in a URL turns up in access logs and proxies, and adding an optional artifact to a test classpath is a maintenance cost you take on only for a contract you must actually verify.
Decide when a legacy query-parameter credential is worth testing at all versus pushing the service owners to header carriage, and make sure the exception, if you grant one, is documented rather than copied test to test.
## Two overloads and one fork in the code `io.restassured.specification.AuthenticationSpecification` declares two `oauth2` methods: `oauth2(String accessToken)` and `oauth2(String accessToken, OAuthSignature signature)`. The single-argument form is not a separate implementation — it calls the two-argument form with `OAuthSignature.HEADER`. Everything therefore comes down to one branch inside the two-argument method, and that branch decides which scheme object lands on the request specification. - If the signature is `HEADER`, the specification's `authenticationScheme` becomes a `PreemptiveOAuth2HeaderScheme` carrying the token. - For anything else — and `OAuthSignature` has exactly two constants, so that means `QUERY_STRING` — the scheme becomes an `OAuth2Scheme` carrying the token *and* the signature. Two schemes, two completely different mechanisms, reached through the same method name. ## The HEADER path: string concatenation `PreemptiveOAuth2HeaderScheme` is about as thin as a class can be. Its `generateAuthToken()` returns `"Bearer " + accessToken`, and its `authenticate(...)` hook assigns that value to the `Authorization` header as the request goes out. There is no cryptography, no canonical request string, no nonce and no timestamp. Against the vineyard harvest-log API, `given().auth().oauth2(token).get("/v1/harvests")` produces a request that is byte-for-byte what you would have written by hand with `header("Authorization", "Bearer " + token)`. ## The QUERY_STRING path: real signing `OAuth2Scheme.authenticate(...)` hands the token and the signature to REST Assured's internal `AuthConfig`, which registers an Apache HttpClient request interceptor. That interceptor is built on **scribejava**: it reconstructs the outgoing request as a scribejava `OAuthRequest`, signs it, and then — because the signature is not `HEADER` — replaces the request's URI with the signed complete URL. The credential therefore rides in the query string of the harvest-log URL rather than in a header, and the URI you see in a request log is not the URI you wrote. Three consequences follow directly: 1. `com.github.scribejava:scribejava-apis` must be on the classpath. It is declared `optional` in REST Assured's own POM, so nothing pulls it in transitively and its absence surfaces only when this code path executes. 2. Query-string carriage is visible everywhere a URL is: access logs, proxy logs, browser history and any `Referer` a downstream page sends. That is a property of the placement, not of REST Assured, but it is the reason the header form is the default. 3. Only this path is affected by anything OAuth-shaped in the configuration surface; the header form ignores it entirely. ## Side by side | aspect | `auth().oauth2(token)` | `auth().oauth2(token, QUERY_STRING)` | |---|---|---| | scheme installed | `PreemptiveOAuth2HeaderScheme` | `OAuth2Scheme` | | where the credential goes | `Authorization` header | the request URI | | signing | none, plain concatenation | scribejava signing interceptor | | optional dependency | not needed | `scribejava-apis` required | | default | yes, `HEADER` is implied | no, you must ask for it | ## A note on `OAuthConfig` `RestAssuredConfig` carries an `OAuthConfig`, and candidates reach for it when an `oauth2` call misbehaves. It will not help. `OAuthConfig` exposes a single setting, `addEmptyAccessTokenToBaseString(boolean)`, defaulting to `false`, and the internal auth config reads it only on the OAuth **1** signing path used by `auth().oauth(...)`. Neither `oauth2` overload consults it. ## Choosing between them for a harvest-log suite - Reach for the default `auth().oauth2(token)` in essentially every case. It is the placement modern APIs accept, it needs no extra artifact, and the resulting request is trivially explainable. - Reach for `QUERY_STRING` only when the service under test genuinely reads the credential from the URL and you are testing that contract deliberately — for example a legacy harvest-log export endpoint that predates header-based credentials. - If a test must exercise the unauthenticated path, use `auth().none()` rather than omitting the call, because a globally configured `RestAssured.authentication` would otherwise still apply. ## Where people go wrong - Believing `oauth2` always signs. Only the `QUERY_STRING` form does; the default is a header write. - Adding `scribejava-apis` to fix a bearer-token problem. The header path never touches it. - Assuming there is a third `OAuthSignature` value such as `BODY` or `NONE`. There are two. - Reading the wiki's Scribe requirement as applying to all of OAuth 2. It applies to OAuth 1 and to the query-parameter form of OAuth 2 only. - Expecting the QUERY_STRING form to leave the URI you typed intact. The interceptor rewrites it. The short version: one method name, one enum argument, and the enum argument decides whether REST Assured writes a header or signs your URL.
- What happens if you use OAuthSignature.QUERY_STRING without scribejava-apis on the classpath?REST Assured declares `com.github.scribejava:scribejava-apis` as an optional dependency, so it is not pulled in transitively. The signing interceptor that `OAuth2Scheme` relies on cannot be loaded, and the failure surfaces as a classpath error when that request executes rather than at compile time. The header form never touches the artifact.
- Does RestAssuredConfig's OAuthConfig change anything for an oauth2 call?No. `OAuthConfig` carries one setting, `addEmptyAccessTokenToBaseString(boolean)`, default `false`, and REST Assured's internal auth config reads it only on the OAuth 1 signing path behind `auth().oauth(...)`. Both `oauth2` overloads ignore it, so tuning `OAuthConfig` to fix a bearer-token problem changes nothing at all.
saying these in an interview costs you the question
- Says every oauth2 call signs the request
- Thinks OAuthSignature has a BODY or NONE constant
- Adds scribejava-apis to fix a plain bearer header
- Expects QUERY_STRING to leave the request URI unchanged
- Claims OAuthConfig tunes the oauth2 header path