Content security policy
Content Security Policy (CSP) is currently supported in model-driven and canvas Power Apps. Admins can control whether the CSP header is sent and, to an extent, what it contains. The settings are at the environment level, which means it would be applied to all apps in the environment once turned on.
Each component of the CSP header value controls the assets that can be downloaded and is described in more detail on the Mozilla Developer Network (MDN). The default values are as follows:
Directive | Default value | Customizable |
---|---|---|
script-src | * 'unsafe-inline' 'unsafe-eval' |
No |
worker-src | 'self' blob: |
No |
style-src | * 'unsafe-inline' |
No |
font-src | * data: |
No |
frame-ancestors | 'self' https://*.powerapps.com |
Yes |
This results in a default CSP of script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors 'self' https://*.powerapps.com;
. In our roadmap, we have the ability to modify currently noncustomizable headers.
Prerequisites
- For Dynamics 365 customer engagement apps and other model-driven apps, CSP is only available in online environments and in organizations with Dynamics 365 customer engagement (on-premises), version 9.1 or later version.
Configuring CSP
CSP can be toggled and configured through the Power Platform admin center. It's important to enable on a dev/test environment first since enabling CSP could start blocking scenarios if the policy is violated. We also support a "report-only mode" to allow for easier ramp-up in production.
To configure CSP, navigate to the Power Platform admin center -> Environments -> Settings -> Privacy + Security. The following image shows the default state of the settings:
Reporting
The "Enable reporting" toggle controls whether model-driven and canvas apps send violation reports. Enabling it requires an endpoint to be specified. Violation reports are sent to this endpoint regardless of whether CSP is enforced or not (using report-only mode if CSP isn't enforced). For more information, see reporting documentation.
Enforcement
Enforcement of CSP is controlled independently for model-driven and canvas apps to provide granular control over policies. Use the model-driven/canvas pivot to modify the intended app type.
The "Enforce content security policy" toggle turns on the default policy for enforcement for the given app type. Turning on this toggle changes the behavior of apps in this environment to adhere to the policy. Therefore, the suggested enablement flow would be:
- Enforce on a dev/test environment.
- Enable report-only mode in production.
- Enforce in production once no violations are reported.
Configure directives
This section allows you to control individual directives within the policy. Currently, only frame-ancestors
can be customized.
Leaving the default directive toggled on uses the default value specified in the table shown earlier in this article. Turning off the toggle allows admins to specify custom values for the directive and append them to the default value. The example below sets custom values for frame-ancestors
. The directive would be set to frame-ancestors: 'self' https://*.powerapps.com https://www.foo.com https://www.bar.com
in this example, meaning the app could be hosted in the same origin, https://*.powerapps.com
, https://www.foo.com
and https://www.bar.com
, but not in other origins. Use the Add button to add entries to the list and the delete icon to remove them.
Common configurations
For Microsoft Teams integration using the Dynamics 365 app, add the following to frame-ancestors
:
https://teams.microsoft.com/
https://teams.cloud.microsoft/
https://msteamstabintegration.dynamics.com/
For the Dynamics 365 App for Outlook, add the following to frame-ancestors
:
- Your Outlook Web App homepage origin
https://outlook.office.com
https://outlook.office365.com
For embedding Power Apps in Power BI reports, add the following to frame-ancestors
:
https://app.powerbi.com
https://ms-pbi.pbi.microsoft.com
Important considerations
Turning off the default directive and saving with an empty list turns off the directive completely and doesn't send it as part of the CSP response header.
Examples
Let's take a look at a couple examples of CSP configuration:
Example 1
In the example:
- Reporting is turned off.
- Model-driven enforcement is enabled.
frame-ancestors
is customized tohttps://www.foo.com
andhttps://www.bar.com
- Canvas enforcement is disabled.
The effective headers would be:
- Model-driven apps:
Content-Security-Policy: script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors https://www.foo.com https://www.bar.com;
- Canvas apps: CSP header wouldn't be sent.
Example 2
In the example:
- Reporting is turned on.
- Reporting endpoint is set to
https://www.mysite.com/myreportingendpoint
- Reporting endpoint is set to
- Model-driven enforcement is enabled.
frame-ancestors
is kept as default
- Canvas enforcement is disabled.
frame-ancestors
is customized tohttps://www.baz.com
The effective CSP values would be:
- Model-driven apps:
Content-Security-Policy: script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors 'self' https://*.powerapps.com; report-uri https://www.mysite.com/myreportingendpoint;
- Canvas apps:
Content-Security-Policy-Report-Only: script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors https://www.baz.com; report-uri https://www.mysite.com/myreportingendpoint;
Organization settings
CSP can be configured without using the UI by modifying the following organization settings directly:
IsContentSecurityPolicyEnabled controls whether the Content-Security-Policy header is sent in model-driven apps.
ContentSecurityPolicyConfiguration controls the value of the frame-ancestors portion (as seen above, it's set to
'self'
ifContentSecurityPolicyConfiguration
is not set). This setting is represented by a JSON object with the following structure –{ "Frame-Ancestor": { "sources": [ { "source": "foo" }, { "source": "bar" } ] } }
. This would translate intoscript-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors 'foo' 'bar';
- (From MDN) The HTTP Content-Security-Policy (CSP) frame-ancestors directive specifies valid parents that may embed a page using
<frame>
,<iframe>
,<object>
,<embed>
, or<applet>
.
- (From MDN) The HTTP Content-Security-Policy (CSP) frame-ancestors directive specifies valid parents that may embed a page using
IsContentSecurityPolicyEnabledForCanvas controls whether the Content-Security-Policy header is sent in canvas apps.
ContentSecurityPolicyConfigurationForCanvas controls the policy for canvas using the same process described in
ContentSecurityPolicyConfiguration
above.ContentSecurityPolicyReportUri controls whether reporting should be used. This setting is used by both model-driven and canvas apps. A valid string sends violation reports to the specified endpoint, using report-only mode if
IsContentSecurityPolicyEnabled
/IsContentSecurityPolicyEnabledForCanvas
is turned off. An empty string disables reporting. For more information, see reporting documentation.
Configuring CSP without UI
Especially for environments not in the Power Platform admin center such as on-premises configurations, admins may want to configure CSP using scripts to directly modify settings.
Enabling CSP without UI
Steps:
- Open browser dev tools while using the model-driven app as a user with organization entity update privileges (System Administrator is a good option).
- Paste and execute the below script into the console.
- To enable CSP, pass the default configuration -
enableFrameAncestors(["'self'"])
- As an example of enabling other origins to embed the app -
enableFrameAncestors(["*.powerapps.com", "'self'", "abcxyz"])
async function enableFrameAncestors(sources) {
const baseUrl = Xrm.Utility.getGlobalContext().getClientUrl();
if (!Array.isArray(sources) || sources.some(s => typeof s !== 'string')) {
throw new Error('sources must be a string array');
}
const orgResponse = await fetch(`${baseUrl}/api/data/v9.1/organizations`);
if (!orgResponse.ok) throw new Error('Failed to retrieve org info');
const orgs = await orgResponse.json();
const { organizationid, contentsecuritypolicyconfiguration, iscontentsecuritypolicyenabled } = orgs.value[0];
console.log(`Organization Id: ${organizationid}`);
console.log(`CSP Enabled?: ${iscontentsecuritypolicyenabled}`);
console.log(`CSP Config: ${contentsecuritypolicyconfiguration}`);
const orgProperty = prop => `${baseUrl}/api/data/v9.1/organizations(${organizationid})/${prop}`;
console.log('Updating CSP configuration...')
const config = {
'Frame-Ancestor': {
sources: sources.map(source => ({ source })),
},
};
const cspConfigResponse = await fetch(orgProperty('contentsecuritypolicyconfiguration'), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
value: JSON.stringify(config),
}),
});
if (!cspConfigResponse.ok) {
throw new Error('Failed to update csp configuration');
}
console.log('Successfully updated CSP configuration!')
if (iscontentsecuritypolicyenabled) {
console.log('CSP is already enabled! Skipping update.')
return;
}
console.log('Enabling CSP...')
const cspEnableResponse = await fetch(orgProperty('iscontentsecuritypolicyenabled'), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
value: true,
}),
});
if (!cspEnableResponse.ok) {
throw new Error('Failed to enable csp');
}
console.log('Successfully enabled CSP!')
}
Disabling CSP without UI
Steps:
- Open browser dev tools while using the model-driven app as a user with organization entity update privileges (System Administrator is a good option).
- Paste and execute the following script into the console.
- To disable CSP, paste into the console:
disableCSP()
async function disableCSP() {
const baseUrl = Xrm.Utility.getGlobalContext().getClientUrl();
const orgResponse = await fetch(`${baseUrl}/api/data/v9.1/organizations`);
if (!orgResponse.ok) throw new Error('Failed to retrieve org info');
const orgs = await orgResponse.json();
const { organizationid, iscontentsecuritypolicyenabled } = orgs.value[0];
console.log(`Organization Id: ${organizationid}`);
console.log(`CSP Enabled?: ${iscontentsecuritypolicyenabled}`);
const orgProperty = prop => `${baseUrl}/api/data/v9.1/organizations(${organizationid})/${prop}`;
if (!iscontentsecuritypolicyenabled) {
console.log('CSP is already disabled! Skipping update.')
return;
}
console.log('Disabling CSP...')
const cspEnableResponse = await fetch(orgProperty('iscontentsecuritypolicyenabled'), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
value: false,
}),
});
if (!cspEnableResponse.ok) {
throw new Error('Failed to disable csp');
}
console.log('Successfully disabled CSP!')
}