Skip to content

Batch anonymous authorization checks per object - #6257

Open
bram-atmire wants to merge 2 commits into
DSpace:mainfrom
bram-atmire:item-page-request-reduction
Open

bram-atmire wants to merge 2 commits into
DSpace:mainfrom
bram-atmire:item-page-request-reduction

Conversation

@bram-atmire

@bram-atmire bram-atmire commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

References

Fixes #6256. Related to the request-batching discussion in #3161.
Companion backend guard: DSpace/DSpace#13134. This request reduction uses the existing REST API and does not require that companion change or a REST contract change.

Description

Batch anonymous authorization checks per object using /authz/authorizations/search/object without a feature filter. The backend evaluates permissions once per object request; Angular selects each requested feature from the returned grants. This replaces the hardcoded list of features assumed to be denied anonymously.

On the measured anonymous item page, 14 authorization requests become 2 (site and item), and total API requests fall from 39 to 27. All 14 individual decisions match the batched results, including public COAR discovery. There is no metadata catalogue request.

Authenticated visitors and explicit-EPerson queries retain individual checks. Anonymous batching uses the existing request cache for pending-request deduplication, expiry, invalidation and SSR transfer; it adds no separate permission cache. It follows all pagination links before deciding that a feature is absent. If any page fails, it falls back to the individual feature request, preserving behavior on installations with a broken custom feature.

Authentication readiness follows the same blocking flag used by application initialization. The previous loaded check never completed for successful anonymous initialization.

Measurements

Production builds, one public item, same local backend/data, HTML page caches disabled. Four process runs in baseline/revised/revised/baseline order; each includes one initial render and ten warm renders. The baseline is the pre-PR authorization service at 2f1bf85ce2ea.

Metric Before After
Authorization API requests 14 2
Total API requests 39 27
Feature catalogue requests 0 0
COAR inbox HTML link and HTTP Link header Present Present

Counts were identical across all 22 renders per variant. Median warm full-response time was 270→262 ms; timings varied by run order, so this is not a demonstrated latency or throughput improvement. The measured benefit is 12 fewer HTTP requests (86% fewer authorization requests; 31% fewer total API requests). The original PR performance claims are superseded.

Browser verification: authorization GETs were 2 during hydration, 1 when navigating to a new item, and 0 on returning to the first item. CORS adds matching OPTIONS preflights. Item content and COAR inbox links were preserved with no JavaScript exceptions.

Instructions for Reviewers

  1. Enable ldn.enabled and visit a public item anonymously with server page caching disabled. Expect two unfiltered authorization searches (site and item), with embed=feature and size=100, instead of one search per feature.
  2. Confirm COAR inbox links in the HTML and HTTP Link header, and configuration/policy-dependent features such as QA and anonymous edit grants.
  3. Navigate to another item: expect one authorization lookup for the new item; the site result should be reused. Return to the first item and check cache reuse.
  4. Log in and verify ordinary feature checks and edit controls. Explicit-EPerson checks must retain their original path.
  5. Return a batch error, or fail a later page: confirm fallback to individual feature checks. Verify pagination with a granted feature appearing after the first page.

Validation: 137 selected Angular tests passed (including a rerun of the affected tests); production browser and SSR builds passed. Full source lint passed with generated decorator registries excluded, and no source circular dependencies were found with those generated files excluded. The standard checks still encounter pre-existing generated-registry lint errors/cycles; full CI and Docker-based end-to-end tests remain pending. No new dependencies, UI text or configuration options.

Checklist

This checklist provides a reminder of what we are going to look for when reviewing your PR. You do not need to complete this checklist prior creating your PR (draft PRs are always welcome).
However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!

  • My PR is created against the main branch of code (unless it is a backport or is fixing an issue specific to an older branch).
  • My PR is small in size (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
  • My PR follows all coding best practices based on the Code Conventions Guide
  • My PR passes ESLint validation using npm run lint
  • My PR doesn't introduce circular dependencies (verified via npm run check-circ-deps)
  • My PR includes TypeDoc comments for all new (or modified) public methods and classes. It also includes TypeDoc for large or complex private methods.
  • My PR passes all specs/tests and includes new/updated specs or tests based on the Code Testing Guide.
  • My PR aligns with Accessibility guidelines if it makes changes to the user interface.
  • My PR uses i18n (internationalization) keys instead of hardcoded English text, to allow for translations.
  • My PR includes details on how to test it. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
  • If my PR includes new libraries/dependencies (in package.json), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.
  • If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
  • If my PR fixes an issue ticket, I've linked them together.

Implementation and validation assisted by OpenAI Codex.

An anonymous item render fires ~15 per-feature authorization checks, about ten
of which ask about admin/edit/login-only capabilities an unauthenticated
visitor can never hold. AuthorizationDataService.isAuthorized now answers
false locally, without a REST call, for those features when no one is logged
in, and waits for isAuthenticationLoaded() before deciding so authenticated
users are never transiently treated as anonymous.

Measured on a local DSpace 11 stack with OpenTelemetry tracing: an anonymous
item render drops from 42 to 31 backend calls (15 to 5 authorization checks)
and single-core SSR render throughput rises ~45%.

Refs DSpace#6256, DSpace#3161

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@lgeggleston lgeggleston added bug improvement authorization related to authorization, permissions or groups performance / caching Related to performance, caching or embedded objects labels Sep 23, 2026
@lgeggleston lgeggleston moved this to 🙋 Needs Reviewers Assigned in DSpace 11.0 Release Sep 23, 2026
@lgeggleston

Copy link
Copy Markdown
Contributor

Hi @bram-atmire, thanks for the PR! Would you be able to add the complete PR checklist to the description? Also you may have already seen, but this is currently running into a few errors in the CI (looks like just linting).

@MMilosz
MMilosz self-requested a review September 25, 2026 06:38
@MMilosz

MMilosz commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

This is a big step towards a good idea. Cutting waste on anonymous traffic-the largest load contributor-feels like exactly the right place to optimize

After checking the code, I have a tiiiny comment regarding the open design question you asked:

The authenticated-only feature set is a static list in AuthorizationDataService. That holds for every deployment I know of, but the edit/write features check a policy the backend genuinely evaluates, so a static list steps slightly outside the policy-driven model.

Personally, I lean toward the backend-first approach rather than frontend changes. Genuine question, did you already consider defining the whitelist directly in the backend instead of the frontend?

This is my "open design question for open design question", but how would you feel about this alternative:

  1. In the backend, let each Feature declare its own requirement (e.g., an enum flag like, for example EPersonRequirement.REQUIRED)
  2. Then, introduce a guard in AuthorizationFeatureServiceImpl#isAuthorized : if (feature.getEPersonRequirement() == EPersonRequirement.REQUIRED && context.getCurrentUser() == null) { return false; }
  3. Ultimately, expose a whitelist of features via the API (refactor an existing endpoint like /api/authz/features, currently, it's admin-only)
  4. Then, have Angular fetch it once (and perhaps cache it for a while) to use it on the client side

Does that make sense?

My reasoning for the backend-first approach is that:

  • A static UI list tightly couples the frontend to backend authorization internals
  • To extend the list, someone has to read through the backend *Feature.java implementations to find which ones check getCurrentUser() != null (which I suspect is how you prepared the Feature list in the PR)
  • What's important is that Features vary in what they depend on:
    • authentication "what EPerson are you?"
    • authorization "do you have a policy? are you in a group?"
    • config "is it enabled?" (QAAuthorizationFeature only check if qaevents.enabled is true)
    • or, a mix of all

Happy to see this moving. Overall, great work (on all your PRs lately)!

@bram-atmire bram-atmire changed the title Skip authenticated-only authorization checks for anonymous visitors Batch anonymous authorization checks per object Sep 26, 2026
@bram-atmire

bram-atmire commented Sep 26, 2026 •

Copy link
Copy Markdown
Member Author

@MMilosz, thank you for the review & feedback!! I agree that the backend should own the permission decisions. I removed the hardcoded frontend list. Your distinction between authentication, policy and configuration was important: QA/COAR can be enabled anonymously, and some edit/manage features can be granted through Anonymous resource policies.

The companion backend PR, DSpace/DSpace#13134, adds a default-false requiresAuthentication() declaration and the shared guard you suggested. Only features already guaranteed to reject a null context user opt in; policy-dependent and configuration features retain their existing behavior.

For the HTTP request reduction, I switched Angular to the existing all-features object search. A conservative authentication-only list plus a catalogue fetch gave no net request saving on the tested item page. The revised batching shares one lookup per object and selects each feature from the grants returned by the backend. It follows pagination, uses the existing request cache/invalidation, and falls back to individual checks if a batch fails. Logged-in and explicit-EPerson checks retain their original path. No new public metadata endpoint/property is needed.

The updated production SSR measurements are 14 → 2 authorization requests and 39 → 27 total API calls, consistent across 22 renders per variant. All 14 decisions matched the individual requests, and both HTML and HTTP COAR inbox links were preserved. Browser navigation required one authorization GET for a new item and none when returning to the cached item. I replaced the earlier performance claims; the timing sample does not establish a reliable throughput gain.

@lgeggleston, the complete PR checklist and updated validation/reviewer instructions are now in the description. The original import-order lint issue is fixed. Local selected tests and production builds passed; the standard lint/circular checks still report generated-registry issues as documented in the description, and CI for this revision is pending.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authorization related to authorization, permissions or groups bug improvement performance / caching Related to performance, caching or embedded objects

Projects

Status: 🙋 Needs Reviewers Assigned

Development

Successfully merging this pull request may close these issues.

Skip authenticated-only authorization checks for anonymous visitors

3 participants