Dataverse Connection Setup
Your portal talks to Dataverse as an application in its own right, not as the visitor who happens to be browsing it. That takes two pieces: an app registration in Microsoft Entra ID that owns the credentials, and an application user in your Dataverse environment that gives those credentials a identity and a security role. This page walks through both, and through the access the portal actually needs.
This is not the sign-in app
This registration is the portal's own service identity — it reads and writes data on the portal's behalf, and its credentials go under
D365:ClientId/D365:ClientSecret. A second, separate registration handles signing users in with Microsoft. Keep them apart: this one needs no redirect URI and no delegated permissions, but it holds far more power inside your environment. See Entra ID Sign-In Setup
1. Register the Application
Create the registration in the Microsoft Entra admin center, in the tenant that owns your Dataverse environment.
- Browse to Entra ID > App registrations and select New registration.
- Enter a Name that identifies the portal and the environment, for example
Contoso Portal — Dataverse (Production). Nobody sees this on a consent screen, so favour clarity for whoever audits it later. - Under Supported account types, choose Single tenant only. This identity never leaves your organization.
- Leave Redirect URI empty. No user is ever redirected here — the portal authenticates with the client credentials flow, machine to machine, with no browser involved.
- Select Register.
- On the Overview page, copy the Application (client) ID. That value becomes
D365:ClientId, and you need it again in step 4 to create the application user.
2. Create a Client Secret
The secret is the portal's only credential, so treat it as the key to your environment's data.
- Open Certificates & secrets under Manage and select the Client secrets tab.
- Select New client secret, describe it by environment (for example
portal-production), and choose an expiry. - Copy the Value column immediately — not the Secret ID. It is shown only once, and it becomes
D365:ClientSecret.
Important
When the secret expires the portal stops reaching Dataverse entirely — not just for one feature, but for every request — so put the expiry date somewhere you will see it. For production, a certificate credential avoids the expiry cliff, and
AuthenticationType.Certificateaccepts one in place of the secret.
3. API Permissions: None Required
This step is deliberately empty, and that surprises people. Plenty of older guidance tells you to add the Dynamics CRM → user_impersonation delegated permission. That permission is for applications acting on behalf of a signed-in user. The portal does not do that — it authenticates as itself, and its access comes entirely from the Dataverse application user you create in the next step.
Note
Microsoft's own server-to-server guidance says the same: when you connect as an app, you don't grant Access Dynamics 365 as organization users, because the application is bound to a specific user account instead. Adding it does not break anything, but it grants a capability the portal never exercises — leave it off.
4. Create the Application User in Dataverse
The app registration alone cannot touch data. Dataverse needs a user record bound to it, which is what turns the client id into an identity your environment recognizes.
- Sign in to the Power Platform admin center.
- Select Manage in the navigation pane, then Environments, then your environment.
- Select Settings > Users + permissions > Application users.
- Select + New app user.
- Select + Add an app and find your registration by name or by the client id from step 1, then select Add. Only Entra app registrations appear in this list — enterprise applications do not.
- Choose a Business Unit. The root business unit is the usual answer; a child unit narrows what the portal can reach even before security roles apply.
- Select the edit icon next to Security roles and assign the roles the portal needs — the next step covers which ones, and why there is usually more than one.
- Select Create.
Tip
An application user consumes no paid license, and you can only have one per Entra app registration per environment. Use a separate registration for each environment so that revoking development access never touches production.
5. Give the Application User a Security Role
The application user can do nothing at all until it holds a security role. There are two routes, and which one is right depends on a single question: would you mind the portal's credential reaching data the portal itself never shows?
The quick route: System Administrator
Assigning System Administrator works, and where the environment holds nothing you would mind exposing — a demo, a prototype, a sandbox whose data is already public — it is a reasonable choice rather than a mistake. Just make it knowingly. The portal is internet-facing, and this credential is what decides how far an attacker gets if the secret ever leaks. System Administrator means every table in the environment, including every one the portal was never built to show.
The scoped route: our role, plus one of your own
Anywhere real data lives, assign two roles. Security roles are additive, so the application user ends up with the union of both — and keeping them separate is what lets you review your own grants without re-reading ours.
- Assign Power Portals Pro Application User, which ships in the managed solution. It grants exactly what the framework needs to run, and nothing else. Then add the application user to the column security profile of the same name, which ships beside it: a security role can never grant a secured column, and Power Pages secures two contact columns the framework writes as itself,
adx_identity_logonenabledandadx_identity_passwordhash. Without the profile, existing users sign in normally and the first registration fails with does not have create permissions to a Login Enabled secured field. Do the same for every application user the portal connects as, including a managed identity's in a deployed app. - Create a second, custom role for your own tables. Dataverse would let you add them to the shipped role instead, but keep them apart: the shipped role is ours, a solution upgrade can reapply it, and grants mixed into it stop being reviewable without reading ours alongside them. With the MCP server,
create_security_rolemakes it andassign_application_user_rolegives it to the application user. - Add your tables to that role. If you use the MCP server this needs no trips through the security-role form —
describe_role_table_privilegesshows what a role currently grants on a table and at what depth, andadd_table_privileges_to_rolegrants whatever is missing. Granting never takes anything away:remove_table_privileges_from_roledoes that, removing privileges or lowering their depth, anddelete_security_roledeletes a role once nobody holds it. None of them touch a managed role, the shipped one included, so the grants always land in yours.
Important
On enhanced authorization, assign
Power Portals Pro Application User - Enhanced Authorizationinstead. It ships in the Power Portals Pro Enhanced Authorization solution and is a superset of the role above — every privilege that one grants, at the same depth, plus the ones for reading web roles and maintaining Web Role Configuration. It replaces the shipped role rather than joining it, so assigning both is harmless but pointless. Without it the portal cannot read web roles: new users are given none, and the portal shows them nothing without reporting an error. The column security profile is still needed either way — no security role, however broad, can grant a secured column.
What the shipped role already covers
Power Portals Pro Application User is derived from the calls the framework makes as itself, so it tracks what the framework actually needs rather than what seemed prudent at the time:
| What the portal does | Access the role needs |
|---|---|
| Registers and signs in portal users | Create, Read and Write on contact, and Create, Read, Write and Delete on adx_externalidentity, which stores the link between a portal user and an external login. |
| Binds grids to Dataverse views and formats values | Read on savedquery, and Read on organization, usersettings and transactioncurrency so dates, numbers and currency format correctly. |
| Sends confirmation and password-reset email | Create, Read and Write on email, and Read on the queue your configured sender address resolves to. |
| Portal roles | Create, Read, Write and Delete on ppp_portalrole and ppp_portalroleconfiguration, and Append/AppendTo on contact so roles can be assigned. The portal itself only reads these at runtime; the write privileges are there so that RoleManager<PortalRole> works for hosts that manage roles in code, and so the MCP server's portal-role tools work in a project that connects as the application user. |
| Validates the website license | Permission to run the ppp_ProWebsiteLicenseCheck action, and Read on ppp_powerportalsprowebsite — the action runs in the caller's context, so it reads the website records as the application user. Missing either one makes every licensed endpoint return 402, which reads like an expired licence rather than a missing privilege. |
| Dataverse-native security | Create, Read and Write on systemuser, Read on businessunit and role, Create and Read on powerpagesusermapping, Read on powerpagesite, and the Act on Behalf of Another User privilege that impersonation rests on. |
What it deliberately does not cover is your own tables — no role we ship can know what they are, which is what the custom role above is for. Expect Create, Read, Write and Delete on every table your portal exposes, at the scope you intend visitors to reach. That is the bulk of the work, and it is entirely specific to your application.
Important
Impersonation narrows scope. It never widens it. Under Dataverse-native security the portal stamps each request with the signed-in visitor's identity, and Dataverse applies the intersection of that visitor's privileges and the application user's — an operation is allowed only if both hold the privilege for it. So the application user's role is a ceiling on what any visitor can do. A visitor whose own role grants Read on a table still reads nothing if the application user lacks it, and adding a table to your custom role never grants a visitor more than their own role already allowed.
6. Store the Connection Settings
The portal reads these from configuration under the D365 section. Keep them in user secrets during development so they never reach source control:
{
"D365": {
"Url": "https://yourorg.crm.dynamics.com",
"ClientId": "<application (client) id>",
"ClientSecret": "<client secret value>",
"EmailSenderEmailAddress": "portal@yourcompany.com"
}
}
EmailSenderEmailAddress is the address the portal's outbound email is sent from. It must match a queue or an enabled systemuser in the environment — a queue is the usual choice, since it does not tie portal mail to a person who might later leave.
Creating the sender queue
Start in Exchange Online: create a shared mailbox for the address portal mail should come from — no-reply@yourcompany.com is the usual choice. A shared mailbox needs no licence. Then, in the maker portal, Advanced settings → Business Management → Queues → New, give the queue that address, and save; Dataverse creates a mailbox record for it against the environment's Exchange Online profile. A queue is the right answer for a no-reply sender, and it beats pointing the portal at a colleague's account — whose name would otherwise sit on every automated message the portal sends, and whose departure would stop it sending at all.
Important
The mailbox still has to be approved and enabled. Open the queue's mailbox record and run Approve Email — which only an Office 365 Global Administrator or Exchange Administrator may do — then Test & Enable Mailbox. Until that has passed, Dataverse accepts everything the portal sends and delivers none of it. No error reaches the portal, so this looks exactly like working email right up until somebody reports never receiving a password-reset message. The MCP server's
create_email_queuedoes the queue, the approval and the test in one call, andcheck_email_senderexplains a sender that is not delivering.
Tip
In hosted environments, supply the same keys as environment variables or from a secret store, using a double underscore in place of the colon —
D365__ClientId,D365__Secret— because the colon separator is not portable across platforms.
Tip
Once deployed to Azure you can drop
ClientSecretaltogether. SetD365:ManagedIdentityIdto a user-assigned managed identity’s client id and the portal authenticates as that identity instead — no secret, and so nothing to expire. It needs its own application user, created from that client id exactly as in step 4. For an environment in another tenant, setD365:TenantIdas well and the identity becomes the credential of the app registration inD365:ClientIdinstead. See Azure deployment.
7. Wire Up the Connection
The server project configures ConnectionOptions when it registers the framework:
builder.Services.AddPowerPortalsProWebServer()
.Configure<ConnectionOptions>(options =>
{
options.AuthenticationType = AuthenticationType.ClientSecret;
options.ServiceUri = new Uri(builder.Configuration.GetRequiredValue("D365:Url"));
options.ClientId = builder.Configuration.GetRequiredValue("D365:ClientId");
options.ClientSecret = builder.Configuration.GetRequiredValue("D365:ClientSecret");
});
The project template writes this for you, so in a generated project there is nothing to add here — supply the three configuration values and the connection works. Start the portal and browse to any page backed by Dataverse data to confirm it.
Troubleshooting
- Labels show as raw keys, a “Some content is unavailable” notice appears, and no data loads. The portal could not sign in to Dataverse, and it still starts so that the rest of it can say so. The log gives the reason on one line starting Could not connect to Dataverse, with the Entra error translated:
AADSTS700016means the client ID does not exist in the environment's tenant — often an old ID left in user secrets after an app registration moved tenants —AADSTS7000222an expired secret, andAADSTS7000215a wrong one./healthanswers503 Unhealthyuntil it is fixed; after fixing the setting, restart the portal. - The caller is not a valid Dataverse user, or authentication fails outright. The app registration exists but no application user is bound to it in this environment. Application users are per-environment — creating one in development does nothing for production.
- Authentication fails with an invalid client secret. Usually the Secret ID was copied instead of the secret's Value, or the secret has expired. Create a new one and update configuration.
- A principal is missing a privilege. The application user's role is missing access to the table named in the error. Dataverse caches privileges per server node, so allow a minute or two after changing a role before concluding the change didn't help.
- Every data endpoint returns 402. The website license check is failing. Confirm the managed solution is installed, that a Portal Website record holds your license key, and that the application user's role can run
ppp_ProWebsiteLicenseCheck. - Email fails with “Unable to find an email sender with the email address”.
EmailSenderEmailAddressdoes not match any queue or enabled system user in the environment. - Email sends without an error but never arrives. The sender’s mailbox has not passed Test & Enable. Most often its address has no shared mailbox in Exchange Online — the mailbox’s alert then mentions validating the mailbox configuration in the remote server. Create the shared mailbox, then run Test & Enable again; the MCP server’s
check_email_sendershows the mailbox’s state and alerts.
