Where this stands in 2026
Partly supersededThe core idea still holds. With the default (standard) authorization model, relationship-based table permissions still shape every list query, and long permission chains can still hit the join limit. A few details have changed: Dataverse now allows up to 15 link-entities per FetchXML query, table permissions gained Self and Custom access types, and a preview "enhanced authorization" model moves permission checks into Dataverse itself.
Official docs for today’s approach
- Set table permissions in Power PagesAccess types, including Self and the preview Custom type.
- Join tables using FetchXML: limitationsThe current limit of 15 link-entities per query.
- Set column permissionsColumn-level security, which didn’t exist in 2020.
- Enable enhanced authorization (preview)Moves permission evaluation into Dataverse.
Written in 2020 for PowerApps Portals. See what things are called now.
Microsoft has since renamed the product and most of its parts. The text keeps the original terms.
| In this note | Today |
|---|---|
| PowerApps Portals | Power Pages |
| CDS (Common Data Service) | Dataverse |
| Entity | Table |
| Entity Permission | Table permission |
| Entity List | List |
Getting Started
My hope here is that some of the lessons I’ve learned while developing for PowerApps Portals since it launched as Dynamics Portals in 2016 will prove helpful to others.
The start of a PowerApps Portal design is extremely important, as several factors can determine whether the implementation is completed within scope. While look & feel (including accessibility) and core functionality are obvious focal points, the data model and how it impacts security may not be.
When it comes to Portal security, there will be instances when requirements are simple and little consideration is needed. Other times, complicated entity relationships will make for seemingly impossible requirements. Developers may need to consider a significant change to the data structure, or restrict which Web Roles can be granted in combination, to ensure the functionality works correctly.
There is already plenty of documentation explaining the high-level points of Portal security, so I don’t plan on covering them here.
To keep the terminology simple, any reference to a “user” means the Contact navigating the Portal.
The Read Entity Permission & Query Manipulation
Overview
From a thousand-foot view, any Entity Permission with the Read option selected impacts Retrieve and Retrieve Multiple calls, but not Read rights in Dynamics terms. For example, at the time of this writing, if you don’t grant Read permissions to an entity, users will still see the record’s name in lookup values on entities they do have access to – they just won’t be able to select a new value.
Recreated illustration: Example of a lookup value present when user does not have Read permissions: the lookup shows the related record name, but opening the lookup dialog returns a permissions error instead of records.
- Callout 1: The lookup still displays the related record’s name, even though the user has no Read permission on Account.
- Callout 2: Opening the lookup dialog to choose a different value fails: the list query is filtered by permissions and returns nothing the user can select.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
The Lookup record’s key is shown despite a lack of Read permissions
Read permissions can be either Global or relationship-based, with the latter utilizing Account, Contact or Parent-Permission relationships. Global Read takes a simple “if it exists, access is granted” approach that doesn’t impact anything beyond what you’d expect. The real fun comes when you dig deeper into how relationship-based permissions work.
Query Manipulation
Relationship-based permissions basically work by intercepting and manipulating any queries to CDS within the application where Enable Entity Permissions is set to True. While the application has had many updates since, there’s no reason to believe that the current methodology for query manipulation is much different from what can be found in the Dynamics Portals Source Code released in 2017. In this code, you can trace the specifics through the Adxstudio.Xrm.Security.CrmEntityPermissionsProvider class to see how these manipulations occur, though that level of detail is less important.
As the source code shows, the manipulation consists of injecting link-entity tags directly into the query. The specifics of the injection depend on the hierarchy of permissions granted to the user’s combination of Web Roles. This is very important to understand, as an inefficient security model can lead to a poor user experience when more than the allotted number of linked entities (typically 10) are injected. This impacts any list of records – Entity Lists, sub-grids and Lookup selection modals – by displaying an error message instead of actual data. In my experience, accessing records directly does not appear to have any issues.
Here are a few considerations to help ease development around this issue:
1. Links Wrapped in Or Conditions
One very important thing to keep in mind is that the permissions themselves are based on an or principle. Though it is possible to set up views that follow the logic of “show Opportunities where the Account is the user’s Company and the user was listed as the Primary Contact on the Opportunity’s Lead,” permissions can’t do the same, which would put any subsequent pages at risk if the user met only one of the conditions. This is usually only a concern if users share URLs, since it would be difficult for the average person to otherwise manipulate their way to a record that isn’t listed.
2. Use the Correct Relationship Option
When the root of the hierarchy is based on the Account option, it adds a filter condition for the relationship to the Company Name (parentcustomerid) from the user’s Contact record. See below for an example of the permission hierarchy “All Leads related to my Company,” which is a default permission titled “Contact Us Access” in most Portal installations, compared to the same permissions derived from Contact. By using the Account option, we eliminate an unnecessary link between the Contact and the Account. Sometimes that link is unavoidable, such as when the requirements are to replace the Customer field (Contact & Account) with a simple Account field, or when relationships to Account are changed to Many-to-Many.
Using the Account Relationship
<fetch mapping="logical" version="1.0" distinct="true" output-format="xml-platform">
<entity name="lead">
<attribute name="createdon" />
<attribute name="leadid" />
<order attribute="createdon" descending="false" />
<filter type="and">
<filter type="or" hint="union">
<condition attribute="customerid" operator="eq" value="a8a19cdd-88df-e311-b8e5-6c3be5a8b200" />
</filter>
</filter>
</entity>
</fetch>Using the Contact Relationship
<fetch mapping="logical" version="1.0" distinct="true" output-format="xml-platform">
<entity name="lead">
<attribute name="createdon" />
<attribute name="leadid" />
<order attribute="createdon" descending="false" />
<filter type="and">
<filter type="or" hint="union">
<condition entityname="generated_alias_contact_0" attribute="contactid" operator="eq" value="02b2b808-e0d9-ea11-a815-000d3a3349d4" />
</filter>
</filter>
<link-entity name="account" from="accountid" to="customerid" alias="generated_alias_account_0" link-type="outer">
<link-entity name="contact" from="parentcustomerid" to="accountid" alias="generated_alias_contact_0" link-type="outer" />
</link-entity>
</entity>
</fetch>3. Having Multiple Entry Points
Though I’ve typically seen this when self-referential relationships come into play (e.g., “All Opportunities for my Account or my Account’s child Accounts”), having multiple permissions that grant access to a record can exponentially increase the number of link-entity tags. This can very quickly hit the limit, and sometimes requires clever workarounds such as bypassing the multiple entry points. Using the previous example, one might add the Contacts from the Parent Account to the child Accounts as well, or add the Contacts directly to the Opportunities.
4. Multiple Permissions or Roles
If an Entity Permission is created and applied to multiple Web Roles, the user can be granted all Web Roles without issue. However, if you recreate the exact same Entity Permission as a new record and apply it to one of the Web Roles, the Portal does not check for this and will duplicate the link-entity tags. This is typically one of the easiest inefficiencies to correct.
Similarly, if a user has multiple Web Roles with different hierarchies to the same target entity, the combined link-entity tags may surpass the limit. There is not much that can be done about this, other than limiting the number of Web Roles users are given, possibly by creating hybrid roles instead of assigning multiple roles.
5. Watch for Unexpected Links
Many-to-Many relationships are the most difficult to work around, as they actually add two links for each connection. Combine this with self-referential relationships, and managing the number of links becomes even harder. Solutions here can be very similar to 3. Having Multiple Entry Points.
Similarly, there may be times when development skirts right up to the limit on link-entity tags, only for an outlying view that wasn’t taken into consideration to push it over, so all views in use must be considered. Additionally, a sub-grid or Lookup on a form with Show Related Records Only set to True will increment the number of tags, on top of those created by permissions and built into the view.
Wrapping Up
Hopefully the information here was helpful in understanding security considerations. If you have any specific questions or comments, feel free to reach out. Next up, I’ll be sharing a debugging process I use both to investigate issues and to show new developers the impact of their changes.
Restored in 2026 from the Internet Archive at its original address. Figures are recreated illustrations of lost screenshots; the text was lightly copy-edited.