Showing posts with label identity. Show all posts
Showing posts with label identity. Show all posts

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!

Friday, 17 May 2019

Azure AD B2C with Sitecore Identity

As with my last post I'm not going to go into detail about how to set up the foundation of a Sitecore Identity plugin, this is just the specifics of Azure AD B2C.

Sample code is on my Sitecore-Identity-AzureADB2C repo

Azure AD B2C

First step is obviously to create an Azure AD B2C instance in Azure.  This will set up an entire new directory that you will need to switch to in order to actually work with the Azure AD B2C tab on the left side of the Portal.

In the Azure AD B2C tab (like in AD or Auth0 and everything else) you'll need to create an Application.  Grab the Application ID (client ID) for setting in the config.  Add the Reply URL: https://your.identity.server/signin-idsrv.

We'll set up a custom user attribute which we'll use to determine whether the user is a Sitecore admin (if you are simply using B2C for an external site you can use a different name or skip this altogether).  Go into User attributes and add a new attribute called SitecoreAdmin of type boolean.



Next up create a User Flow.  I created a "Sign up and sign in v2" but I'd say it will also work with the non-v2 version (I just like using the latest version of everything).  Remember the name of the flow for your config. Inside the flow set the identity providers you want to use - bear in mind for testing it's easiest to set that SitecoreAdmin property on a "Local Account" so include that at the very least.  Under "User Attributes" and "Application Claims" ensure your SitecoreAdmin property is checked so that it is included in the list of claims which Sitecore Identity Server will receive.


Sitecore Identity Server

Grab the code and populate the clientId, tenant, and policy name.
In the Sitecore.Plugin.IdentityProvider.AzureB2C.xml config file note the added transformation:

<ClaimsTransformation3 type="Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation, Sitecore.Plugin.IdentityProviders">
  <SourceClaims>
    <Claim1 type="extension_SitecoreAdmin" value="true" />
  </SourceClaims>
  <NewClaims>
    <Claim1 type="http://www.sitecore.net/identity/claims/isAdmin" value="true"/>
  </NewClaims>
</ClaimsTransformation3>

Our property is exposed as a claim with the name extension_SitecoreAdmin which is mapped as per the documentation (also see the link for the final step of mapping the IsAdministrator property in Sitecore).

Enjoy logging in to Sitecore through Azure AD B2C!

Thursday, 16 May 2019

Auth0 Login with Sitecore Identity

I'm not going to go through and cover all the steps involved in configuring subproviders in Sitecore Identity server by creating a Sitecore Identity plugin as that has been covered numerous times for AD, ADFS, and other external providers.  I'd also recommend this excellent 3-part blog post by Himadri which walks through in a bit more detail how Sitecore Identity works.
I simply thought I'd share the code I developed for a POC recently to get Auth0 working.  Fortunately Auth0 has some great quick-start documentation on Login with .NET Core which translates nicely to the Identity Server format.

See my Sitecore-Identity-Auth0 repo on github for the code.

Auth0

Log in to Auth0 and create a new application (regular web application).  Give it a name, grab the domain, client id, and secret and put these in the Sitecore.Plugin.IdentityProvider.Auth0.xml file from the code.  Also set the following:
  • Callback URLs: https://your.sc.identityserver/callback
  • Allowed logout URLs: https://your.sc.identityserver/Account/Logout
Putting the code (with values from above) into Identity Server and logging in should now tell you that you are not authorized to log into Sitecore.  This means it was successful but your user has not been given the necessary Sitecore role to log in to the backend (eg. author / admin)

Additional Claims

Of course simply adding login support isn't generally the end of the story, you might need to ensure your users can log into the Sitecore backend (as authors or admins) or map other properties to the user (eg. interest) when they log in.

Let's create some dummy user properties in Auth0 that we can use to identify a user's role in Sitecore, as well as details we know about them. Create a new user in Auth0 or edit an existing one.
  • Under user_metadata (user-editable data) add: { "interest": "Skiing" }
  • Under app_metadata (not user-editable data) add: { "sitecore_role": "Author", "job_title": "Manager" }

In Auth0 create a new Rule (empty) and paste in the following function:
function (user, context, callback) {
 const namespace = 'https://habitathome.dev.local/';
 context.idToken[namespace + 'Interest'] = user.user_metadata.interest;
 context.idToken[namespace + 'SitecoreRole'] = user.app_metadata.sitecore_role;
 context.idToken[namespace + 'JobTitle'] = user.app_metadata.job_title;
 callback(null, user, context);
}

This maps the user data (that we'll create) to claims in the token which is returned to Sitecore Identity server.  You can make the namespace whatever you like as long as it maps to the same thing in the config we'll add next.

In Sitecore.Plugin.IdentityProvider.Auth0.xml under ClaimsTransformation2 add the following new claims transformations (see Configure claims transformations in the doco for details):
<ClaimsTransformation3 type="Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation, Sitecore.Plugin.IdentityProviders">
  <SourceClaims>
 <Claim1 type="https://habitathome.dev.local/Interest" />
  </SourceClaims>
  <NewClaims>
 <Claim1 type="Interest" />
  </NewClaims>
</ClaimsTransformation3>
<ClaimsTransformation4 type="Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation, Sitecore.Plugin.IdentityProviders">
  <SourceClaims>
 <Claim1 type="https://habitathome.dev.local/JobTitle" />
  </SourceClaims>
  <NewClaims>
 <Claim1 type="JobTitle" />
  </NewClaims>
</ClaimsTransformation4>
<ClaimsTransformation5 type="Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation, Sitecore.Plugin.IdentityProviders">
  <SourceClaims>
 <Claim1 type="https://habitathome.dev.local/SitecoreRole" value="Author" />
  </SourceClaims>
  <NewClaims>
   <Claim1 type="role" value="sitecore\Author" />
  </NewClaims>
</ClaimsTransformation5>
<ClaimsTransformation6 type="Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation, Sitecore.Plugin.IdentityProviders">
  <SourceClaims>
 <Claim1 type="https://habitathome.dev.local/SitecoreRole" value="Admin" />
  </SourceClaims>
  <NewClaims>
        <Claim1 type="http://www.sitecore.net/identity/claims/isAdmin" value="true"/>
  </NewClaims>
</ClaimsTransformation6>  

You should now be able to log in to the Sitecore back-end as an Author or Admin (or any other role you choose if you customise the code).
To access the Interest or JobTitle properties you can use the following syntax:
  • Sitecore.Context.User.Profile["Interest"]
  • Sitecore.Context.User.Profile.GetCustomProperty("Interest")
Enjoy using Auth0 with Sitecore!