A blog about testing ·

Authentication vs authorization: how QA should test access rights

Many serious security bugs start with a simple confusion: the system knows who you are, but forgets to check whether this object belongs to you.

Access testing model
  1. No token: should be 401
  2. Wrong token: should be 401
  3. Right user, wrong role: 403 or hidden action
  4. Right role, wrong object: 403 or 404

Authentication answers “who are you?”

Authentication is login, password, token, session and expiry. It proves identity, but it does not decide which data the user may read or change.

Basic auth tests include correct credentials, wrong password, email case, missing token, expired token and logout.

Authorization answers “what may you do?”

Authorization checks role and ownership. A user may be logged in and still have no right to open another user order, account, document or customer card.

This is where many API bugs hide because IDs are easy to guess and frontend buttons are not real protection.

The object ownership check

Create or find two users. Open an object as the owner, note its id, then request the same id with the second user token. The expected result is usually 403 or 404.

Why 404 is sometimes better

For private objects, 404 hides whether the object exists. 403 says it exists but you cannot access it. Which one is correct depends on product requirements.

Role checks

Roles should be tested both in UI and API. If an admin-only button is hidden, send the request anyway with a regular user token.

A hidden button is usability. A rejected request is security.

Common access bugs

The most common bugs are missing owner filters, role checks only in frontend, tokens accepted after logout, and endpoints that return different errors for existing and non-existing private objects.

When you report an access bug, include both accounts, both tokens context and the object id. Without that, developers cannot reproduce the permission boundary.

The actor × action × object matrix

A role list is not enough. Build rows such as manager A reads client A, manager A edits client B, a normal user calls an admin endpoint, and a former employee reuses an old token. Every row needs an expected allow, deny or hide decision.

Test more than GET. BOLA may be fixed for reads but remain in PATCH, DELETE, bulk endpoints, exports or nested ids. UUIDs make guessing harder but never replace a server-side permission check.

Minimum access matrix
Actor       Action  Object        Expected\nmanager A   GET     client A      200\nmanager A   GET     client B      403/404 by contract\nmanager A   PATCH   client B      403/404, no data change\nviewer      DELETE  client A      403\nno token    GET     client A      401 + WWW-Authenticate

Session and token lifecycle

Test issue, rotation after login or privilege change, expiry, refresh, logout, revocation, password change and parallel devices. Some architectures allow an old access token until a short expiry; then logout must revoke refresh and the UI must not promise more.

  • The session id changes after login to prevent session fixation.
  • Cookies use Secure, HttpOnly and suitable SameSite; tokens do not leak into URLs or logs.
  • Expired, corrupted and incorrectly signed tokens are rejected.
  • Role changes reach permission checks within the agreed time despite caches.
  • Refresh reuse, logout and password changes follow the documented policy.

BOLA, BFLA and property-level access

BOLA means the user may call the function but the backend does not check the object id. BFLA means the role should not call the function at all, such as a manager calling admin export. Property-level failure exposes or accepts fields the role must not use.

In a CRM, test a foreign client id, an admin-only bulk export and a PATCH containing owner_id or is_vip that the form never shows. Hiding a button fixes none of those server risks.

Common access-control questions

Should a foreign private object return 403 or 404?

Either can be valid by contract. 404 hides existence; 403 states refusal. Consistency, no data leakage and no action are the requirements.

Does a UUID prevent BOLA?

No. UUIDs reduce guessing, but ids leak through links, logs, exports and responses. The server must check authorization for every object.

Is checking roles through the UI enough?

No. Hidden controls improve UX, but a request can be sent directly. The server must authorize every method and object.

Access testing is small in steps and large in impact: one changed id can reveal whether the backend really protects user data.

Practise on live services Next write-up

← All write-ups