Search authorization must cover the whole response, not only the link opened after a result click. Titles, snippets, total counts, facets, autocomplete suggestions, and timing can all reveal that a private record exists. A search index often holds stale permission fields because membership changes separately from content. Define the security source of truth, then make query-time filtering or a trusted permission join part of result selection. Recheck access when the detail route opens and before mutations. If a search backend uses document-level rules, test their role-combination behavior and ensure application permissions map to those rules correctly.
Search Permissions and Private Results
Working case
Team East loses access to case 47 at noon. The case's indexed title remains until a worker catches up. At 12:01, an East reviewer searches for the equipment name. A detail-page 403 prevents reading the record, but a search card that displays its title and excerpt still discloses information. The query path must exclude it using current rights; counts and suggestions must use the same scope. A shared link to the former result should still return a permission decision from the primary service.
Implementation boundary
function visibleCaseResults(candidates, allowedCaseIds) {
return candidates.filter(caseRecord => allowedCaseIds.has(caseRecord.caseId))
.map(caseRecord => ({ caseId: caseRecord.caseId, title: caseRecord.title }));
}
console.log(visibleCaseResults([{ caseId: 47, title: "Pump" }, { caseId: 62, title: "Lift" }], new Set([62])));
// Output: [ { caseId: 62, title: 'Lift' } ]This small function demonstrates a projection after an authorization decision; a production search must apply the visibility constraint before ranking, pagination, facets, and counts. Otherwise it can leak totals and produce sparse pages. The allowed set cannot be taken from a client claim. Derive it from the authenticated principal, current team membership, and record policy, preferably as a query predicate or secure search role. If fine-grained joins are expensive, consider an authorized candidate window followed by controlled refill, while keeping a hard cap and no private aggregate signals.
Cost and boundaries
Checking every candidate after a broad search can be expensive and may expose page shape. Permission-aware indexing or query filters improve retrieval but add update work when membership changes. Query-time joins to the authoritative store provide fresher decisions with database and network cost. Choose according to the sensitivity of records and the required revocation speed; benchmark worst-case teams with few allowed hits. A cache of permissions needs a short, explicit lifetime and invalidation route for revocation, not an indefinite success entry.
Failure trace
A developer filters visible cards but leaves the unfiltered total count and autocomplete endpoint. The team can infer private case activity even though detail routes reject access. In another failure, two roles combine unexpectedly in the search service and one grants broader visibility than intended. Test one user with multiple roles, revocation during an active session, empty authorized result sets, and a stale index. Inspect all response fields and suggestions, not only the rendered cards.
Verification
- Revoke access while indexed content remains stale and inspect cards, snippets, counts, facets, and suggestions.
- Combine roles and verify the intended visibility intersection or union explicitly.
- Open and mutate a formerly visible case; both routes must recheck current authority.
Practice drill
Create two teams and seed cases with distinctive titles. Remove one team membership while keeping the search index stale. Search, request suggestions, inspect facets and totals, follow an old result link, and try a mutation. No private title or count should appear, and each authoritative route should deny the old member. Repeat with a user holding two roles. Record query work and refill behavior so the secure path is also usable under sparse visibility.
Decision note
Treat search as a protected read API. Apply current permissions before any result-derived content is exposed.
Common Mistakes
- Relying on detail-page authorization while search exposes private metadata.
- Filtering cards but returning unfiltered counts or suggestions.
- Caching permission results without a revocation strategy.
Connected lessons
Application Search and Retrieval; Search Index Freshness and Rebuilds; Search Query Normalization and Ranking; Search Filters and Result Cursors; Authorization: check permission for this record on every request; Search Index Freshness and Rebuilds; Search Filters and Result Cursors.
Apply and check
Build Project: private case search and review Web Development: search and quality contracts quiz.
Further connections
Object-Level Authorization for Reads and Writes; Private Export Authorization and Artifact Scope.
