The contact is what the site authorises

A Power Pages site checks who someone is in one system and decides what they may see in another. The identity provider confirms the sign-in. The Dataverse contact record carries the access. Between them sits a small join of external identity rows, and almost every access problem you debug on that site lives in the join rather than the sign-in.

The chain runs one way. A sign-in resolves to an external identity row, that row points at a contact, web roles attach to the contact, table permissions attach to web roles, and page permissions reference the same roles. Once the session exists, nothing goes back to the provider to ask anything further. Dataverse decides everything after the first second.

So the first question on a portal access ticket is never about the login. Somebody signs in and sees an empty list, or signs in and sees another customer's orders, and both symptoms start in one place. Find the contact the session resolved to, read its web roles, then read the table permissions on those roles. If you have not worked through what Dataverse enforces, the portal will keep surprising you.

Open registration and invitation are different products

The registration model is the decision everything else inherits. With open registration, anyone who can authenticate at your provider can create a contact on your site. With invitation, the contact exists in Dataverse first, somebody sends a redemption code, and sign-in binds to a record your business already knows something about.

Open registration is cheap to operate and expensive to secure. The contact created at first sign-in has no account, no owner, and no relationship to anything you sell. Every table permission attached to the authenticated users role is readable by whoever filled in a form. Teams pick open registration for a knowledge base, then add a case list eight months later without revisiting what registration means.

Invitation costs staff time and buys you provenance. Somebody creates contacts, sends codes, and handles the person who lost theirs. The trade-off is real, and the direction of travel is not symmetric. Moving from invitation to open registration takes an afternoon. Moving from open to invitation means deciding what to do with a population of self-registered contacts whose origin nobody recorded, which is a project with a business owner rather than a configuration change.

The same person arrives twice

A customer signed up with a local account two years ago. Today they click the button for their employer's Entra ID tenant, because the password reset mail goes to a mailbox they no longer read. The site sees an issuer and subject it has never seen, finds no match, and creates a contact. One human, two contacts, two sets of cases.

Cleaning that up later costs more than it sounds. Merging contacts in Dataverse moves some related records and leaves others, web role assignments do not reliably follow, and anything built on the contact identifier in a flow or report now points at a record marked as merged. Repeat across a few hundred customers and reconciliation becomes its own workstream.

Decide at design time which providers you accept and what a second one means. Either it creates a second contact and you plan for the duplicates, or signing in with a new provider offers an explicit linking step where the person proves control of the existing account first. Silent linking is the option to avoid.

Email is an attribute and the subject is the key

Matching a new sign-in to an existing contact by email address is the failure everyone repeats, because at registration the address is the only value both sides hold. Email is a mutable attribute. People change their name and their address changes with it, companies migrate domains after an acquisition, addresses get recycled, and one shared mailbox can be read by four people in a week.

The durable key is the pair your provider hands you and never rewrites: the issuer and the subject identifier. Store both on the contact in their own columns, match on the pair at every sign-in, and let the email address become contact detail the person can edit without touching access. The case for durable business keys is the same case here.

Email matching also has a security shape. If a new registration binds itself to an existing contact because the addresses match, anyone who can receive mail at that address inherits that contact's orders and case history. Adding issuer and subject columns to a running site is recoverable work, captured at each person's next sign-in and backfilled over a renewal cycle. What you cannot recover is the origin of contacts created before you started.

Providers change and people change employer

A corporate customer moves from one identity tenant to another, and every subject identifier for their staff changes over the cutover weekend. If your contact holds exactly one issuer and one subject, that population is unmatched on Monday, and your support desk takes a hundred calls saying the site created a new blank account.

The pattern that survives this keeps identity references as history rather than a single value. Allow several external identity rows against one contact, add the new issuer and subject when a person re-links, and keep the retired row for the audit question about who signed in last March. Overwriting saves a column and loses your only evidence.

The mirror case is the person who changes employer and keeps the same consumer login. The subject is unchanged, your matching works perfectly, and it hands them the contact still attached to their old employer. Identity persisted and entitlement should not have. Entitlement belongs on the link between contact and account, and somebody has to be told when that link ends, the same gap behind deprovisioning that only knows one login domain.

Somebody has to manage the other users at that account

Most business to business portals end up with several contacts against one account, and the question nobody asks at design time is whether one of them administers the others. If the answer is yes, you are building delegated administration: a named person who can invite colleagues, deactivate the one who left, and assign web roles inside their own company.

If the answer is no, every joiner and leaver at every customer becomes a ticket for your team, forever. That running cost gets underestimated because it stays invisible until the site has enough customers to hurt. Delegated administration carries its own bill: a dedicated web role, permissions scoped to the account, and a rule about what an account administrator may not do to a colleague's records.

Account hierarchy is where the design gets sharp. Decide whether a contact at a parent company sees the child company's records, and write the answer down before anyone configures a table permission, because the scope you pick encodes it permanently. The same argument runs through a customer account hierarchy on any platform, and the answer is a business rule rather than a technical preference.

One wrong table permission is reachable by anyone who registers

Table permissions are evaluated for whoever holds the web role, and the authenticated users role is held by everyone who completed registration. Attach a read permission with global scope to that role and every person who signed up can read every row in the table. Nothing was compromised and no privilege was escalated. Somebody filled in a registration form.

The anonymous role widens it further, because a read permission granted there, combined with a list or a web API call the page does not filter, publishes the table to the internet. Scope is the control carrying the weight. Contact scope limits rows to the signed-in person, account scope to their company, parent scope to the relationship you configured, and global scope means the whole table. A portal security checklist should carry scope review as a standing item.

Open the site you already run and do two things this week. List every web role, find the ones serving as the authenticated and anonymous defaults, sort their table permissions by scope, and say out loud why anything with global scope on a customer-facing table is there. Then open a contact created in the last thirty days and check whether it carries an issuer and a subject identifier, or only an email address.