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
- Information flow
- Lifecycle walkthrough
- Implementation gaps and their consequences
- Should Identity persist SyRF administrator status?
- Are application-role claims needed in tokens at all?
- Decisions and bounded next steps
- Documentation map and currentness
- Source evidence
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¶
- 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.
- 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.
- Compare old/new decisions without changing enforcement. Resolve differences, including impersonation and stored-only/claim-only users, before a separately approved switch.
- 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.
- 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¶
- 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.
- 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.
- 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.
- 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.
- 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. |