SystemUser Sign-In
Power Portals Pro recognizes signed-in Dataverse systemuser records as portal users — alongside the default contact-backed users. No configuration is required: when a user signs in via Microsoft authentication, the portal first looks for a matching systemuser; if one exists, the session runs as that systemuser and Dataverse enforces security via impersonation. If no matching systemuser is found, sign-in falls back to the contact path. The same portal can serve internal Dataverse-licensed staff and external customer contacts at the same time.
When this matters
The systemuser-backed flow is useful when:
- The portal serves staff — employees, support agents, partners with Dataverse licenses — who are already provisioned as systemusers.
- You want Dataverse security roles to be the single source of truth for what those users can see and do.
- You don't want to duplicate identity by maintaining a parallel
contactrecord for every staff user.
Microsoft authentication only
Only the Microsoft / Entra ID external provider can resolve to a systemuser; users are matched against
systemuser.azureactivedirectoryobjectid, and any systemuser withisdisabled = trueis blocked. Local username/password and other external providers (Google, etc.) always sign in as a contact.
How it works
On a Microsoft sign-in, the portal:
- Looks up the authenticated Azure AD Object ID against
systemuser.azureactivedirectoryobjectid. If an active record matches, the session is bound to that systemuser. - Otherwise, falls back to the contact-backed flow (matching the external login through
adx_externalidentity). - For systemuser sessions,
ServiceClient.CallerIdis set to thesystemuseridso every Dataverse call executes as the signed-in user — with their security roles, business-unit scope, and row-level access. Portal-sideITablePermissionHandler/ITableRecordPermissionHandlerinstances are bypassed for the request. - Grids, forms, and buttons reflect the user's real Dataverse privileges — computed from
RetrieveUserPrivilegesRequestand cached per user. Contact sessions continue to flow through the registered handlers as before.
Security model
For systemuser-backed sessions, because CallerId impersonation delegates enforcement to Dataverse:
- Record- and column-level security comes from the signed-in user's Dataverse security role(s) — not from portal code.
- Field-level security, hierarchical ownership, and business-unit scoping all apply automatically.
- Portal-side permission handlers (
ITablePermissionHandler/ITableRecordPermissionHandler) are bypassed for the request. They continue to run as usual for contact-backed sessions in the same portal. - The user's Dataverse security roles are projected onto the principal as
ClaimTypes.Roleclaims, so existing[Authorize(Roles = "…")]attributes andUser.IsInRole(...)checks work against them without an extra Dataverse round-trip per request. Claims refresh on principal validation, so role assignments made in Dataverse pick up within the security-stamp interval (~10 minutes) without forcing a re-sign-in.
Admins: testing as a contact
Portal admins typically need to verify the contact-side experience — does the homepage render correctly for a logged-in customer? does this case correctly hide that toolbar button? — without losing their ability to perform admin actions. Power Portals Pro supports this directly: an admin can keep one Microsoft sign-in linked to both a systemuser (their internal account) and a contact (a test record, or their own customer-side record), and flip between the two without signing out.
- Chooser on sign-in. When a Microsoft sign-in resolves to more than one identity, the post-callback page shows a chooser listing each candidate (kind + display name + email). Picking one signs the user in as that identity for the rest of the session.
- Remembered pick. The chooser writes a small per-provider-key cookie so subsequent sign-ins skip the prompt and go straight to the last-picked identity. The remembered pick updates whenever the user switches in-session, so the next ambiguous sign-in honors the new preference.
- In-session switch. When the signed-in user has a sibling identity, a Switch to contact / Switch to systemuser entry appears in the profile menu. It posts to
/api/auth/switch-identity, which re-issues the auth cookie for the alt identity — no fresh OAuth round-trip required. The endpoint reads the alt-identity id from the principal (never from the request body) so a tampered click can't sign the user in as someone else.
Mixed audiences: design your data accordingly
A single portal can serve both systemuser and contact users in the same deployment. If your customizations include FetchXML filters that scope rows by
adx_contactid == currentUserId(a common pattern in contact-only portals), those filters won't make sense when the signed-in user is a systemuser. Branch in your customization code on the user kind so each audience sees the data appropriate to it.
Privilege caches
The environment-wide privilege metadata map is loaded once at application startup and cached indefinitely; restart the application (or call IPrivilegeMetadataCache.InvalidateAsync) to pick up new tables or privilege-schema changes. Each signed-in systemuser's effective privileges are cached for 24 hours and can be evicted with IUserPrivilegeCache.InvalidateAsync(userId).
