A token scope permits a class of operation; tenant ownership decides which records that operation may touch.
Spring Security scope versus tenant ownership: two separate decisions
The same HTTP status can hide a different failure
In the checked web context, a signed token with receipt.write cannot call a receipt.read endpoint: the filter chain returns 403 before the service. A token with receipt.read but tenant-east cannot read the tenant-west path: the method rule returns 403. Both are forbidden, but the responsible layer differs. A valid scope does not grant all tenant rows.
The filter requires SCOPE_receipt.read. The managed reader uses @PreAuthorize to compare its tenant argument with authentication.name, which came from the validated JWT tenant_id. The JDBC query also has tenant_id in its predicate. This gives a database backstop if a caller changes routes or calls another service method. Roles and authorities have different naming rules; do not substitute a role string for a scope without an explicit mapping.
Trace decisions in order
First verify token trust and required claims. Then check endpoint scope, service ownership and row predicate. Record denial counts by layer without logging bearer tokens. If the user has access to several tenants, the identity system needs a membership rule for the selected tenant; this single-tenant fixture deliberately does not implement that model.
Checked source
.authorizeHttpRequests(requests -> requests
.requestMatchers("/contract/tenant/**").hasAuthority("SCOPE_receipt.read"))
@PreAuthorize("#p0 == authentication.name")
public String read(String tenant) { return scopedLookup(tenant); }Verification boundary
JwtTenantBoundaryTest.wrongScopeIsForbiddenBeforeTheService and JwtTenantBoundaryTest.anotherTenantIsForbiddenAfterAuthentication runs in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete test.
Costs and limits
Two MockMvc requests check local filter and method outcomes. The fixture has no remote issuer, real tenant registry, token revocation, database row-level security or deployed gateway. Extra ownership checks add small local comparison cost; the database predicate must still be indexed for large tables.
Common Mistakes
- Do not equate receipt.read with permission to read every receipt.
- Do not infer the denial layer from HTTP 403 alone.
- Do not let a URL tenant override the signed tenant identity.
Read next
Spring Security JWT tenant principal: map only a validated claim, Spring JdbcTemplate tenant predicates: put ownership in the SQL query, Spring method authorization: test the proxied service boundary, Spring Security roles and authorities: check the actual granted string.
