Skip to content
Power Pages

Power Pages Security Starts With Table Permissions

A Power Pages site is anonymous by default. Everything a visitor can see or do is granted explicitly, and the grant model is the part people get wrong.

Rajiv KumarMay 14, 20266 min read

Power Pages puts Dataverse on the public internet. Table permissions and web roles are the entire access model — not page visibility, not hidden links — and understanding how a permission is scoped is what separates a safe site from an exposed one.

Power Pages is the only part of the Power Platform that puts Dataverse in front of people who have not signed in to anything. That single fact should change how you approach it. In a canvas or model-driven app the worst case is an internal user seeing internal data they should not. In Power Pages the worst case is the internet seeing it.

The good news is that the platform starts closed. A newly provisioned site can read nothing. The risk is not that the default is wrong; it is that the model for opening it up has four moving parts, and getting one of them wrong opens more than intended.

The four parts of the access model

Access to a Dataverse row from a Power Pages site requires all of the following to line up:

  1. A table permission — which table, which privileges (create, read, write, delete, append, append to), and which scope.
  2. A web role — the table permission must be associated with at least one web role, or it grants nothing.
  3. A contact in that role — the signed-in visitor is a Dataverse contact record, and web roles are assigned to contacts. The Anonymous Users and Authenticated Users roles are the two special cases.
  4. Something that reads the data — a list, a form, a Liquid tag or the Web API, each of which has its own configuration.

Break any link and access is denied. That is why "I granted the permission and it still does not work" is the single most common Power Pages question, and why the answer is almost always the second item.

Scope is the part that matters

A table permission's scope decides which rows the privilege applies to. This is the field people set carelessly.

ScopeGrants access toTypical use
GlobalEvery row in the tableGenuinely public reference data — a product catalogue, a list of locations
ContactRows related to the signed-in contact through a specified relationship"My orders", "my cases"
AccountRows related to the contact's parent accountA customer portal where colleagues at one company share visibility
SelfThe contact's own recordA profile page
ParentRows related to a row the visitor can already access, via a parent table permissionOrder lines under an order the visitor can see

Global read on a table containing customer records is the mistake that makes the news. It is one dropdown away from Contact scope, it looks identical in the maker experience, and it will not fail any test you write against your own account — because your own account can see the rows either way.

Parent permissions and why they exist

Consider orders and order lines. A visitor should see the lines belonging to their own orders, and no others. There is no direct relationship from an order line to a contact, so Contact scope cannot express it.

The answer is two permissions:

  • On Order: scope Contact, through the relationship that links an order to its customer contact. Privileges: read.
  • On Order Line: scope Parent, with the order permission as its parent and the order-to-line relationship selected. Privileges: read.

The child permission grants nothing on its own. It says "for any row the parent permission already allows, extend these privileges down this relationship". That composition is what keeps the rule correct when a visitor's set of orders changes.

Web roles, and the two you did not create

Every site has an Anonymous Users role and an Authenticated Users role. Table permissions attached to Anonymous Users apply to visitors who have not signed in. That is occasionally correct — a public product catalogue — and catastrophic by accident.

Before a site goes live, list every table permission associated with the Anonymous Users role and justify each one out loud. It is a five-minute exercise and it is the highest-value review in the whole project.

Column permissions and the Web API

Table permissions work at row granularity. If a table contains a column that must not reach the browser even for rows the visitor can read — an internal note, a risk score, a cost — a table permission will not hide it. Column permissions do, and they apply to forms, lists and the Web API alike.

The Web API deserves particular care. It respects table and column permissions, but it also has to be enabled per table through site settings, and enabling it makes those rows queryable by any script running on the page. Enable it for the tables that need it, not as a convenience.

Liquid does not bypass security, with one exception

Liquid tags such as fetchxml and the entity list objects run under the visitor's permissions. What they return respects table permissions, which is the behaviour you want.

The exception is anything you cache or render server-side into a page that is then served to more than one visitor. If a page is cached, the first visitor's data can be shown to the next. Treat per-visitor data as uncacheable, and keep it out of anything that renders once and serves many.

Common mistakes

  • Granting a permission and forgetting the web role. An unassociated table permission does nothing. This is the first thing to check when access is denied.
  • Using Global scope for convenience during development. It works, so it survives to production. Start at the narrowest scope that fails, then widen deliberately.
  • Relying on hidden pages. Removing a link from the navigation does not remove the page, and page-level rules are not a substitute for table permissions.
  • Forgetting Append and Append To. Creating a row that references another row requires Append on the child and Append To on the parent. A create that fails with a vague error is usually this.
  • Testing only as an administrator. Test as a contact with exactly one web role, in a private window. Your own session tells you nothing about what a visitor sees.

Troubleshooting

SymptomCause to check first
List renders empty for everyoneTable permission exists but is not associated with a web role
List renders for you, empty for a test contactScope is Contact but the relationship on the permission is not the one linking the row to the contact
Form saves fail silentlyMissing Write, or missing Append/Append To on a lookup being set
Anonymous visitors see more than expectedA permission is attached to the Anonymous Users role, directly or through a role a contact inherits
Web API returns 403 for a table you grantedThe Web API is not enabled for that table in site settings
Changes do not appear on the siteConfiguration is cached; restart or clear the site cache after permission changes

Frequently asked questions

Do table permissions replace Dataverse security roles?

For portal visitors, effectively yes: a visitor is not a licensed Dataverse user, and their access is determined by table permissions and web roles. Dataverse security roles still govern internal users working in model-driven apps over the same data.

Can one contact hold several web roles?

Yes, and permissions are additive. Two roles granting different scopes on the same table produce the union, not the intersection. This is worth remembering before adding "just one more role" to solve an access problem.

How do I let a visitor see rows belonging to their whole company?

Account scope, with the contact's parent account set. The relationship you choose on the permission decides how a row is linked to that account.

Is a page rule enough to protect a page?

Page permissions control navigation and page access, not data access. A page rule with no table permission behind it protects the route, not the rows. Always secure the data first.

The review that is worth doing

Before any Power Pages site goes live, produce a single table with one row per table permission: table, privileges, scope, relationship, and the web roles it is associated with. Read it end to end. Every permission that is Global, and every permission attached to Anonymous Users, needs a sentence of justification. If a sentence does not come easily, the permission is wrong.

Share