Authentication asks "who are you?" Authorization asks "are you allowed?" Most security breaches happen not because someone hacked the login — but because the authorization was broken. A regular user could access admin data. One user could see another user's records. These are the bugs that make headlines.
Most applications have roles: admin, manager, user, viewer. Each role has different permissions. A user should not access admin endpoints. A viewer should not modify data. Authorization testing verifies these boundaries.
| Endpoint | Admin | Manager | User | Guest |
|---|---|---|---|---|
| GET /admin/users | 200 | 403 | 403 | 401 |
| GET /users/me | 200 | 200 | 200 | 401 |
| PUT /users/{id} | 200 | 200 (own team) | 200 (own) | 401 |
| DELETE /users/{id} | 200 | 403 | 403 | 401 |
| GET /reports/financial | 200 | 200 | 403 | 401 |
| POST /settings/global | 200 | 403 | 403 | 401 |
Create this matrix FIRST before writing tests. Get it reviewed by the dev team. This is your authorization test plan. Every cell in this table is a test case.
import org.testng.annotations.Test;
import static org.hamcrest.Matchers.*;
public class AuthorizationTest extends BaseTest {
// ===== ADMIN-ONLY ENDPOINTS =====
@Test
public void testAdminCanListAllUsers() {
asAdmin()
.when()
.get("/admin/users")
.then()
.statusCode(200)
.body("users.size()", greaterThan(0));
}
@Test
public void testRegularUserCannotListAllUsers() {
asUser()
.when()
.get("/admin/users")
.then()
.statusCode(403)
.body("error", containsString("forbidden"));
}
@Test
public void testGuestCannotListAllUsers() {
asGuest()
.when()
.get("/admin/users")
.then()
.statusCode(401);
}
// ===== OWN DATA vs OTHER'S DATA =====
@Test
public void testUserCanViewOwnProfile() {
asUser()
.when()
.get("/users/me")
.then()
.statusCode(200)
.body("email", equalTo("user@example.com"));
}
@Test
public void testUserCannotViewOtherUserProfile() {
// User ID 1 trying to view user ID 999
asUser()
.when()
.get("/users/999/profile")
.then()
.statusCode(403);
}
@Test
public void testUserCanUpdateOwnProfile() {
asUser()
.body("""
{ "name": "Updated Name" }
""")
.when()
.put("/users/me")
.then()
.statusCode(200);
}
@Test
public void testUserCannotUpdateOtherUserProfile() {
asUser()
.body("""
{ "name": "Hacked Name" }
""")
.when()
.put("/users/999")
.then()
.statusCode(403);
}
// ===== ADMIN CAN ACCESS EVERYTHING =====
@Test
public void testAdminCanViewAnyUserProfile() {
asAdmin()
.when()
.get("/users/999/profile")
.then()
.statusCode(200);
}
@Test
public void testAdminCanDeleteUser() {
asAdmin()
.when()
.delete("/admin/users/test-delete-user-id")
.then()
.statusCode(anyOf(is(200), is(204)));
}
@Test
public void testUserCannotDeleteAnyUser() {
asUser()
.when()
.delete("/admin/users/test-delete-user-id")
.then()
.statusCode(403);
}
// ===== PRIVILEGE ESCALATION =====
@Test
public void testUserCannotEscalateOwnRole() {
asUser()
.body("""
{ "role": "admin" }
""")
.when()
.put("/users/me")
.then()
// Should either ignore the role field or return 403
.statusCode(anyOf(is(200), is(403)));
// If 200, verify role did NOT change
asUser()
.when()
.get("/users/me")
.then()
.body("role", not(equalTo("admin")));
}
}IDOR is the #1 authorization vulnerability in APIs. It happens when the server trusts the user-supplied ID without checking if the user owns that resource. Example: GET /orders/123 — what if user A can see user B's order just by changing the ID?
import org.testng.annotations.Test;
import static org.hamcrest.Matchers.*;
public class IdorTest extends BaseTest {
@Test
public void testUserCannotAccessOtherUsersOrders() {
// Step 1: Get user A's orders to find a valid order ID
String userAOrderId = asUser()
.when()
.get("/orders")
.then()
.statusCode(200)
.extract()
.jsonPath()
.getString("orders[0].id");
// Step 2: Login as user B (different credentials)
String userBToken = loginAs("userB@example.com", "UserB@1234");
// Step 3: Try to access user A's order with user B's token
given()
.header("Authorization", "Bearer " + userBToken)
.when()
.get("/orders/" + userAOrderId)
.then()
.statusCode(403); // Must NOT return 200
}
@Test
public void testUserCannotModifyOtherUsersOrder() {
given()
.header("Authorization", "Bearer " + userToken)
.contentType("application/json")
.body("""
{ "status": "cancelled" }
""")
.when()
.put("/orders/other-users-order-id")
.then()
.statusCode(403);
}
@Test
public void testUserCannotDeleteOtherUsersData() {
given()
.header("Authorization", "Bearer " + userToken)
.when()
.delete("/orders/other-users-order-id")
.then()
.statusCode(403);
}
private String loginAs(String email, String password) {
return given()
.contentType("application/json")
.body(String.format("""
{ "email": "%s", "password": "%s" }
""", email, password))
.when()
.post("/auth/login")
.then()
.statusCode(200)
.extract()
.jsonPath()
.getString("token");
}
}IDOR is easy to test and easy to miss. For every endpoint that takes an ID parameter (user ID, order ID, account ID), try accessing it with a different user's token. If it returns 200 with someone else's data — that is a critical P0 security bug.
Q: What is IDOR and how would you test for it?
A: IDOR (Insecure Direct Object Reference) is when an API lets authenticated users access resources they do not own by simply changing the resource ID in the URL. Example: if GET /orders/123 returns order data regardless of which user is asking, that is IDOR. To test: (1) Login as User A, create a resource, note its ID. (2) Login as User B. (3) Try to GET/PUT/DELETE User A's resource using User B's token. (4) Expect 403. If you get 200 — it is a critical security bug. Test this for every endpoint that takes an ID parameter.
Q: What is the difference between 401 and 403? When do you expect each?
A: 401 means the server does not know who you are — authentication failed. It happens when: no token, expired token, invalid token. 403 means the server knows who you are but you do not have permission — authorization failed. It happens when: regular user hits admin endpoint, user tries to access another user's data. Key difference: 401 is an identity problem, 403 is a permissions problem. In testing, I always check: Guest gets 401 (no identity), User gets 403 on admin endpoints (has identity, lacks permission), Admin gets 200 (has identity + permission).
Key Point: Authorization testing verifies permission boundaries. Build a role-endpoint matrix. Test IDOR by accessing other users' resources. Every ID in a URL is a potential IDOR vulnerability.