Rev 03 note from 15.05.2025
Article is updated with complete instructions.
Rev 02 note from 06.03.2025
Section Default pages with public access
is updated.
Rev 01 note
Some parts of this article are updated 09.08.2024 to conform with the RFC3986 Uniform Resource Identifier (URI) standard.
Login process with Microsoft Entra ID (former Azure AD) identity provider
When a user navigates to any secured page, the Power Pages redirects the user to the sign in page:
https://site.powerappsportals.com/SignIn
5a0c25a6-4739-ef11-8409-6045bd8728a9
is your Azure AD tenant.
Clicking a button Microsoft Entra ID (Azure AD earlier) on this page will trigger the authentication process redirect which can also be initiated by navigating to the following URL:
https://site.powerappsportals.com/Account/Login/ExternalLogin?provider=https://login.windows.net/5a0c25a6-4739-ef11-8409-6045bd8728a9/&ReturnUrl=%2F
After successful authentication, the user will be taken to the page from the ReturnUrl route. In this case it's the Home page as %2F
is the URI-encoded /
.
Default pages with public access
Even if you set Page Permissions of all your pages to Authenticated Users web role only, there are some inbuilt pages which will by default be available publicly, ie. for users that are not authenticated:
https://site.powerappsportals.com/_layout/tokenhtml
https://site.powerappsportals.com/_services/about
https://site.powerappsportals.com/SignIn
https://site.powerappsportals.com/Account/Login
https://site.powerappsportals.com/Account/Login/Logoff
https://site.powerappsportals.com/Account/Login/Register
https://site.powerappsportals.com/Account/Login/ExternalAuthenticationFailed
https://site.powerappsportals.com/page-not-found
- we will use for MSAL token redirect.
If some other pages are used for the token redirect, MSAL will likely return error block_iframe_reload or hash_empty_error since hash with token is manipulated by the flow.
MSAL recommends:
Our recommended mitigation for this is to set your redirectUri to a blank page that does not implement MSAL when invoking silent APIs.
For this reason the only page we can control and setup web/page templates with msal script is ../page-not-found
.
Navigation to a specific hash (fragment) after login
Let's say that I want to go directly to the following route:
https://site.powerappsportals.com/?name=valves&status=active#suppliers
If my user session is active, I may just paste this link to the url bar and see the results I wanted. Since I'm already authenticated, no login redirect is required.
But if I'm not authenticated or my session is expired, the login redirect will be triggered with the following ReturnUrl
:
https://site.powerappsportals.com/Account/Login/ExternalLogin?provider=https://login.windows.net/5a0c25a6-4739-ef11-8409-6045bd8728a9/
&ReturnUrl=%2F?name=valves&status=active#suppliers
And... after successful authentication I will be redirected to the Home page instead of the requested route. This happens due to the hash sign and all that comes after it is lost during redirect.
Side note on how the redirect is triggered
If Microsoft Entra ID
is enabled as Identity Provider, and when a user who does not have an active session navigates to the portal url, further portal's behaviour depends on one specific setting LoginButtonAuthenticationType
.
Go to make.powerapps.com -> Apps -> All tab -> Power Pages administration -> Play -> select Site Settings -> open setting Authentication/Registration/LoginButtonAuthenticationType
When Value
field of this setting is blank
The user will be redirected to a page with url https://site.powerappsportals.com/SignIn?ReturnUrl=%2F
where he would have to click a button Microsoft Entra ID to proceed with the authentication. You can at this stage open dev tools and note the id of this button that will look like this: https://login.windows.net/5a0c25a6-4739-ef11-8409-6045bd8728a9/
.
In this case the portal will load the Header
web template.
When Value
field of this setting is not blank
You can set the value field of this setting manually to https://login.windows.net/5a0c25a6-4739-ef11-8409-6045bd8728a9/
. In this case, the user will be automatically redirected to the authentication form skipping the Header
web template.
That's not what we want if we have SPA setup with fragment based routing as explained in this post looking like this: https://site.powerappsportals.com/?name=valves&status=active#suppliers
.
#suppliers
here represent html content loaded as Suppliers
SPA page.
How to prevent # from disappearing
Task
In the ReturnUrl
the hash sign #
shall be replaced with %23
so that this part of the redirect url:
...ReturnUrl=%2F?name=valves&status=active#suppliers
becomes:
...ReturnUrl=%2F?name=valves&status=active%23suppliers
Steps in short
To do this we need to:
1) ensure that the setting LoginButtonAuthenticationType
is blank so that the header is rendered
2) add additional script to the portal's Header
web template to replace the #
with %23
and trigger the authentication process
Steps in details
Go to make.powerapps.com -> Apps -> All tab -> Power Pages administration (or Portal Management in earlier versions) -> Play
1) Check LoginButtonAuthenticationType
settings
select Site Settings -> open setting Authentication/Registration/LoginButtonAuthenticationType -> make sure the value field is blank
2) Add additional script
select Web Templates -> open Header
This will have some liquid and JavaScript code that renders portal's header. We need to add extra script on top, before other content:
Script with comments
<script>
// Example navigation to https://site.powerappsportals.com/?name=valve&sapid=123456#suppliers
const l = location;
const p = l.pathname.toLowerCase();
const shallRedirect = p.includes('signin');
// alternatively add || p.includes('login') + some other routes from above
// but exclude Account/Login/Logoff and Account/Login/ExternalAuthenticationFailed
if (shallRedirect) {
// this logic will run only if user is redirected to the sign in page, and also if he manually goes to /SignIn/ or /Account/Login/ or /Account/Login/Register/
const hash = l.hash ? l.hash.replace('#', '%23') : ''; // %23suppliers
const tenant = window["Microsoft"]?.Dynamic365?.Portal?.tenant; // 5a0c25a6-4739-ef11-8409-6045bd8728a9
// Power Pages create a global object Microsoft with portal details, from here we can extract the tenant id
// alternatively we can hardcode it
const search = l.search.replace('?', '&'); // &ReturnUrl=%2F%3Fname%3Dvalve%26sapid%3D123456
location.replace(l.origin + '/Account/Login/ExternalLogin?provider=https://login.windows.net/' + tenant + '/' + search + hash)
}
// resulted redirect url:
// https://site.powerappsportals.com/Account/Login/ExternalLogin?provider=https://login.windows.net/5a0c25a6-4739-ef11-8409-6045bd8728a9/&ReturnUrl=%2F%3Fname%3Dvalve%26sapid%3D123456%23suppliers
document.currentScript.remove(); // clean up a bit
</script>
<!-- default Header content continues further -->
{% assign defaultlang = settings['LanguageLocale/Code'] | default: 'en-us' %}
Script without comments
<script>
const l = location;
const p = l.pathname.toLowerCase();
const shallRedirect = p.includes('signin');
if (shallRedirect) {
const hash = l.hash ? l.hash.replace('#', '%23') : '';
const tenant = window["Microsoft"]?.Dynamic365?.Portal?.tenant;
const search = l.search.replace('?', '&');
location.replace(l.origin + '/Account/Login/ExternalLogin?provider=https://login.windows.net/' + tenant + '/' + search + hash)
}
document.currentScript.remove();
</script>
Final results as tested
1) User navigates to url https://site.powerappsportals.com/?name=valve&sapid=123456#suppliers
2) The user is redirected to url https://login.microsoftonline.com/...
and asked to provide email and password
3) After login is successful, the user is redirected back to this url: https://site.powerappsportals.com/?name=valve&sapid=123456#suppliers
Hi. I just run into the same issue as you described. What would be the fix you are talking about?