OAuth2 is surprisingly good at letting you load test protected endpoints without actually needing to manage user sessions.

Here’s a k6 script that demonstrates how to load test an API protected by OAuth2, focusing on obtaining and using an access token to authenticate requests.

import http from 'k6/http';
import { check, sleep } from 'k6';

// --- Configuration ---
const OAUTH_TOKEN_URL = 'https://your-auth-server.com/oauth/token';
const API_URL = 'https://your-protected-api.com/resource';
const CLIENT_ID = 'your-client-id';
const CLIENT_SECRET = 'your-client-secret';
const GRANT_TYPE = 'client_credentials'; // Or 'password', 'authorization_code', etc.

// --- Options ---
export const options = {
  vus: 10, // Number of virtual users
  duration: '30s', // Duration of the test
};

// --- Global variables ---
let accessToken = null;

// --- Function to get OAuth2 token ---
function getOAuthToken() {
  const payload = {
    grant_type: GRANT_TYPE,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    // Add 'scope' if required by your OAuth2 provider
    // scope: 'read write',
  };

  const response = http.post(OAUTH_TOKEN_URL, payload);

  if (check(response, {
    'Token request succeeded (200 OK)': (r) => r.status === 200,
    'Response contains access_token': (r) => r.json('access_token') !== undefined,
  })) {
    accessToken = response.json('access_token');
    console.log(`Successfully obtained access token: ${accessToken.substring(0, 10)}...`);
  } else {
    console.error(`Failed to obtain access token. Status: ${response.status}, Body: ${response.body}`);
    // In a real test, you might want to fail the VUG here or retry
  }
}

// --- Main test execution ---
export default function () {
  // Get a new token if we don't have one, or if it's expired (logic omitted for simplicity)
  if (!accessToken) {
    getOAuthToken();
  }

  // --- Make a request to the protected API ---
  const params = {
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
  };

  const apiResponse = http.get(API_URL, params);

  check(apiResponse, {
    'API request succeeded (200 OK)': (r) => r.status === 200,
    // Add more checks for your specific API response
    // 'Response body contains expected data': (r) => r.json('some_field') === 'expected_value',
  });

  sleep(1); // Pause between requests for this VUG
}

// --- Cleanup (optional, useful for token refresh logic) ---
export function teardown(data) {
  // Add logic here to invalidate tokens if your auth server supports it
  console.log('Test finished.');
}

This script first defines your OAuth2 provider’s token endpoint, your API’s protected endpoint, and your client credentials. The getOAuthToken function makes a POST request to the token endpoint with the necessary grant type and credentials. If successful, it stores the access_token in a global variable.

The default function, which runs for each virtual user, first checks if an accessToken is available. If not, it calls getOAuthToken. Then, it makes a GET request to your protected API, including the Authorization header with the obtained Bearer token. Finally, it performs checks on the API response and pauses.

The real magic happens in how the Authorization header is constructed: Bearer ${accessToken}. This is the standard way to present an OAuth2 Bearer token to a resource server. The resource server then validates this token with the authorization server (or by inspecting the token itself if it’s a JWT) to grant access.

The options object controls the load profile, and the teardown function can be used for any cleanup tasks, though for simple token acquisition, it’s often not strictly necessary. You’d want to implement more robust token refresh logic in a production scenario, checking for token expiry before making API calls.

The next concept to explore is how to handle different OAuth2 grant types, like password or authorization_code, which involve user interaction or redirects and require more complex k6 scripting.

Want structured learning?

Take the full K6 course →