Course outline

API Testing / Intermediate: interacting contracts / Lesson 8

Test authentication and authorization matrices

Exercise subject-resource-action relationships with invented identities and local policy functions.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate identity validation from permission to act on a resource.
  • Build positive and negative ownership, role, and tenant cases.

Ask two different questions

Authentication establishes a trusted subject context under a chosen credential model. Authorization decides whether that subject can perform an action on this resource now. A test that supplies a reader identifier directly to a policy function does not test authentication. Make that limitation explicit and use invented identities. Then test the relationship that matters: owner versus another reader, same tenant versus another tenant, permitted action versus a different action. A successful list operation does not automatically establish permission for an individual detail, export, edit, or background workflow.

Make denied relationships visible

A compact matrix is more reviewable than a single administrator success case. Include an owner, a same-tenant nonowner, a cross-tenant subject, and an absent subject. Decide the expected public refusal under the disclosure policy without assuming every API uses the same 401, 403, or 404 mapping. The example tests a local read policy with three conditions. It deliberately contains no roles or credentials. Passing it establishes only that the synthetic predicate distinguishes the listed relationships, not that every route invokes the predicate with trusted data.

python A synthetic ownership and tenant matrix
resource = {"owner": "reader-a", "tenant": "class-1"}
def may_read(subject, item):
    return subject is not None and subject["id"] == item["owner"] and subject["tenant"] == item["tenant"]

cases = [(None, False), ({"id":"reader-a","tenant":"class-1"}, True), ({"id":"reader-b","tenant":"class-1"}, False), ({"id":"reader-a","tenant":"class-2"}, False)]
for subject, expected in cases:
    assert may_read(subject, resource) is expected
print("four access relationships checked")
Expected output
four access relationships checked

This complete program evaluates predeclared trusted subject fixtures. It does not parse or validate tokens. Real subject establishment, route coverage, storage scoping, and response disclosure remain outside this model.

Check fields as well as resource access

Permission to read a resource may not include every property. A public book view can include title and identifier while an editorial note remains restricted. Test the response projection for each documented view and verify that forbidden fields are absent rather than merely null if absence is required. Also reject client-supplied ownership or privilege fields during writes according to the contract. The second program builds an allowlisted public projection. It is intentionally small and checks only one fixture; it does not discover all private fields or replace a framework authorization layer.

python Check an allowlisted public representation
record = {"id":"book-4", "title":"Quiet Rivers", "editorNote":"classroom-private", "owner":"editor-a"}
public = {key: record[key] for key in ("id", "title")}
assert public == {"id":"book-4", "title":"Quiet Rivers"}
assert "editorNote" not in public
assert "owner" not in public
print("public projection checked")
Expected output
public projection checked

The complete offline example preserves the selected public fields and excludes two synthetic restricted fields. No private data is used. It verifies this projection only, not storage access, request permission, or every possible serializer path.

Observe denials across all effects

For a denied edit, require the protected record to remain unchanged and include jobs, notifications, and derived views when relevant. List tests should verify both membership and exclusion: seeing the ownerโ€™s item is insufficient if another tenantโ€™s item appears beside it. Filtering after pagination can produce empty pages or incorrect counts, so review where scoping happens. Keep synthetic resource identifiers explicit and never test guessed identifiers against a real service. The objective is to validate a known policy on owned fixtures, not to explore an unfamiliar system.

Review invocation and lifecycle assumptions

A correct policy helper can be bypassed accidentally if one route omits it or loads the resource before applying tenant scope. Trace every public operation to the trusted enforcement boundary in code review, and assign actual middleware and storage checks to an approved isolated environment. Include supported lifecycle changes such as role removal or resource reassignment where the contract promises immediate effect. A local boolean matrix cannot establish cache invalidation or session revocation. State that uncertainty explicitly rather than claiming comprehensive security from a handful of policy assertions.

Common errors and fixes

The owner passes but every nonowner also passes

Why it happens
Only a positive role case was tested.
How to fix it
Add same-tenant and cross-tenant denials and verify unchanged state.
Prevent it next time
Review subject-resource-action matrices for every sensitive operation.

A list contains forbidden neighboring items

Why it happens
The test checks inclusion but not exclusion.
How to fix it
Assert the exact authorized membership and relevant count.
Prevent it next time
Include mixed-owner fixtures with clear expected scope.

A policy pass is reported as token validation

Why it happens
The test injects an already trusted subject.
How to fix it
Label the model boundary and separately verify identity integration.
Prevent it next time
Document which security boundary each test includes.

Check your understanding

1. A nonowner edit returns 403 but a queued email exists. What should the case assert?

Show worked solution

The refusal must preserve the protected record and omit the unauthorized email under this contract. Capture both observations. Then inspect where permission is checked relative to mutation and enqueueing. Correct public status alone does not establish that the denied operation had no business effect.

2. Why include the same reader id in two tenants?

Show worked solution

It detects policies that compare only the reader id and forget tenant scope. The synthetic matrix intentionally holds one attribute constant while changing the other. Whether identifiers are globally unique in a real system must be documented; the test should follow its actual resource and tenant model.

Key takeaways

  • Authentication and authorization require different evidence.
  • Negative cases verify exclusion and unchanged effects.
  • A policy function pass does not prove every route enforces it.

Official references