Gatling can make your API testing incredibly robust, but getting those OAuth2 tokens into your requests can feel like wrestling an octopus.

Let’s see Gatling in action, grabbing a token and then using it.

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class OAuth2Simulation extends Simulation {

  val httpProtocol = http
    .baseUrl("https://your-api.com") // Replace with your API base URL
    .acceptHeader("application/json")

  // --- OAuth2 Token Acquisition ---
  val tokenFeeder = csv("tokens.csv").circular // Load tokens from a CSV file

  val oauth2Scenario = scenario("OAuth2 Token Acquisition")
    .feed(tokenFeeder) // Feed token details from CSV
    .exec(http("Request OAuth2 Token")
      .post("/oauth2/token") // Your OAuth2 token endpoint
      .header("Content-Type", "application/x-www-form-urlencoded")
      .formParam("grant_type", "client_credentials") // Or "password", "refresh_token", etc.
      .formParam("client_id", "#{clientId}") // From CSV
      .formParam("client_secret", "#{clientSecret}") // From CSV
      .check(status.is(200))
      .check(jsonPath("$.access_token").saveAs("accessToken")) // Save the token
    )
    .exec { session =>
      println(s"Obtained Access Token: ${session("accessToken").as[String].take(10)}...") // Log for verification
      session
    }

  // --- API Request Using Token ---
  val apiCallScenario = scenario("API Call with Token")
    .exec(http("Call Protected API")
      .get("/protected/resource") // Your protected API endpoint
      .header("Authorization", "Bearer #{accessToken}") // Use the saved token
      .check(status.is(200))
    )

  // --- Simulation Setup ---
  setUp(
    oauth2Scenario.inject(atOnceUsers(1)) // Get one token initially
      .andThen(apiCallScenario.inject(rampUsers(100) during (30.seconds))) // Use the token for many API calls
  ).protocols(httpProtocol)
}

This simulation does two main things: first, it obtains an OAuth2 access token, and second, it uses that token to call a protected API endpoint. The tokenFeeder reads clientId and clientSecret from a tokens.csv file. The oauth2Scenario then makes a POST request to your token endpoint, typically with grant_type, client_id, and client_secret. Crucially, it uses .check(jsonPath("$.access_token").saveAs("accessToken")) to extract the token from the JSON response and store it in Gatling’s session. The apiCallScenario then uses this accessToken by injecting it into the Authorization header as a Bearer token.

The problem Gatling’s OAuth2 integration solves is the dynamic nature of API authentication. Hardcoding tokens is a non-starter for any serious testing. You need a way to programmatically acquire and refresh tokens as part of your load tests. Gatling’s exec blocks and .check() DSL are perfect for this, allowing you to chain requests, extract data from responses, and use that data in subsequent requests. The session is the key here; it’s a mutable map that carries information (like your accessToken) between steps in a scenario and even across different scenarios within the same simulation.

The magic happens in how Gatling handles session variables. When you .saveAs("accessToken"), you’re telling Gatling to take the extracted value (the access token from the JSON) and put it into the current user’s session under the key accessToken. Then, whenever you reference #{accessToken} in a subsequent request’s header, body, or URL, Gatling automatically substitutes it with the value stored in that user’s session. This makes it seamless to pass dynamic credentials or data throughout your test.

Most people know you can save data from a response, but they often miss that you can also save data directly from the request itself. For instance, if your token endpoint required a custom header that was dynamically generated, you could do something like:

.exec { session =>
  val dynamicHeaderValue = java.util.UUID.randomUUID().toString // Example dynamic value
  session.set("dynamicHeader", dynamicHeaderValue) // Store it in the session
}
.exec(http("Request with Dynamic Header")
  .post("/some/endpoint")
  .header("X-Custom-Dynamic", "#{dynamicHeader}") // Use the session variable
  .check(...)
)

This allows you to inject even more dynamically generated, stateful information into your requests beyond just API response data.

The next challenge is handling token expiration and refresh tokens gracefully within a simulation.

Want structured learning?

Take the full Gatling course →