In the previous post, we built our first full user flow with k6. Now it’s time to move closer to real production scenarios—where users authenticate, carry tokens, and send meaningful headers with each request.
In this post, we’ll:
- Log in using email/password
- Extract a JWT token from the response
- Store it for the rest of the test
- Call protected endpoints using Authorization: Bearer
- Add custom headers such as X-Client-Version and X-Request-Source
- Run a multi-step authenticated flow
This is the foundation for every real-world SaaS, e-commerce, dashboard, and API microservice load test.
Table of contents
Open Table of contents
Flow Overview (ASCII Diagram)
+-------------------+
| User Login |
| POST /auth/login |
+---------+---------+
|
| extract JWT
v
+---------------------------+
| Request Protected Resource|
| GET /my/crocodiles |
+-------------+-------------+
|
| send token + custom headers
v
+------------------------------+
| Update Item (authenticated) |
| PUT /my/crocodiles/<id> |
+------------------------------+
Code Walkthrough
Login payload and headers
const loginPayload = JSON.stringify({
username: "testuser@example.com",
password: "supersecure",
});
const loginHeaders = {
"Content-Type": "application/json",
"X-Client-Version": "1.0.0",
"X-Request-Source": "k6-learn-series",
};
loginPayloadis the JSON body sent to the login endpoint.loginHeadersinclude Content-Type and two custom headers. Custom headers help identify traffic or provide metadata to the server (client version, source, etc.).
POST /auth/token/login/ — retrieve JWT
const loginRes = http.post(
"https://fakeloadtest.com/auth/token/login/",
loginPayload,
{ headers: loginHeaders }
);
loginResSends credentials to the login endpoint.- The server should return an
access-tokenin the response body (here we expect a JSON field named access).
Validate login and token presence with checks
check(loginRes, {
"login successful": (r) => r.status === 200,
"token received": (r) => r.json("access-token") !== undefined,
});
# Extract token and short-circuit if missing
const token = loginRes.json("access-token");
if (!token) return;
- Two checks:
HTTP 200for success, and that the JSONcontainsanaccess-tokenfield. - check results are visible in the test summary and help define whether the test is exercising healthy endpoints.
loginRes.json("access-token")reads the access field from the JSON response.- If there’s no token, it exits from this iteration early to prevents further unauthorized calls.
Build auth headers
const withAuth = {
...commonHeaders,
Authorization: `Bearer ${token}`
};
- Combine the Auth headers with common headers. This way we can just send
withAuthheaders without duplicating common headers.
Putting it all together
import http from "k6/http";
import { sleep, check } from "k6";
export const options = {
vus: 5,
duration: "30s",
};
export default function () {
// --- shared/common headers used for all requests (non-auth + auth)
const commonHeaders = {
"Content-Type": "application/json",
"X-Client-Version": "1.0.0",
"X-Request-Source": "k6-learn-series",
};
// 1. Login and retrieve JWT
const loginPayload = JSON.stringify({
username: "testuser@example.com",
password: "supersecure",
});
const loginRes = http.post(
"https://fakeloadtest.com/auth/token/login/",
loginPayload,
{ headers: commonHeaders } // use common headers even for login
);
check(loginRes, {
"login successful": (r) => r.status === 200,
"token received": (r) => r.json("access") !== undefined,
});
const token = loginRes.json("access");
if (!token) return;
// Build withAuth by copying commonHeaders and adding Authorization
const withAuth = {
...commonHeaders,
Authorization: `Bearer ${token}`
};
// 2. Get Product list (protected endpoint)
let products = http.get("https://fakeloadtest.com/api/products/");
check(products, {
"Get Products": (r) => r.status === 200,
});
const list = products.json();
if (!list.length) return;
// 3. Update a product
const id = list[0].id;
const updatePayload = JSON.stringify({
name: "UpdatedTest",
qty: 5,
});
let updated = http.put(
`https://fakeloadtest.com/api/products/${id}/`,
updatePayload,
{ headers: withAuth }
);
check(updated, {
"update success": (r) => r.status === 200,
});
sleep(1);
}
k6-course/auth-flow.js
Now Run:
k6 run auth-flow.js
The script would run with 1 user as a default and give you some nice statistics like:
- request count
- duration metrics
- checks
- iterations
Now you know exactly how to use authencation token for your apis. This concludes this post. Hope you enjoyed it.
Exercises
Try these quick steps:
-
After updating the product, add
DELTEOperation:- http.del(…)
- Pass a custom header:
- X-Debug-Mode: “true”
-
Reuse the same authHeaders
-
Add a check to verify deletion succeeded
-
Add sleep(1) after the delete