Skip to content

Identity and application authority: what lives where

In plain English

Identity answers “which account signed in, and how?” SyRF answers “what may this person do?” The implementation is transitional: Identity also stores application-group claims imported during migration, and the API accepts these alongside roles stored in Project Management (PM). A token claim is therefore currently an input to authorization, not just a description of the login.

Update, M1a (2026-09-30, #3858): a PM-owned decision boundary now exists for one capability, ListUsers, behind two default-off flags (application-authority mode). With the deployed default (off), everything below still describes runtime behaviour; the audit's source pins are unchanged. Update, M1b (#3860): in the enforced mode only, a PM-owned, revisioned and audited grant/revoke of Administrator and a self-capabilities read exist (grant and revoke application roles); they never write Identity's copy. Update, M1c (#3865): in the enforced mode only, every path that can remove an active administrator (role revoke, SyRF account deletion, Identity's admin and self-service deletion, the operator import) goes through one serialized last-administrator guard, with a PM pending-deactivation reservation taken before Identity destroys anything (the lifecycle guard). Update, M2a (#3867): in the enforced mode only, every application capability (email templates, batch administration, runtime feature-flag management as well as ListUsers) is decided by PM authority for HTTP, direct bearer and SignalR, and the web presents the backend capabilities response instead of the syrf_groups claim; every remaining claim consumer has an owner in the claim-consumer ledger.

Having a copy in two places is not inherently wrong. The important questions are who may change the original, how the copy is refreshed, and how quickly removal takes effect. The repository has an explicit migration copy, but no general ongoing application-role synchronization mechanism was found.

The separate permissions programme, #3335 already records the owner's accepted target: SyRF owns application roles; external groups affect access through explicit mappings. This document explains current code and the gap to that target. It does not approve mappings, change grants, replace that programme's plan, or authorize rollout.

For the independently developed cookie/session proposal, its assumptions, and the comparison with both the existing plan and this implementation, read Authentication architecture: independent proposal and three-way comparison. For how human authentication connects to API/PM permission checks, RabbitMQ workload credentials, deferred commands and protected realtime delivery, read the service/client inventory and overall authorization model.

Scope and evidence

Initial read-only source audit dated 27 September 2026, pinned to 0026b56a80fe85f1aea0f690663b02663de6899e. Source links below use that revision, so line numbers remain reproducible. No account data, credentials, live flags or databases were inspected or changed; no live harness or migration was run. Source behavior does not prove which version or authentication provider is deployed.

“Verified” below means verified in repository source, not end-to-end execution. “Gap” means a missing or divergent path in the inspected source. “Risk” describes a consequence requiring targeted acceptance tests. Deployment and acceptance status remain in the Phase 5 roadmap and validation ledger, not in this architecture reference.

Contents

Ownership matrix

“Source of truth” is separated from “value actually read today”: those are not always the same.

Information Stored/read today Authority and boundary
Password hash, MFA configuration/recovery material, passkey public credentials, external provider keys, security stamp Identity IdentityUsers, inherited ASP.NET Identity fields plus ApplicationUser.Passkeys/Logins Identity manages native authentication. Provider email is not an account-linking key. No biometric template is stored. E1, E2
Account ID versus researcher ID Identity account Id is OAuth sub; nullable SyrfUserId identifies PM pmInvestigator._id Identity mapping binds the two; they are not interchangeable. PM owns the researcher/domain record. Historical Auth0 subject is provenance, not the native account ID. E1, E4, E8
Mapping history and email uniqueness reservations Identity IdentityInvestigatorMappings, IdentityEmailReservations Identity coordinates one-time binding/claiming; permanent mapping history survives deletion. PM creates/owns domain records through its provisioning service. E4, E5
Application-wide roles PM Investigator.ApplicationRoles; Identity ApplicationUser.SyrfGroups; token and BFF-session copies Migration documentation calls PM authoritative. Current API enforcement accepts the union of claim groups and PM role names. Identity is the actual producer of native group claims from its own copy. No single continuously reconciled authority is implemented. E3, E6, E9
ASP.NET Identity role collection Identity IdentityRoles model/store registration Its existence must not be mistaken for the SyRF application-role source: UserClaimsService emits SyrfGroups, not a lookup of this collection. E1, E2
Project ownership, membership, active/disabled status and project groups PM project aggregate/security settings PM resource policy is authoritative for these facts. Project Administrator is a separate project-scoped group, not the application role or Identity role collection. E10
Stage grants PM stage security settings, project-group grants, defaults and legacy AllowedMemberIds Evaluated in PM domain code; not copied into Identity tokens. AllowedMemberIds contains membership IDs. Detailed policy redesign belongs to #3335. E10
Auth0 blocked state and PM Investigator Deactivated at migration Separate manifest/ledger provenance; both become native Identity indefinite LockoutEnd Each original source owns its own status. Import collapses their authentication effect, not their historical meaning. Clearing native lockout alone does not establish that both source reasons were removed. E3
Current native admission Identity RequiresPasswordReset, EmailConfirmed, required profile fields, mapping, security generation Identity admission checks these on native issuance/renewal/introspection. They are not project membership and not a continuous PM deactivation check. E7
Profile name/email/picture Both Identity and PM There are explicit update paths in both directions, with historical canonical-name intent. This is not general role synchronization or a cross-database transaction. E8, E11
Tokens and browser session Identity OpenIddict token/authorization stores; API server-side BFF session store Identity controls token validity; BFF holds tokens and a claim snapshot behind a browser cookie. PM still decides resource access. E12, E13

Information flow

flowchart TD
    A[Auth0 export and historical mappings] --> M[Explicit migration reconciliation/import]
    P[PM database: investigators, roles, projects and grants] -->|roles, deactivation, mapping evidence| M
    M --> I[Identity database: accounts, mappings, SyrfGroups, token records]
    I -->|sign-in, token issuance and refresh| T[Native tokens and userinfo]
    T --> B[BFF server-side session and saved claims]
    I -->|token validity via introspection| B
    B --> R[API or SignalR authorization]
    T -->|direct bearer request| R
    P -->|current PM roles and resource policy| R
    I -->|authenticated provisioning request| Q[PM provisioning endpoint]
    Q --> P
    P -->|profile events via API identity adapter| I
    B -->|callback records sign-in and profile| P

There is intentionally no ongoing “PM roles → Identity groups” arrow: only the migration arrow is verified. Introspection validates the native token; it does not synchronize roles between databases or replace the BFF session's saved claims. Identity's read-only PM connection supports bounded dormant-account claiming and owner notification; it is not a general authorization database reader. E4, E7, E12

Lifecycle walkthrough

1. Migration: an explicit snapshot, not a subscription

InvestigatorReconciler associates exported Auth0 users with Investigator records. It obtains application roles from the matched PM record and explicitly maps Administrator to lowercase administrator. Unknown role names are not blindly lowercased; readiness/parity checks must resolve them. The importer writes the resulting space-separated value to Identity SyrfGroups. It also preserves stable IDs, provider links and independent blocked/deactivated provenance. Either blocking source produces an indefinite native lockout. E3

This normalization is implemented. The historical statement that migration emits the wrong administrator case is not a current finding. It also does not prove that every runtime consumer normalizes PM role names: EffectiveApplicationGroups still unions the raw names. E6

Migration/campaign verification can compare populations at an explicit checkpoint. Running those tools is not an ongoing event feed and is outside this audit's authority. Re-import is not a safe substitute for a designed role-change operation.

2. Registration and provisioning

Registration creates an Identity account. Binding a researcher identity is a separate, fail-closed step. For a new researcher, the mapping service calls PM's authenticated provisioning endpoint with the selected Investigator ID and profile fields, then binds SyrfUserId and appends mapping history. This request does not carry application roles or project grants. For an eligible dormant record, Identity reads PM data and requires SyRF mailbox proof; a deactivated or previously mapped record is refused. An arbitrary provider's verified-email claim is not sufficient. These bounded claim rules must not be generalized into automatic email-based linking. E4, E5

Identity does not thereby gain ownership of projects or memberships. Its PM database access is read-only; the domain service performs provisioning writes. Multiple steps and compensation/idempotency safeguards exist, rather than one transaction spanning both databases.

3. Sign-in, claims and application profile

Native authentication verifies a configured credential. The authorize endpoint validates the Identity cookie/security stamp, admission requirements and a non-null SyrfUserId. With the syrf_api scope, UserClaimsService emits user_id, syrf_groups and a role representation from the Identity account. It does not fetch Investigator.ApplicationRoles. Userinfo now uses the same SyRF claim producer. E2, E7

The BFF callback obtains these claims and resolves the researcher ID. On that path, PM records sign-in usage and updates profile name/email/picture from the supplied values. ApplicationService rejects a missing native mapping rather than interpreting a GUID-shaped sub as an Investigator ID. However, the subsequent helper can create a missing PM record when a valid claimed ID is supplied and no conflicting email exists. Thus “never create during resolution” in high-level guidance is broader than the literal helper behavior; verified-ID provisioning and inference from sub must be distinguished. E8

4. Refresh and runtime enforcement

Native code/refresh exchange reloads the Identity account, checks its security generation, authentication evidence, mapping presence and admission, and rebuilds claims from that account. It does not reload PM roles. A refresh may therefore get updated Identity groups while still missing a PM role change. E7

BFF refresh replaces the stored access/refresh tokens but does not rebuild the session's SyrfGroups or Roles. Subsequent cookie-authenticated requests still construct their principal from that saved snapshot. Native introspection checks token activity, subject/audience and matching authentication evidence; it is not a group-refresh operation. Direct bearer requests and BFF requests can consequently differ after a group change. E12

For API and SignalR authorization, EffectiveApplicationGroups combines request claim groups with PM application-role names. Resource policy then evaluates project/stage grants. The UI permission report still supplies only request groups, so it is not guaranteed to report the same effective authority. Do not use UI visibility as the security boundary. E6, E9, E10

Since M3a (#3879, enforced mode only): project and stage policy, on HTTP and SignalR, and the permission report come from one evaluator (ProjectAuthorityEvaluator) over the project's facts read linearizably per request. Application groups grant no project or stage content, active membership comes first, and only View/RequestToJoin can be public. Off and shadow keep the union described above. See project and stage policy.

Since M4b (#3889, enforced mode only): protected SignalR pushes are decided at delivery from fresh subject and project facts, not from groups saved when the client subscribed; a suspended subject or removed member stops receiving member content even if no change notification reaches the replica holding the socket. See realtime delivery.

5. Role changes and revocation

No general role setter/synchronizer was found in the production Identity endpoint or API IIdentityService contract. The checked source has migration writers and test/dev fixtures, not a supported ongoing PM-role change protocol. Current claim groups can continue granting access after a PM role is removed because the API uses an additive union. Conversely, a raw PM role name may not match a lowercase configured claim-group name. Actual impact depends on the relevant policy and population; no live affected-user count is claimed. E2, E3, E6, E11

Existing native revocation is real: IdentitySessionRevocationService revokes tokens and authorizations, and security-sensitive paths rotate the account security stamp. Introspection checks that generation. These mechanisms are not automatically triggered by editing a PM role or either database by hand. E7, E13

6. Deactivation is not one operation

Keep four concepts separate: an Identity login lockout, PM Investigator account deactivation, disabled membership of one project, and revoked tokens/sessions. They have different scope and checks.

Migration snapshots PM deactivation into native lockout; dormant claiming reads PM deactivation. The custom native admission/refresh/introspection checks inspected here do not reread PM Deactivated, nor does IdentityAdmissionService evaluate LockoutEnd. Credential sign-in paths have their own lockout checks. Changing a database flag alone must therefore not be documented as immediate, system-wide revocation of existing sessions. A targeted lifecycle test is required before promising that behavior. E3, E4, E7

The API account-deletion path first asks its configured identity adapter to delete the login account, then marks the Investigator deactivated. Native deletion first revokes tokens/authorizations and rotates the stamp; failures stop destruction and return a bounded refusal/unknown outcome. The PM save is a second step and may fail after Identity deletion. The API reports that uncertainty rather than claiming atomic success. Its removePersonalData parameter does not select a separate temporary-deactivation flow in the inspected method. Auth0 deletion does not have the same already-issued-token revocation guarantee. E13

Implementation gaps and their consequences

Verified fact or gap Consequence / qualification
PM roles are copied into Identity during migration; no general continuous synchronizer was found “PM is authoritative” is documented intent, not a complete live consistency contract. Role additions/removals need a defined writer and propagation rule.
BFF refresh changes tokens but retains saved groups/roles Token renewal alone is not proof that cookie-session permissions changed. Test direct-token and existing-BFF behavior separately.
API/SignalR use claim + PM roles; permission report uses claims only Stored-only grants can disagree with UI reports. This remains #3335 work, not a new evaluator implementation in this PR.
Migration normalization exists, but runtime union preserves raw role names Do not resurrect the fixed migration case bug; separately test canonical runtime mapping before relying on PM-only administrator grants.
PM deactivation is not part of every current native account check A database edit is not a documented global disable/revoke workflow. Imported lockout, future deactivation and existing sessions need separate tests.
Migrated PM records retain legacy Auth0Id; profile events still address their identity adapter using it Under the native adapter, UpdateNameForUser forwards that value to an endpoint which looks up Identity account ID; a 404 is swallowed. This is a source-level mismatch for legacy-subject records, not a tested live failure. Name/picture/email paths need provider-aware acceptance. E8, E11
PM profile update and Identity update are separate operations; callback can overwrite PM profile from claims Documentation must identify writer precedence and failure handling rather than imply transactional synchronization.

These findings are not permission to repair roles, change deactivation semantics, create another identity store, or run the S30 helper. The synthetic administrator grant and Google link requested for that rehearsal remain separate operator actions. Directly editing Identity SyrfGroups would change a real authorization input for that account; it is not merely “fixing authentication” and would not establish the desired PM role management architecture.

Should Identity persist SyRF administrator status?

Provisional audit synthesis: not as a second independently managed authority. For this application, prefer SyRF-owned roles checked by the backend, with Identity supplying stable identity and authentication evidence. Keep a derived claim projection only where an identified consumer needs it. This is a system-specific recommendation consistent with the recorded C2 direction, not a standards mandate or implementation approval. The separately commissioned requirements-first recommendation is preserved in the companion comparison, distinct from this source-led synthesis. Neither is implementation approval.

Three choices must not be conflated: owning a role, persisting a copy of a role, and transporting a role in a signed token. A role claim can be assembled from an authoritative service at issuance without storing another role record. A persistent projection can also be legitimate, but needs a single source writer, version/provenance, reconciliation of removals, and defined stale/unavailable behavior.

Option Concrete benefit in SyRF Cost and implication
1. SyRF owns roles; backend checks them using stable identity Removes an independent persistent grant and makes removal a backend authorization concern. The API handler already loads the PM Investigator before even application-policy evaluation, so the change need not introduce an entirely new PM dependency at that point. Project/stage policy already depends on PM. Requires one evaluator used by API, UI capability reporting and SignalR, including existing streams/queued work. PM failure must not fall back to stale allow. Role vocabulary/admin editing and an access-impact migration remain necessary.
2. SyRF owns roles; Identity emits a clearly derived projection Preserves the existing Auth0-compatible syrf_groups/role contract while consumers migrate. A persistent projection lets the issuer emit claims without a PM round-trip at each issuance; an on-demand projection avoids duplicate persistent storage. Persistent projection needs reliable removal propagation, versioning, replay/reconciliation and a maximum delay; on-demand reads add issuance-time latency and a PM availability dependency. Neither refresh-token rotation nor introspection alone refreshes BFF saved roles. Sensitive backend operations still need current authority to meet immediate-revocation requirements.
3. Identity owns application roles, with one management authority Could centralize role administration and issuance if several applications deliberately share an identity-management platform. It can avoid PM reads for issuance and offer a single operator interface if actually built. Would require retiring PM as an independent application-role authority and deliberately revisiting C2. SyRF-specific policy is coupled to Identity; project/stage grants still belong to PM. Current IdentityRoles registration and SyrfGroups field do not supply a complete management/audit/revocation interface. No multi-application need was established by this audit.

Are application-role claims needed in tokens at all?

No protocol requirement makes SyRF administrator roles mandatory token claims. A viable alternative is to validate identity and token/session validity, resolve the stable Investigator mapping, and obtain application permissions from SyRF on the backend. Option 1 explicitly permits no persistent Identity role copy and no application-role claims in access tokens, ID tokens or userinfo. This is a design option, not approval to remove today's claims. RFC 9068 defines how authorization attributes can be represented; it does not require every application to put roles into tokens.

Do not conflate application roles with sub, the validated Investigator mapping (user_id today), issuer, audience, expiry, delegated scopes, or authentication evidence such as auth_time/amr. The no-role option retains the identity and protocol validations appropriate to each token type. An API audience or scope does not by itself establish permission to administer SyRF or read a particular project.

Concrete consumers that must change together

This is a source-level dependency inventory, not a claim that every downstream policy has been retested.

Current consumer Current dependency No-role-claim alternative
API application/resource authorization and SignalR invocation authorization EffectiveApplicationGroups unions request groups with PM roles. E6 Resolve canonical application roles from the backend authority; preserve resource membership/grant checks.
Permission report sent to the UI Passes request groups only. E9 Produce effective capabilities from the same evaluator as enforcement, including resource-specific context.
BFF session and /me Saves groups/roles at callback, reconstructs principal claims, exposes the saved fields through /me; token refresh does not replace them. E12, E16 Keep authentication/session evidence, but compute current capabilities through SyRF; do not preserve a separate authoritative grant snapshot in the cookie session.
Angular authentication, admin UI and selected route guards BFF provider converts /me.syrfGroups into a claim-shaped object; admin selector drives navigation, admin effects and ensureSyrfAdmin guards. E14, E16 M2a: navigation, admin effects and the admin-UI options present the backend capabilities response (mirrored into the store) wherever PM decides; ensureSyrfAdmin is replaced by requireApplicationCapability, which re-reads capabilities on each entry; the claim selector is only the off/shadow fallback (ledger). Consume backend capability results for display/routing. Fail closed while unknown; every protected backend action still rechecks authority. As built (M2a): guards wait for a fresh answer; menus show the claim decision until the first answer, and an unknown answer (not the API's coded 403/503) keeps the claim decision so off/shadow stay unchanged.
Support impersonation middleware Direct administrator group-claim check, outside the union helper. E14 M2b (#3869), flag impersonationReadOnlyEnforcement only: the actor's PM ImpersonateUsers is re-read on every request and hub invocation, decisions use the target only (the actor's claim is invisible), changes need a short-lived edit context, and both people are audited (support impersonation). With the flag off the claim check is unchanged. Explicit backend permission check on the original actor, preserving target restrictions and auditing; never trust a UI capability assertion.
Runtime feature-flag administration Provider-status and mutation paths directly check request groups against administrator/SyrfAdmin. E16 M2a: both go through the gate as ManageRuntimeFeatureFlags (claims decide only in off/shadow). Route both checks through the authoritative system-administration decision; preserve production mutation restrictions.
Long-lived statistics subscriptions Subscription filter saves request groups with the connection/project/user. E16 Resolve/revalidate effective authority for protected delivery and handle removal across live connections; changing only connection-time authentication is insufficient.

Other endpoint/domain calls also pass GetSyrfAppGroups() into policy helpers. A migration must trace those call chains, not merely delete a claim producer or replace the top-level authorization handler. The inventory demonstrates compatibility work; it does not establish that token roles are architecturally necessary once those consumers are migrated.

A capability response could answer “may administer settings?” or “may edit this stage?” without exposing the underlying role vocabulary. It is UI guidance, not a transferable authorization credential. Loading such a response must not grant access during an authority outage, and stale UI visibility must never override a fresh backend refusal. PM reads are already part of core authorization, but converting direct claim-only consumers adds an authority dependency there; latency, availability and invalidation need tests. Keeping the current BFF/session transport does not require keeping application-role claims in it.

What the current copy really buys

The verified compatibility benefit is preserving claim shapes expected by existing consumers: native UserClaimsService intentionally uses Auth0-compatible names and BFF role formatting E2. Angular's admin selector accepts administrator or SyrfAdmin; support impersonation middleware checks the administrator claim directly, outside the claim-plus-PM-role helper E14 (unless the M2b impersonationReadOnlyEnforcement contract is on, which decides it from PM ImpersonateUsers instead). Simply deleting the Identity field or ceasing claim emission would therefore break consumers, even if some API policies still passed.

The current copy also lets native issuance read Identity alone for roles. But it does not make normal SyRF authorization independent of PM: the API reads the Investigator and resource there anyway E6. It does not save the native BFF introspection round-trip either E12. No latency benchmark, load requirement or enduring multi-application requirement was found that establishes a need for a persistent second role record. Those are possible benefits to measure, not demonstrated reasons to keep two independent writers.

Performance, availability and removal

Application-owned checks can use a carefully invalidated cache, but an arbitrary TTL conflicts with the accepted rule that subsequent operations are denied after removal. The existing PM repository cache has a two-second expiry E15; do not call it an immediate distributed revocation guarantee. For sensitive administrator operations, prefer a current authoritative check or a revision/invalidation protocol proved to meet that rule. Measure cost before adding a new persistent projection.

Under all three options, distinguish role removal (loss of actions), application suspension (loss of SyRF admission), Identity disablement (credential admission), token revocation and logout. Clearing a role is not equivalent to deleting an account. A projection-only option without a current check gives bounded eventual removal, not the already-agreed immediate-operation rule. Long-lived SignalR subscriptions need their own reauthorization/invalidation contract; a check at connection time is insufficient to claim revocation thereafter. This audit does not claim those contracts are implemented.

Migration implications of the recommendation

  1. Inventory existing claim values, PM roles and consumers; present effective access changes for approval. The current Auth0 population was not inspected here. Do not import or remove grants based on names alone.
  2. Establish canonical SyRF role names, one audited management path and a shared backend decision used by API/SignalR and capability reporting. Preserve stable Identity-to-Investigator mapping.
  3. Compare old/new decisions without changing enforcement. Resolve differences, including impersonation and stored-only/claim-only users, before a separately approved switch.
  4. Prove removal and suspension while already signed in: direct token, current BFF session, refresh, multiple browser sessions/replicas, reconnect and active SignalR subscription; include PM/Identity outage, delayed/replayed events and failed writes. Previously saved research must remain intact.
  5. Keep compatibility claims explicitly derived and non-authoritative where still required. Remove the persistent Identity role copy only after its consumers are migrated and approved rollback behavior is defined. Do not erase it or rewrite migration history as part of this docs PR.

Who recommends what

These primary sources were checked on 27 September 2026. The application-specific recommendation above is our synthesis, not a claim that any source mandates a particular database layout.

Source Guidance What it does not establish
OWASP Authorization Cheat Sheet Separate authentication from authorization; deny by default; check permissions server-side on every request. Whether SyRF roles must physically reside in PM or Identity.
IETF RFC 9068 §2.2.3.1 Defines appropriate role/group/entitlement claim types when an authorization server puts those attributes in JWT access tokens. A requirement to include application roles at all or persist a second role record in the issuer; nor proof that SyRF uses this exact JWT profile.
Microsoft ASP.NET Core resource authorization Resource-specific decisions need the resource and custom authorization, not merely a generic attribute. That SyRF's custom handlers are absent: they exist and load resources. The issue is consistent inputs and lifecycle semantics.
Microsoft cookie backend-change guidance Backend account changes need principal validation/rejection or replacement; a pre-existing cookie is not self-updating. That SyRF has no native session validity check: it has introspection. Its separate saved-claim freshness problem is verified in E12.
OpenIddict token storage Stored revocability and per-request token validation are distinct choices; introspection is one way a separate API learns validity. That generic defaults describe SyRF's configured behavior. Native BFF already introspects; PM role freshness is a different question.
IETF RFC 7662 §4 Introspection caching trades freshness for load/latency and must not outlive reported token expiry. That introspection re-queries live application roles or updates a BFF session's group snapshot.

Decisions and bounded next steps

Already agreed — do not ask again as if undecided

The #3335 handover's REPORT.md records C1/C2 (8 September): SyRF owns application roles; external groups require explicit mappings; the access impact must be presented and approved before applying changes. It also records immediate denial of subsequent protected operations after revocation (R1), and no automatic system-administrator access to project content (A2). Those are target decisions, not proof of deployed code. The handover's PLAN.md WP4 proposes reusing PM roles and a minimal audited administration surface; it remains programme work, not a newly approved change here.

Genuinely unresolved details for the owners

  1. Transition mapping: which existing external group values map to which SyRF role, and what access additions/removals are approved? Keep existing access until that inventory and approval exist.
  2. Role transport and storage: after SyRF owns role administration, should application-role claims disappear entirely, or does an identified consumer justify a derived claim? Only then decide whether that projection needs persistent storage or can be obtained at issuance. Define unavailable/stale behavior; do not silently assume a projection is the chosen design.
  3. Revocation implementation: how will the accepted immediate-operation rule cover BFF snapshots, direct tokens, already-connected SignalR clients and queued work? Role publication and revocation must not leave two independent writers; implementation belongs with #3335 and auth integration.
  4. Account disable/restore: define the supported global operator action, separate from project membership and destructive account deletion. Specify which state changes and what happens to existing credentials, cookies and outstanding grants. Do not clear independent blocked/deactivated reasons by accident.
  5. Profile authority: choose the supported name/email edit surface and identity-ID translation during mixed-provider operation, plus reconciliation of failed two-service updates. Preserve verified-email rules.

The smallest next implementation slice should be selected only after these contracts are resolved with the existing programme owner: one role-change operation, its authoritative read, and grant/removal acceptance through both direct API and an existing BFF session. Wider group-editor changes can follow independently. Before promising global deactivation, add a separate existing-session disable/restore acceptance slice. This docs PR does not create duplicate issues or approve either slice.

Why this distinction is sound

OpenID Connect Core defines authentication and claims about a user; transporting an application claim does not itself decide where the application's role-administration authority should live. OWASP authorization guidance supports explicit trust boundaries, least privilege and checking authorization on each request. OpenIddict's token-storage guidance distinguishes token storage/revocation from validation strategies. Applied to SyRF, these support a clear ownership and freshness contract—not a blanket rule forbidding Identity from carrying role claims.

Documentation map and currentness

Document What it is good for Currentness / limit found in this audit
This reference Two-store ownership and lifecycle explanation Source-pinned; in review, not a deployment ledger or new design approval.
Behaviour-parity matrix Native behavior requirements and migration preservation rules Explicitly calls PM authoritative for roles and documents import. Its earlier token-claims row still says userinfo duplicates projection; current code and later validation entry say that was closed by PR #2926. Read dated updates, not one isolated row.
Phase 5 roadmap, validation, context Operational sequencing, acceptance and locked boundaries Keep live evidence here. Merged code, an isolated rehearsal and ordinary staging/provider cutover are distinct. Historical findings are retained beside closure notes.
Migration feature Original design rationale and correction history Explicitly Deprecated. August correction rows include old deployment state and the old userinfo divergence; not current operational instructions.
Revised strategy Why BFF/Auth0 precedes native cutover Older issue prose still describes missing CSRF/session infrastructure; verify current implementation rather than treating it as an open-task inventory.
Archived BFF migration plan Historical plan The former docs/planning/auth0-openiddict-bff-migration.md path has moved here; do not restore it as a second source of truth.
Enable BFF guide Configuration and browser/BFF topology Not an authority contract. Its example mapping Auth0 user_id directly to the namespaced researcher ID is not the current stable Investigator-ID rule; follow the parity gate, not that illustrative snippet.
MongoDB reference Collection ownership, mappings and reservations Useful storage reference; does not establish continuous role synchronization.
Identity deployment notes Original service-integration checklist Undated “remaining” tasks predate the implemented host/chart work; not a reliable live deployment/status ledger. No service-root README was found in this snapshot.
Emailed step-up, recovery outbox, claim recovery Focused security mechanisms and their boundaries Retain as specialist references; do not duplicate them into a second operational runbook.
Permissions programme #3335 Effective-authority redesign, accepted ownership decisions and broader feature inventory Its separate handover is authoritative for recorded owner decisions; September source/population evidence is dated, not freshly revalidated here.

The audit adds a coherent entry point rather than rewriting historical plans or changing another owner's status ledger. The identified historical contradictions remain explicitly flagged above for future maintenance; no old test totals or deployment claims are promoted to present-day acceptance evidence.

Source evidence

All paths and one-based lines below refer to the pinned source revision. Tests were inspected by name/search where relevant in the initial 27 September pass; the dated verification follow-up in the service/client inventory records subsequent bounded checks and their coverage separately. Neither pass is live revocation acceptance.

Ref Exact source and meaning
E1 ApplicationUser.cs:17: inherited account, mapping, profile, groups, passkeys, links; :164 separate Identity role model.
E2 UserClaimsService.cs:24: scoped projection from Identity account only.
E3 InvestigatorReconciler.cs:79, :287–296; UserImporter.cs:575, :596: group copy and indefinite lockout; migration ledger preserves reasons at :215.
E4 InvestigatorMappingService.cs:243, :697, :753; ProjectManagementClaimSource.cs:35: bounded claim lookup, deactivation and binding.
E5 InvestigatorProvisioningController.cs:10: authenticated PM provisioning contract, no role payload.
E6 EffectiveApplicationGroups.cs:23; AuthorizationHandler.cs:69; SignalRAuthorizationHandler.cs:50: request groups plus PM roles.
E7 AuthorizationController.cs:143, :373–446, :509–561; IdentityAdmissionService.cs:7; AdmissionIntrospectionHandler.cs:43: actual admission and token-generation checks.
E8 ApplicationService.cs:54, :108–145: verified-ID resolution, profile/sign-in updates and create-if-missing helper.
E9 PermissionReportResolver.cs:22: report still passes only request groups.
E10 ProjectPermission.cs:58; StagePermission.cs:42; ProjectPermissionGroup.cs:33: separate resource-level grants.
E11 IdentityService.cs:22, :367; InvestigatorNameUpdatedHandler.cs:19; Investigator.cs:204; AdminApiController.cs:160: profile update chain and subject-ID mismatch.
E12 BffAuthController.cs:327, :620–627; SessionAuthenticationHandler.cs:235; BffSessionTokenValidator.cs:95: saved groups, token-only refresh, introspection validation.
E13 IdentitySessionRevocationService.cs:91; AdminApiController.cs:389, :468; AccountController.cs:189, :253–277: ordered native deletion and separate PM deactivation.
E14 auth.selectors.ts:187; UserImpersonationMiddleware.cs:58: claim-only administrator consumers.
E15 RepositoryCache.cs:43: two-second cache expiry, not a cross-service revocation protocol.
E16 BffAuthController.cs:444; bff-auth.provider.ts:107; admin.routes.ts:36; RuntimeFeatureFlagsController.cs:46, :84; ProjectStatisticsSubscriptionFilter.cs:24: BFF/UI exposure, direct administrator checks and saved subscription groups.