Wednesday 24 February 2021

Sitecore CM+Identity Behind Proxy or CDN

I recently worked with a client who had set their Sitecore login servers (SI, CM) behind Cloudflare and an Azure App Gateway, and was using URL re-writing to transform the public URL to the internal URL.
For example:

  • User -> https://si.cdn.domain.com.au -> Cloudflare -> AWW -> https://si.domain.com.au -> login
  • User -> https://cm.cdn.domain.com.au -> Cloudflare -> AGW -> https://cm.domain.com.au -> authoring

They were (understandably) encountering some issues with the setup, as this isn't entirely "out-of-the-box", so I was happy to look into it.

The first thing to check was the usual 2 settings to get CM and SI to communicate:

On CM:

<sc.variable name="identityServerAuthority" value="https://si.cdn.domain.com.au" />

And on SI:

<AllowedCorsOriginsGroup1>https://cm.cdn.domain.com.au|https://cm.domain.com.au</AllowedCorsOriginsGroup1>

(as well as the shared secret).

Once ensuring those were set correctly, it was time to compare the request / response flow during login.  You can do this by opening the network tab in your browser (make sure to "preserve log" in Chrome or "persist log" in Firefox as it's navigating multiple URLs) 

I've copied the flow from my local installation here for reference (hostnames in bold, as you'll notice them also in the ReturnUrl and redirect_uri parameters):

  1.     GET 302 https://sc93.sc/sitecore/
    o    Location: /identity/login/shell/SitecoreIdentityServer
  2. GET 200 https://sc93.sc/identity/login/shell/SitecoreIdentityServer
  3. POST 302 https://sc93.sc/identity/externallogin?authenticationType=SitecoreIdentityServer&ReturnUrl=/identity/externallogincallback?ReturnUrl=&sc_site=shell&authenticationSource=Default&sc_site=shell
    • Location: https://sc93.identityserver/connect/authorize?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2Fsc93.sc%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0
  4. GET 302 https://sc93.identityserver/connect/authorize?client_id=Sitecore&response_mode=form_post&response_type=code id_token token&scope=openid sitecore.profile&state=OpenIdConnect.AuthenticationProperties=7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https://sc93.sc/identity/signin&sc_account_prefix=sitecore\&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0
    • Location: https://sc93.identityserver/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3DSitecore%26response_mode%3Dform_post%26response_type%3Dcode%2520id_token%2520token%26scope%3Dopenid%2520sitecore.profile%26state%3DOpenIdConnect.AuthenticationProperties%253D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ%26nonce%3D637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk%26redirect_uri%3Dhttps%253A%252F%252Fsc93.sc%252Fidentity%252Fsignin%26sc_account_prefix%3Dsitecore%255C%26x-client-SKU%3DID_NET451%26x-client-ver%3D5.2.2.0
  5. GET 200 https://sc93.identityserver/Account/Login?ReturnUrl=/connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2Fsc93.sc%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0
  6. POST 302 https://sc93.identityserver/Account/Login?ReturnUrl=/connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2Fsc93.sc%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0
    • (with form data)
    • Location: /connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2Fsc93.sc%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0
  7. GET 200 https://sc93.identityserver/connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code id_token token&scope=openid sitecore.profile&state=OpenIdConnect.AuthenticationProperties=7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https://sc93.sc/identity/signin&sc_account_prefix=sitecore\&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0
  8. POST 302 https://sc93.sc/identity/signin
    o    Location: /identity/externallogincallback?ReturnUrl=&sc_site=shell&authenticationSource=Default
  9. GET 302 https://sc93.sc/identity/externallogincallback?ReturnUrl=&sc_site=shell&authenticationSource=Default
    • Location: https://sc93.sc/sitecore/client/Applications/Launchpad?sc_lang=en
  10. GET https://sc93.sc/sitecore/client/Applications/Launchpad?sc_lang=en

The first issue was that the user was ending up at the non-proxy/cdn URL for CM, whereas they should be requesting the proxy URL the whole time.

Going through the OWIN pipelines I noticed this appeared to be occuring in Sitecore.Owin.Authentication.IdentityServer.Pipelines.IdentityProviders.ConfigureIdentityServer, and what do you know? Inside this class we see it calls a BuildRedirectUri() method which actually accepts a "CallbackAuthority". Never heard of it? Neither had I! But hey it's right there commented out at the top of App_Config\Sitecore\Owin.Authentication.IdentityServer\Sitecore.Owin.Authentication.IdentityServer.config!

<!-- Fill the FederatedAuthentication.IdentityServer.CallbackAuthority setting if you need another host to receive callbacks from IdentityServer. It is useful for reverse proxy configuration. -->
<!--<setting name="FederatedAuthentication.IdentityServer.CallbackAuthority" value="http://proxy" />-->

And after a quick search around the net it does seem like a couple of others have encountered it too.  My bad!

So let's set it: <setting name="FederatedAuthentication.IdentityServer.CallbackAuthority" value="https://si.cdn.domain.com.au" />

Great, we're making it a bit further, but still hitting the non-proxy URL... Digging further into the pipeline we get to Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.GetStartUrl where we can see it's calling the GetStartUrlPipeline, however it's not using our proxy URL, it's using the request URL - which, by the time it hits this server, has already been transformed!  Let's fix that with a quick patch:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore role:require="Standalone or ContentDelivery or ContentManagement">
<pipelines>
<owin.cookieAuthentication.signedIn>
<processor type="Identity.Pipelines.CookieAuthentication.CustomGetStartUrl,Identity" 
patch:instead="*[@type='Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.GetStartUrl, Sitecore.Owin.Authentication']" resolve="true" />
</owin.cookieAuthentication.signedIn>
</pipelines>
</sitecore>
</configuration>

Perfect, now we're staying on the proxy hostname, but getting redirected to a "not found" page... Ok let's see if there are any other spots in the code where it's using the request URL rather than the CallbackAuthority (proxy) URL. Yep we have one in GoToLoginPage. Let's patch that one too:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore role:require="Standalone or ContentDelivery or ContentManagement">
<pipelines>
<owin.cookieAuthentication.applyRedirect>
<processor type="Identity.Pipelines.ApplyRedirect.CustomGoToLoginPage, Identity" resolve="true"
patch:instead="*[@type='Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ApplyRedirect.GoToLoginPage, Sitecore.Owin.Authentication']" />
</owin.cookieAuthentication.applyRedirect>
</pipelines>
</sitecore>
</configuration>

Now we're seeing an error in the logs:

8604 04:31:54 ERROR Only local URLs are allowed.
Exception: Sitecore.Exceptions.SecurityException
Message: Only local URLs are allowed.
Source: Sitecore.Kernel
at Sitecore.Web.Authentication.DefaultTicketManager.CheckOnExternalUrl(String startUrl)
  at Sitecore.Web.Authentication.DefaultTicketManager.CreateTicket(String userName, String startUrl, Boolean persist)
  at Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.CreateTicket.Process(SignedInArgs args)

Sitecore is trying to create a "ticket" to manage the logged in users, however it's using the proxy URL which it doesn't know about. For this one, we'll have to swap the URL back to the non-proxy URL with another quick patch:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore role:require="Standalone or ContentDelivery or ContentManagement">
<pipelines>
<owin.cookieAuthentication.signedIn>
<processor type="Identity.Pipelines.CookieAuthentication.CustomCreateTicket,Identity"
patch:instead="*[@type='Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.CreateTicket, Sitecore.Owin.Authentication']" resolve="true" />
</owin.cookieAuthentication.signedIn>
</pipelines>
</sitecore>
</configuration>

And with that, we're finally able to log in and see the dashboard!