==========================
Update 26.02.2025:
See important note on "Prefer": "return=representation"
below.
==========================
Briefly on the project
I'm developing an SPA with Power Pages for managing corporate supplier base. The Power Pages portal is completely stripped of the default dependencies. The MSAL library is authenticating a user with the corporate Azure tenant and Dataverse behind the Power Pages. All queries are done with a token obtained by the MSAL from the Dataverse scope.
This blog is mostly about this journey.
Telemetry implementation
I have come up with the custom telemetry logger that will enable to see statistic of the portal usage, queries sent, user errors logged, etc.
To simplify things, I have created a Dataverse table with two columns:
1) wb_datasuccess
- to log params of the successful requests in JSON
2) wb_dataerror
- to log user errors in JSON
Each query is accompanied by the simple POST request to add a record to the telemetry table that looks like this:
// rev 26.02.2025
// make sure that header "Prefer": "return=representation" is not present
// or set to "Prefer": "null", otherwise the POST will return 403 with comments:
// user with id ... does not have ReadAccess right(s) for record
// with id ... of entity wb Telemetry
function recordTelemetryData(type, dataObj, token) {
try {
fetch(
`${baseUrl}/api/data/v9.2/wb_telemetries`
, {
method: "POST",
headers: {
"Accept": "application/json",
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Authorization": "Bearer " + token,
"Content-Type": "application/json; charset=utf-8"
},
body: JSON.stringify({
[`wb_data${type}`]: dataObj, // has all required params
})
}
)
} catch (err) {
console.dir(err)
}
}
Query call with telemetry enabled:
queryWithMSALToken(
{
queryStr: `WhoAmI`
, doTelemetry: true // invokes the telemetry call
, false
}
, "User login, WhoAmI query" // specifies the type of request
)
The call of the recordTelemetryData
:
async function queryWithMSALToken(queryObj, queryTitle = "", forceRefresh = true, providedToken = '') {
// other code here is removed
if (doTelemetry) recordTelemetryData(
'success'
, JSON.stringify({
queryTitle,
fetchUrl: url,
fetchParams,
responseToReturn,
browser: navigator.userAgent.indexOf("Edg") !== -1 ? "Edge" : "Chrome"
})
, token
)
// other code here is removed
}
The telemetry call is made on behalf of the logged in user, so I would need to limit the user's access to his data only. Ideally, the user shall not be able to get any data from the wb_telemetry
table.
How can we do this?
Security Roles setup and results
Microsoft provides explanation of Security roles and privileges here but generally speaking all documentation on Power Apps is cumbersome and lacks important details.
Let's see options/settings and results available to us with different setup.
First check what admin is getting
Admin by default has access to all data.
Get data
Open new private window in your browser -> type in the URL bar https://your-env.crm.dynamics.com/
-> authenticate with the user that has Admin role-> type in the URL bar https://your-env.api.crm.dynamics.com/api/data/v9.2/wb_telemetries?$select=wb_telemetryid
.
Response
{
"@odata.context": "https://your-env.api.crm.dynamics.com/api/data/v9.2/$metadata#wb_telemetries(wb_telemetryid)",
"value": [
{
"@odata.etag": "W/\"9289665\"",
"wb_telemetryid": "1cabe573-85e8-ef11-be20-000d3aaccaf0"
},
{
"@odata.etag": "W/\"9289199\"",
"wb_telemetryid": "1690f9ba-7ae8-ef11-be20-6045bd950c14"
},
{
"@odata.etag": "W/\"9289228\"",
"wb_telemetryid": "59a6c775-7ee8-ef11-be20-7c1e5273f892"
}
]
}
We have in total 3 records, each was POSTed by a different user.
Create new Security Role for a basic user
For all basic users I tend to create a new Security Role from scratch, clear all permissions (see my other post on how to do this with one single fetch
) and add only those permissions, relevant for my scenario.
I have created a role aaa Basic User
. My basic user is assigned to this role only.
Here (post to be made shortly) I will show a minimal set of permission you would need for such role.
Check what the basic user is getting
Setup 1
- Member's privilege inheritance -> Direct User (Basic) access level and Team privileges
- Create -> Organization
- Read -> User
Get data
Open new private window in your browser -> type in the URL bar https://your-env.crm.dynamics.com/
-> authenticate as the user that has role aaa Basic User
-> type in the URL bar https://your-env.api.crm.dynamics.com/api/data/v9.2/wb_telemetries?$select=wb_telemetryid
.
Response
{
"@odata.context": "https://your-env.api.crm.dynamics.com/api/data/v9.2/$metadata#wb_telemetries(wb_telemetryid)",
"value": [
{
"@odata.etag": "W/\"9289199\"",
"wb_telemetryid": "1690f9ba-7ae8-ef11-be20-6045bd950c14"
}
]
}
We get only 1 record that was POSTed by this authenticated basic user. In fact there are 3 in total as we saw above.
Setup 2 - the one I will use
- Member's privilege inheritance -> Team privileges only
- Create -> Organization
- Read -> User
Get data
Do as stated in the Get data for Setup 1 above.
Response
{
"@odata.context": "https://your-env.api.crm.dynamics.com/api/data/v9.2/$metadata#wb_telemetries",
"value": []
}
No data is returned as a result.
Conclusion
With Member's privilege inheritance
set to Team privileges only
, Read
privileges set to User
and Create
privileges set to Organization
or Business Unit
the basic user:
1) Can POST a request to record his telemetry data.
2) Cannot GET any data from this table with any call.
3) Make sure not to include header "Prefer": "return=representation"
:
- if "Prefer": "return=representation
is present, the POST will return the created record details
- if "Prefer": "null"
or not included, the POST will return 204 No Content
- more on this here
That's exactly the right behavior I need and use in my production scenario.
Top comments (0)