Testing Auth — Valid Tokens, Expired Tokens, Permission Levels

8 min read

You now know how API keys, OAuth, and JWTs work individually. This lesson is about pulling them together into a systematic auth-testing approach — the matrix of tests that, when run on every release, catches the auth bugs that account for some of the most embarrassing security incidents in the industry. Auth bugs aren't just inconvenient; they leak private data, allow privilege escalation, and end up in the news. Good auth testing is one of the highest-leverage things a QA engineer does.

The two questions

Every auth test answers one of two questions:

  • Authentication — "Is the caller who they claim to be?" Failure mode: 401 Unauthorized.
  • Authorisation — "Is the caller allowed to do this?" Failure mode: 403 Forbidden.

Mixing the two is one of the most common bugs in API code, and one of the easiest to test for. Strict separation in your tests makes broken servers obvious.

The authentication matrix

For every protected endpoint:

ScenarioExpected
No token401
Empty token401
Invalid token (random string)401
Expired token401
Token with wrong signature401
Token issued by a different auth server401
Token meant for a different audience401
Valid token, correct scope200

Eight cases. Don't test all eight per endpoint — write one parameterised test per case and run it against every protected endpoint. The maintenance is then proportional to the kinds of failures you care about, not the number of endpoints.

The authorisation matrix

Once authentication passes, role and permission checks kick in. Build a permission matrix: rows are roles, columns are endpoints, cells are expected status codes.

Three roles × five endpoints = fifteen tests. The matrix scales linearly: add an endpoint, add a column. Add a role, add a row. Most teams never build this matrix and end up with role-leak bugs that surface only in production.

A useful tactic: before reading the spec, write down what you think each cell should be, then compare with the team. Disagreements between testers and engineers about the matrix are exactly the conversations worth having.

Token lifecycle tests

Auth bugs hide in the lifecycle, not just the single-request happy path:

  • Fresh token → endpoint returns 200.
  • Expired token → endpoint returns 401 with a clear token_expired error.
  • Refreshed token → original (expired) token still rejected; new token works.
  • Logout / revoke → the just-revoked token stops working immediately. (If your auth scheme is stateless JWT with no denylist, this won't pass — and that itself is the bug to flag.)
  • Token issued before a password change → should be invalidated when the password rotates.
  • Token issued before a role change → should be invalidated, or the role check should re-read the user's current role on each request.

The last two are subtle and frequently broken. Worth a dedicated test each.

Privilege escalation tests

Two classic bugs and how to test for them.

Horizontal privilege escalation

User A's token can access User B's data. The classic shape:

GET /api/users/123/orders   (token belongs to user 123)  → 200
GET /api/users/124/orders   (still using user 123's token)  → should be 403

If the second call returns 200, anyone who knows another user's id can read their data — a textbook IDOR (Insecure Direct Object Reference) vulnerability. Add a test for every "user owns resource" relationship in your API.

Vertical privilege escalation

A standard user can perform admin actions:

POST /api/admin/audit-logs  (token has 'user' role)  → should be 403

If a user-scoped token returns 200 here, the role check is missing or broken. The matrix covers this — make sure each row tests every admin endpoint, not just the ones the role can reach.

Information leakage in auth errors

Error messages can themselves be a vulnerability. Watch for:

  • Different responses for "unknown username" vs "wrong password" — lets attackers enumerate valid usernames.
  • Stack traces in 401/403 bodies — leak server internals.
  • Distinct error codes for "token expired" vs "token signature invalid" can be helpful for clients but also help attackers. Industry leans toward generic messages.

A simple test: send a known-bad token and a known-good-but-wrong-user token. If the response bodies differ in revealing ways, raise it.

Testing token storage and transport

These are mostly review questions, not unit tests, but a competent QA review covers:

  • All auth happens over HTTPS — no plain HTTP fallback.
  • Tokens never appear in URLs (logged), referer headers, or browser history.
  • Mobile apps store tokens in the OS keychain / EncryptedSharedPreferences, not plain files.
  • Server logs redact authorisation headers.
  • Error responses don't echo tokens back to clients.

A two-minute pass through the network tab and the server's log output catches a startling number of these.

A complete auth test plan

For a typical product API, your standing auth test suite should include:

  1. One parameterised "auth matrix" test that runs the eight authentication scenarios above against every protected endpoint.
  2. One parameterised "permission matrix" test that runs every (role, endpoint) pair and asserts on the expected status.
  3. Lifecycle tests — refresh, logout, password change, role change.
  4. IDOR tests — for each "user owns X" relationship, verify another user's token gets 403.
  5. Smoke tests for the auth endpoints themselves — POST /login, POST /token, POST /refresh, POST /logout — with one happy and one negative case each.

Numbers 1 and 2 alone can give you ~50-100 tests on a moderately-sized API for very modest engineering effort.

⚠️ Common mistakes

  • Testing only with admin tokens. Most bugs hide in the gap between "admin" and "everyone else." Ensure your matrix has at least one row per role.
  • Forgetting cross-tenant tests in multi-tenant systems. "Tenant A's user accesses tenant B's data" is a bigger version of the IDOR test, and it's catastrophic when broken.
  • Skipping logout/revocation tests because they "should work." They very often don't — especially with stateless JWTs where no server-side denylist exists.

🎯 Practice task

Build an auth test plan for one real endpoint set. 30-40 minutes.

  1. Pick an API you have access to (yours, or a free service like Trello / Notion / Linear) where you can create at least two users.
  2. Identify three or four endpoints that involve user-owned data (e.g. boards, notes, issues, files).
  3. Build a permission matrix on paper: roles you have access to × endpoints. Mark expected outcomes.
  4. Run the matrix manually with curl, noting actual results next to expected. Each row of the matrix is one test.
  5. Try one IDOR test: take user A's token and access user B's resource by changing the ID in the URL. Note the result.
  6. Try a tampered-token test using jwt.io (from the previous lesson). Note the result.
  7. Stretch: automate one row of the matrix as a small script. Run it as part of your daily routine — auth bugs are exactly the kind that shouldn't recur silently.

That wraps up authentication and authorisation. Chapter 4 turns to the shape of API requests themselves: how to design and test the parameters, bodies, headers, and uploads your APIs accept.

// tip to track lessons you complete and pick up where you left off across devices.