First things first: This is more a proof-of-concept than a realistic approach for your default-environment, as it is a productive environment and I am pretty sure I haven't found all downsights of that approach (I've listed the known ones at the end of the article). But I will show, how to deny users access from creating apps and flows, even if they have valid Power Apps and Power Automate licenses.

The motivation - regulatory requirements

For a client I was searching for a way that the users can use a Power App in a designated production environment, without being able to create own apps and flows in the default environment. The reason is a regulatory requirement: The client is a bank and has to comply with the german Bankenaufsichtliche Anforderungen an die IT/Supervisory Requirements for IT in Financial Institutions (BAIT). Therefor the bank has to establish processes for end-user computing (EUC).

Brief introduction to the requirements regarding EUC

Applications which are developed by business users need a proper change management, test management, user access management including recertification of authorities and risk assessments regarding the protection requirements classification.

Also there has to be an initial assessment to decide if it is a "real" application or just a data collection for simple stuff like remembering birthdays in your team etc. Transferred to the Power Platform there must be a possibility to divide the real applications (dev/test/prod-environments with ALM Accelerator) from the "I've got an email with a PDF-invoice attached and want to save it in a SharePoint-folder"-flows (default environment for Personal Productivity).

IMHO: The requirements aiming on all these shitty secured, but important spreadsheets or access-databases in our companies – not sophisticated platforms like the Power Platform. Let me rephrase it: With the Power Platform it is much easier to be compliant than with the use of excel sheets.

If you are generally interested in Power Platform governance, check out Michael Roth's blog a Microsoft MVP. It is great to understand the concepts to secure your Power Platform. As an internal auditor I'd say he is describing a lot of best practices over there!

But there is some additional work to do, and the first application should be online as soon as possible – so I tried to find a way to limit the authorizations in the default-environment.

Security group for the default-environment

My approach is simple: Try to assign a security group to the default-environment to get rid of the users in a very simple way. Also having the option to let some users work on the default-environment by just assign them to the AD-security group.

Security groups cannot be assigned to default and developer environment types. If you've already assigned a security group to your default or developer environment, we recommend removing it since the default environment is intended to be shared with all users in the tenant and the developer environment is intended for use by only the owner of the environment.

Source: learn.microsoft.com/en-us/power-platform/admin/control-user-access

At this point I have to say thanks to the the other MVP Michael Megel, who recently discovered a hidden way to activate the PCF-framework in Dataverse for Microsoft Teams environments. He and some other serious German Power Platform developers meeting every friday at 10 am in the #PowerAtelier. So if you are fluent in German and haven't joined our little group, do it now! His approach is simple: Just try out if the API, which is triggered in the Power Platform Admin Center, can be useful for the activation of features which are not shown in the UI. I tried out his way with the details of an environment – it worked like a charm.

To quote Michael Megel:

WARNING: Please continue at your own risk. I’m sure, what I show you is not supported or intended by Microsoft.

Tracing the calls

To understand which call the Power Platform Admin Center performs you can easily use the developer tools of your browser. I am based on Chrome/Chromium – so Google Chrome or Microsoft Edge works like a charm for this. Just open them, select the tab “network” and let the machine record your calls.

Slight advice, open the details of your default-environment first by opening your Admin Center, select your default-environment and then edit the details. Then start the recording in the network tab. Otherwise you will record a lot of API-calls which can be a bit confusing for our current task.

When editing the details don’t change anything – just press save and let the development tools record that call.

Trace of API-calls with developer tools

Understanding the PATCH-call

You will see a PATCH-call to the endpoint https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments/Default-{YOUR-ID}?api-version=2020-06-01

When selecting the Payload tab you can analyze the body-value which should look like this:

{
  "properties": {
    "displayName": "Personal Productivity",
    "environmentSku": "Default",
    "updateCadence": { "id": "Frequent" },
    "states": { "runtime": { "id": "Enabled" } },
    "linkedEnvironmentMetadata": {
      "securityGroupId": "00000000-0000-0000-0000-000000000000",
      "domainName": "IAmYourFather",
      "backgroundOperationsState": "Enabled"
    }
  }
}
body of the PATCH-CALL

As you can see in the json, there is a value for "securityGroupId". And when there isn't any security group set it is just the dummy value with all zeros formatted as GUID. So let us see, what happens if we call the API again, but using an actual AD-security group GUID instead of the zero.

One thing that is also recorded in the Headers tab is the authorization method for this call: a bearer-token. This one we need to call the API.

bearer-token for the call

Call the API

For that you could use curl, Postman or whatever you are using to call an API. I simply used a HTTP-action in Power Automate for my proof of concept:

Flow to call the API

The action is pretty straight forward. Select "PATCH" as Method, use the URI you can see in the development tools of your browser, add the content-type: application/json and the Authorization: Bearer ey{token} to your header. Both can be copied from the development tools.

In the body just add in the JSON, which is edited in one simple detail: An actual existing AD-security groups and not the dummy value with all the zeros.

Save the flow, call the API - it usually takes six seconds. Afterwards you can refresh your environment and see the edited details:

Default-environment with a security group

This should do it, right?

So after performing the change your default-environment the users which aren't members of the security group shouldn't be able to add a new app or flow, right? Yeah, I have tried it with several test users but I still was able to create an app and save it. I couldn't share the app with anyone else or even use it in the play-mode. In a way that was enough for me, but the flows were the real issue here: I was able to create, save, test and execute flows. Like apps I wasn't able to share them with other users.

For me it appeared that my initial goal can't be fulfilled with that approach. When I tried to understand what is working after the change and what's not I've discovered one interesting detail:

Solutions are not available - missing Dataverse authorizations

The users are not allowed to access the underlying Dataverse database of that environment anymore. So let's use that fact combined with a feature which was released by Microsoft in public preview two months ago: Add canvas apps and cloud flows to a solution by default (preview).

Dataverse is your friend - mission accomplished

Just go to the features of your default environment and activate it, so that every app and flow is saved in Dataverse:

Feature to force-create every app and flow in a solutions - which is stored in Dataverse

The effect is simple, every user who isn't in the security group we have assigned in the previous steps to the default-environment, gets an error like this:

Privileges to save into Dataverse are missing

The obvious downsides …

As I've mentioned before, it is a proof of concept I have performed on a tenant which I use for stuff like that. I wouldn't recommend that approach for a productive environment. And as the default-environment is the only productive environment for "Personal Productivity" we are having an issue here. Let me create a list which problems I see with my own way:

  • It is not supported - you won't get help from Microsoft if you are experiencing problems
  • It's not clear, how long it will work - maybe Microsoft will update your environment with a batch job (thanks Michael Roth for that thought) and your default-environment will be unsecured again without you realizing it
  • I've discovered data of Microsoft Project Online in the Dataverse database of the default-environment - maybe Project Online won't work in the expected way. I am planning to look deeper into that
  • It is not intuitive for your users what's happening and that they are not allowed to use it (yet) - they may find some workarounds to use Power Automate
  • … I am pretty sure there are some points missing!

The alternatives

Obviously the best alternative for my client are established processes to manage the requirements made by the authorities. We are looking into a usage and extension of the Center of Excellence. Maybe I will write something about that in the future. Alternatives some colleagues/friends brought up so far:

  • Use a clean-up flow to delete newly created apps and flows in the default-environment
  • Message the creators and the IT via flow to get in touch and figure out if there is a business case to develop a "real" solution in a designated environment. (my client is using this way as a workaround)
  • Use a strict DLP-policy with every connector blocked which is blockable

That last point was mentioned in the last PowerAtelier (sorry, don't remember who I should give kudos to): If it is possible to assign security groups to your default environment via API, maybe it is also possible to block connectors which you can't block via the interface. So this is something I am currently trying to achieve as next proof of concept ;-)

If you are having any questions or points I am missing, get in touch with me via LinkedIn!