Sample project of a Microsoft Power Pages Single Page Application with no "low code no code" principle. Provisioned with Vite and optional Node.js hosted as Azure Web App. Suitable for corporate environment with sensitive business data.
- Fully controlled dependencies in Power Pages
- Sign-in with multiple accounts / switch accounts made easy without signout
- Browser-based Microsoft Authentication Library for JavaScript authenticating with:
- Azure AD
- Dataverse (db behind Power Pages)
- Azure Storage
- Azure Cosmos DB
- Role-based access control to resources and content
- Lightweight
- Custom logger
- Multi-layered security on various levels
- Techstack is limited to Azure SaaS cloud-based tools only
- Pulling data from API endpoints with Flows or custom Node.js Web App
- Syncing data with Dataflows
- Optional Domain-Driven Design with Node.js Web App
- Simple setup
- Node.js as a proxy-server
- Isolate business logic from system code
Supplier management portal to provide a buyer/procurement proffesional with information on suppliers organization is dealing with. The portal will keep internal stakeholders updated with current supplier status including challenges, delays and quality issues supplier is experiencing in projects or other engagements, prices and lead times actual vs committed, legal issues, evaluation of suppliers and more. The aim of the portal is to make buyer's work easier and more efficient, improve bargaining power in negotiations with suppliers and help take right business decisions during supplier selection to maintain "5 rights of procurement".
- Check if you have account in portal.azure.com. Use corporate Azure account or see how to make a new Azure developer subscription which can be created with any private email address / phone and credit card
-
I creat subscription with email [email protected], phone and credit card
-
When accessing portal.azure.com first time with this email, I see only a single user me with email
......#[email protected] -
I adjust the email to
[email protected] -
This email I use further to access:
- admin.cloud.microsoft - your main place to manage Azure account and services:
- portal.azure.com
- entra.microsoft.com
- admin.powerplatform.microsoft.com
- make.powerapps.com
- make.powerpages.microsoft.com
- admin.cloud.microsoft - your main place to manage Azure account and services:
-
First time I login to portal.azure.com with
[email protected], the system will ask me to setup MFA authentication with a phone appAuthenticatorthat you should have installed prior -
You get the following licences on your account:
- Microsoft Power Automate Free -> activated on your user
- Microsoft Power Apps for Developer -> activated on your user
- Power Pages vTrial for Makers -> not activated on your user
-
Activate Power Pages vTrial for Makers licence
This is preffered step over 3. Do it as explained here learn.microsoft.com/en-us/power-pages/getting-started/trial-signup.
After filling out the form you get provisioned a power apps environment in admin.powerplatform.microsoft.com with name
PowerPagesTrial-xxxxxxand TypeTrialfor 30 days.Following step 2 you get Trial env which you can convert to
Productionthat is a must to make you web site public, see here learn.microsoft.com/en-us/power-pages/security/site-visibility. Only in public mode you get your public routes to work for MSAL redirect, see Default pages with public access here dev.to/andrewelans/power-pages-spa-login-redirect-2g2n.In step 3 you get a Developer env that you cannot convert to Production and therefore make a Power Pages site public. Trying to convert it in make.powerpowerpages.microsoft.com, the system is sending this req under the hood:
POST https://portalsitewide-nor.portal-infra.dynamics.com/api/v1/powerPortal/UpdateSiteVisibility?tenantId=GUID&portalId=GUID&siteVisibility=publicwith response 400:
{ "Message": "Site visibility cannot be changed to public on developer environment.", "ErrorCode": "D005", "ErrorType": "User error" }After you have your env provisined, check this page dev.to/andrewelans/power-pages-spa-main-setup for adjusting authentication and redirectUri in App registration on portal.azure.com.
-
Step 2 is preffered over this step. Provision a new powerapps environment (take a note on language), power pages portal and set it up as explained here dev.to/andrewelans/power-pages-spa-main-setup.
-
Create 3 guest users
Go to portal.azure.com.
Add 3 new dummy users with User type Guest.
- Bing Whatman
- Gwenny Dinsell
- Essa Stuckes
When created, remember temporary passwords. When logging in with these users first time, you would need to update the password using the temporary one.
Use permanent password
DummyGuest1for all users.When a user does not have a licence, he will not appear as a user in Dataverse (Default Business unit and default Team). As a consequenc, trying to send a simple query like this
https://250101.api.crm19.dynamics.com/api/data/v9.2/WhoAmIwill return a403 Forbiddenerror:{ "error": { "code": "0x80072560", "message": "The user is not a member of the organization." } }Go to admin.microsoft.com -> Home -> find that these new users in Licences column has
Unlicensed-> click on each user's...-> assingMicrosoft Power Apps for Developer. Go to admin.powerplatform.microsoft.com -> your env -> Settings -> Users -> click Add user -> and add new users one by one.You can also do this in bulk with Power Automate as explained in this blog matthewdevaney.com/force-sync-users-from-entra-security-group-to-dataverse-team.
Type Guest will have limited permissions in portal.azure.com and will not be required for MFA-authentication on power pages. However, Guests will still be requested to authenticate with MFA if they try to access:
- Azure portal
- Entra admin center
- Intune admin center
- M365 Admin center
More on MFA:
- learn.microsoft.com/en-us/entra/identity/authentication/concept-mandatory-multifactor-authentication.
- portal.azure.com/#view/Microsoft_Azure_Resources/MfaSettings.ReactView.
Set most restrictive policy on users including guests.
- Guest user access -> Activate
Guest user access is restricted to properties and memberships of their own directory objects (most restrictive) - Restrict access to Microsoft Entra admin center ON
- Show keep user signed in OFF
- Allow users to connect their work or school account with LinkedIn No
- External collaboration settings -> Guest invite restrictions -> No one in the organization can invite guest users including admins (most restrictive) ON
-
Organization of the supplier management portal
Access and content is provided based on the following grouping:
Type Accessible Pages/Routes/Functionality Security Group Dataverse Team Users System Administrator Home
Find Supplier
Frame Agreements Write
AdminSG-SYSTEM-ADMIN admin Andrew Elans Procurement users Home
Find Supplier
Frame Agreements ReadSG-PROCUREMENT-ALL procall Bing Whatman
Gwenny DinsellFrame Agreement Managers Home
Find Supplier
Frame Agreements WriteSG-FA-OWNER faowner Gwenny Dinsell All other tenant's users No access with request access form azure Each page has a corresponding snipet. Snippets are stored in a new Dataverse table with the following structure:
Name az Val Admin az Val ProcAll az Val FAOwner az Val Azure az Val Common nav json content json content json content json content json content Each
az Val..column hasColumn security profileset up.Structure of
json content:{ "seq": 1, "name": "az_valshared", "snippets": [ { "name": "_home", "title": "Home", "route": "/", "hash": "", "html": "<h1>Home</h1><script>console.log(\"hi from home page\")</script>" } ], "divStr": "<li class=\"nav-item\"><button class=\"nav-link handler nav-home\" data-snippet=\"_home\">Home</button></li>", "navbarModule": { "name": "dynamic-02-navbar", "fns": ["snippetsFn", "activateNavBtnFn"] } }To be continued...
-
Create Security Groups
In portal.azure.com -> Groups create corresponding groups and add members.
-
Create a new solution in make.powerapps.com dev.to/andrewelans/power-pages-spa-components-part-1
-
Do some changes to default Power Pages components as per this post dev.to/andrewelans/power-pages-spa-components-part-2 and this repo github.com/AndrewElans/PowerPagesSPA-PowerPagesSetup.
You should now have a working web site with the following behaviour when requesting urls in a new inkognito window (provide you have enabled Public mode):
-
Redirect to login.microsoftonline.com:
- site-250101.powerappsportals.com
- site-250101.powerappsportals.com/dynamic-assets
- site-250101.powerappsportals.com/static-assets
- site-250101.powerappsportals.com/signin
-
Render Page Not Found:
- site-250101.powerappsportals.com/page-not-found
- site-250101.powerappsportals.com/lkjjkdjfkj
-
Render corresponding content:
- site-250101.powerappsportals.com/cat-pc.png
- site-250101.powerappsportals.com/access-denied
- site-250101.powerappsportals.com/_layout/tokenhtml -> blank page with single input on body
Log in to your portal with your user. You will see your email on document.body.dataset.emailpowerpages.
Open Dev Tools -> Sources -> Page.
On all these pages the sources will not have any of the default power pages dependencies:
- site-250101.powerappsportals.com
- site-250101.powerappsportals.com/dynamic-assets
- site-250101.powerappsportals.com/static-assets
- site-250101.powerappsportals.com/page-not-found
Go to
site-250101.powerappsportals.com/access-deniedsee all default dependencies loaded and compare to our simplest setup.Dev Tools -> Network
-
site-250101.powerappsportals.com/access-denied (with default deps)
- 39 requests
- 1.3 MB transferred
- 4.8 MB resources (we can deduct the cat image from here to be objective)
-
site-250101.powerappsportals.com (w/o default deps)
- 1 requests
- 1.3 kB transferred
- 382 B resources
Dev Tools -> Performance
-
site-250101.powerappsportals.com/access-denied (with default deps)
- Scripting 501 ms
- System 165 ms
- Loading 18 ms
- Rendering 14 ms
- Painting 1 ms
-
site-250101.powerappsportals.com (w/o default deps)
- System 19 ms
- Rendering 7 ms
- Scripting 2 ms
And these are very simple pages!
Microsoft now provides SPA functionality as explained here learn.microsoft.com/en-us/power-pages/configure/create-code-sites.
Let's convert our site into the default SPA and see the difference. To do this we add to settings
CodeSite/Enabledset totrue, resync and compare functionality on the same urls in inkognito.-
Redirect to login.microsoftonline.com:
- site-250101.powerappsportals.com no changes
- site-250101.powerappsportals.com/dynamic-assets no changes
- site-250101.powerappsportals.com/static-assets no changes
- site-250101.powerappsportals.com/signin no changes
-
Render Page Not Found:
- site-250101.powerappsportals.com/page-not-found no changes
- site-250101.powerappsportals.com/lkjjkdjfkj changes: all default dependences are loaded. This behaviour will have undesired implication on our routing.
-
Render corresponding content:
- site-250101.powerappsportals.com/cat-pc.png no changes
- site-250101.powerappsportals.com/access-denied no changes
- site-250101.powerappsportals.com/_layout/tokenhtml no changes
-
-
Add table
az SnippetGo to make.powerapps.com -> your env -> Tables -> New table -> Table (advanced properties)
- Display name
az Snippet - Schema name
az_Snippet - Primary column:
- Display name
az Name - Schema name
az_Name
- Display name
Add new columns
az Val Admin,az Val ProcAll,az Val FAOwner,az Val Azure,az Val Commonwith:- Data type
Multiple lines of text - Maximum character count 10000
- Enable column security ON (except for az Val Azure)
- Adjust Schema name to format
az_Val...
Open Forms on this table -> select form Information (Main) -> Edit -> add Multiline text with component
Edit This Monaco editor is used in Power Pages Management Appfor each of the created columns.Not complete description. I will show in a video
- Display name
-
Edit Power Pages Management app
Go to make.powerapps.com -> Apps -> Power Pages Management -> Edit.
Find and select
Web Templates formon the left -> click on topAdd page-> Dataverse table -> selectaa Snippet-> AddNow we can add and edit snippets directly in the app.
Select
aa Snippets formadded just now -> click on topAdd page-> Dataverse table -> find and selectTeam-> AddClick
Save and publishon top right. -
Create user views
We will make custom user views to be able to call userQuery with Web API:
- Teams user a member of
- Snippets user has access to
To be able to test your views in a browser, in a new tab go to make.powerapps.com -> setings cog on top right -> Developer resources -> copy Web API endpoint
https://250101.api.crm19.dynamics.com/api/data/v9.2-> paste URL origin in a the url barhttps://250101.api.crm19.dynamics.comto authenticate this endpoint.In Power Pages Management app -> select
Teamson the left -> Edit columns -> removeBusiness Unit-> Apply -> clickEdit filters-> clickDelete all filters-> clickAdd related entity-> find and selectUsers-> select in a fieldUserand operatorEquals current user-> Apply -> Save as new view -> nameaa User Teams-> Save -> Set as default view -> clickManage and share views-> click...of viewaa User Teams-> Share -> inAdd user/teamfind and selectazure(our default tenant's team) -> add PermissionsRead-> click Share -> Close -> copy from the url bar and takeviewidfrom...&viewid=a49fb75b-bdbb-f011-bbd3-6045bdeaa0cd....Run this query in the browser
https://250101.api.crm19.dynamics.com/api/data/v9.2/teams?userQuery=a49fb75b-bdbb-f011-bbd3-6045bdeaa0cd.Response:
{ "@odata.context": "https://250101.api.crm19.dynamics.com/api/data/v9.2/$metadata#teams(name,teamid,teamtype,ownerid)", "value": [ { "@odata.etag": "W/\"2012344\"", "ownerid": "ac077676-1fbb-f011-bbd3-6045bdeaa0cd", "teamtype": 2, "name": "admin", "teamid": "ac077676-1fbb-f011-bbd3-6045bdeaa0cd" }, { "@odata.etag": "W/\"2011269\"", "ownerid": "da4f12c4-c4b6-f011-bbd3-002248dc6fd6", "teamtype": 0, "name": "azure", "teamid": "da4f12c4-c4b6-f011-bbd3-002248dc6fd6" } ] }In Power Pages Management app -> select
aa Snippetson the left -> Edit columns -> removeCreated On-> Add columns -> add all custom createdaa Val..columns -> Close -> Apply -> Save as new view -> nameaa User Snippets-> Save -> Set as default view -> clickManage and share views-> click...of viewaa User Teams-> Share -> inAdd user/teamfind and selectazure(our default tenant's team) -> add PermissionsRead-> click Share -> Close -> copy from the url bar and takeviewidfrom...&viewid=f810968d-c7bb-f011-bbd3-6045bdeaa0cd....Click
Newto add new record to the aa Snippets -> namenav-> Save and close.Run this query in the browser
https://250101.api.crm19.dynamics.com/api/data/v9.2/aa_snippets?userQuery=f810968d-c7bb-f011-bbd3-6045bdeaa0cd.Response:
{ "@odata.context": "https://250101.api.crm19.dynamics.com/api/data/v9.2/$metadata#aa_snippets(statecode,aa_snippetid,aa_name,aa_valadmin,aa_valazure,aa_valcommon,aa_valfaowner,aa_valprocall)", "value": [ { "@odata.etag": "W/\"2090370\"", "aa_name": "nav", "statecode": 0, "aa_snippetid": "270797bc-c7bb-f011-bbd3-6045bdeaa0cd" } ] } -
Setup Dataverse
Go to admin.powerplatform.microsoft.com -> your env -> Settings
-
Business units -> I have one with name
org459277b5-> open -> edit -> rename toazure -
Teams
- delete all default teams present there except the one named
azure - create new teams with as defined in the step 5 table with
- Team name as in column
Dataverse Team - Team type
Microsoft Entra ID Security Group - Group name as in column
Security Group - Membership type
Members and guests
- Team name as in column
- delete all default teams present there except the one named
-
-
Add defaut logging.
-
Add security groups and create snippets.
-
Set up MSAL Browser and Vite coming...
-
...
-
...
-
...
-
Provision mock json data
Content is in progress...