Sitecore EntityService with ItemService Style Token Authorization

Sitecore’s ItemService and EntityService have been around for a while now and there is a fair amount of community content out there (and Sitecore’s documentation) that describe the role of the two service layers and how to configure them so I won’t reinvent those wheels here.

What I have recently come to discover however is that the EntityService, whilst providing better support for working with custom business objects and writing custom WebAPI controllers does not support authorization out-of-the-box. The settings in Sitecore.Services.Client.config that relate to authorization all apply to the ItemService only.

What I wanted for my project, was the flexibility of the EntityService with (JWT) token based authorization. Best of both worlds.

Ian Graham wrote an excellent article on enabling token based authorization, which you can read here. The configuration steps mentioned there are all relevant to this scenario so consider them a pre-requisite.

After configuring token based authorization and setting up an EntityService you will immediately notice that you can send a request to your service without a token header and it will return a 200 response and the expected JSON data. So how can we use the out-of-the-box token generation and validation that Sitecore provides with the ItemService within the context of EntityService controllers?

Well first, you need to understand that Sitecore’s implementation uses the class Sitecore.Services.Infrastructure.Sitecore.Security.ConfiguredOrNullTokenProvider, which is instantiated in the constructor of Sitecore.Services.Infrastructure.Sitecore.Security.TokenDelegatingHandler


public TokenDelegatingHandler() : this(new ConfiguredOrNullTokenProvider(new SigningTokenProvider()), new UserService())
{
}

The TokenDelegatingHandler is configured in Sitecore.Services.Client.config


<api>
    <services>
        <configuration type="...">            
            <delegatingHandlers hint="list:AddDelegatingHandler">
                <delegatingHandler desc="TokenDelegatingHandler">Sitecore.Services.Infrastructure.Sitecore.Security.TokenDelegatingHandler, Sitecore.Services.Infrastructure.Sitecore</delegatingHandler>
            </delegatingHandlers>
        </configuration>
    </services>
</api>

The TokenDelegatingHandler uses the following method to validate a token passed in the request header


private void AttemptLoginWithToken(HttpRequestMessage request)
{
    if (request.Headers.Contains("token"))
    {
        ITokenValidationResult tokenValidationResult = this._tokenProvider.ValidateToken(request.Headers.GetValues("token").FirstOrDefault<string>());
        
        if (tokenValidationResult.IsValid)
        {
            if ((
                from c in tokenValidationResult.Claims
                where c.Type == "User"
                select c).Count<Claim>() == 1)
            {
                string value = tokenValidationResult.Claims.First<Claim>((Claim c) => c.Type == "User").Value;
                this._userService.SwitchToUser(value);
            }
        }
    }
}

What’s important here is that an instance of HttpRequestMessage is passed into this method.

Now let’s create a custom authorize attribute that inherits from System.Web.Http.AuthorizeAttribute. In this class override the IsAuthorized method, which is passed an instance of HttpActionContext and let’s give it a constructor that creates a new privately available instance of ConfiguredOrNullTokenProvider just like the TokenDelegatingHandler (except we don’t also need the UserService, we just want to know if the token is valid or not).

The HttpActionContext instance passed into the IsAuthorized method gives us access to the HttpRequestMessage (which the TokenDelegatingHandler uses to locate the token header, so we can do the same. Putting it all together


public class TokenAuthorizeAttribute : AuthorizeAttribute
{
    private readonly ITokenProvider _tokenProvider;

    public TokenAuthorizeAttribute() : this (new ConfiguredOrNullTokenProvider(new SigningTokenProvider()))
    {

    }

    public TokenAuthorizeAttribute(ITokenProvider tokenProvider)
    {
        _tokenProvider = tokenProvider ?? throw new ArgumentNullException("tokenProvider");
    }

    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        if (!actionContext.Request.Headers.Contains("token"))
            return false;

        var tokenValidationResult = _tokenProvider.ValidateToken(actionContext.Request.Headers.GetValues("token").FirstOrDefault());           

        return tokenValidationResult.IsValid;
    }
}

Now we simply decorate our entity service controller with our custom token authorization attribute


[ServicesController]
[EnableCors(origins: "*", headers: "*", methods: "GET")]
[TokenAuthorize]
public class ProductController : Sitecore.Services.Infrastructure.Sitecore.Services.EntityService<Product>
{

}

Now you can request tokens the same way you do with the ItemService:

Request Sitecore token using Postman

And pass the value of the token response back to Sitecore in the token header when making requests to your entity service controllers or get a 403 response.

As highlighted on Sitecore Stack Exchange you will get a 500 server error response if you pass a token that has expired (expiry is 20 minutes by default but configurable in the setting Sitecore.Services.Token.Authorization.Timeout), well certainly in 8.2 Update 2 and Update 3 you will, I haven’t tested other versions. If you don’t care about that, you are done here.

If however, you do want to get a proper 403 response for expired tokens (like me and the OP on Stack Exchange) read on.

To make this error go away we have to (unfortunately) do some slightly dirty things. First we need to replace the default implementation of TokenDelegatingHandler, which is straight forward to do because it’s just configuration


<api>
    <services>
        <configuration>
            <delegatingHandlers hint="list:AddDelegatingHandler">
                <patch:delete />
            </delegatingHandlers>
            <delegatingHandlers hint="list:AddDelegatingHandler">
                <delegatingHandler desc="TokenDelegatingHandler">MyHandler, MyAssembly.dll</delegatingHandler>
            </delegatingHandlers>
        </configuration>
    </services>
</api>

In our custom handler we want to replace the default SigningTokenProvider with our own implementation in two of the constructors


public class MyHandler : TokenDelegatingHandler
{
    public MyHandler()
        : base(new ConfiguredOrNullTokenProvider(new MySigningTokenProvider()), new UserService())
    {

    }

    protected MyHandler(HttpMessageHandler innerHandler)
        : base(innerHandler, new ConfiguredOrNullTokenProvider(new MySigningTokenProvider()), new UserService())
    {

    }
}

This is where it gets gory, yes you guessed it, no virtual methods, private members etc so we have to copy paste a bunch of Sitecore’s code and replace the bit responsible for the 500 error which as the thread on Stack Exchange explains is in the logging of SecurityTokenInvalidLifetimeException exceptions raised. I’ve thrown up a gist so you don’t have to go through the pain of tidying it all up.

That’s it. Sitecore’s EntityService with ItemService style token authorization and proper 403 responses for expired tokens.

Happy… securing.

Sitecore & Xamarin for Mobile Apps

I had the pleasure of presenting this topic at Sitecore Symposium this year in Las Vegas. The aim of the topic was to demonstrate the steps required to get started with Xamarin development in conjunction with Sitecore’s Mobile SDK.

I have turned this presentation into an article, which you can find here:

https://www.valtech.com/blog/sitecore–xamarin-for-mobile-apps/

You can also download the slides from the presentation here:

Everyone gets an app!

Sitecore Quick Tip: Getting and Setting SafeDictionary

Sitecore’s SafeDictionary is used throughout Sitecore’s libraries and the purpose of it (as opposed to using Dictionary) is to allow dictionary[key] without exceptions, instead returning default(T).

To read values into a SafeDictionary from a Sitecore field that uses a query string internally (validator parameters, rendering parameters, name value field etc.) you can use:


var parameters = Sitecore.Web.WebUtil.ParseQueryString(item["Field Name"]);

Then of course you can modify the SafeDictionary:


if (!parameters.ContainsKey("foo"))
{
    parameters.Add("foo", "bar");
}

And writing the SafeDictionary back to the field:


item["Field Name"] = string.Join("&", parameters.Select(x => $"{System.Web.HttpUtility.UrlEncode(x.Key)}={System.Web.HttpUtility.UrlEncode(x.Value)}"));

Sitecore Sunday Pt 2: Programmatic Profile Association

Sitecore profiles and content tagging offer an interesting mechanism to implicitly understand the profiles your site visitors belong to, the journeys they take and how they behave differently to other users. For more information on the concept of profiles take a look at this post by nonlinear’s Amanda Shiga.

Where the tagging of content items with profiles can seem redundant is when profiles map 1 to 1 with taxonomies that already exist in your sites architecture, for example:

  • The content they are viewing is already tagged with controlled vocabulary e.g. luxury
  • The content they are viewing exists within a specific information architecture e.g. the luxury section
  • There are properties of the content they are viewing that identify the type of customer they are e.g. a price banding that indicates luxury

This is especially true for sites with thousands of pages and/or products. Who is going to tag all that content/data again?

In this post I want to explore technical solutions to automating the process of associating profiles to your site visitors without the upfront and ongoing tagging using the very taxonomies mentioned above.

Yo Dawg, I'm Implicitly, Implicitly Profiling

The first thing to look at is how does Sitecore traditionally associate profiles and profile scores with a users contact record (xDb contact that is) when they view an item tagged with a profile? It starts in the startTracking pipeline (Sitecore.Analytics.Tracking.config):


<startTracking>
<!-- other processors removed for brevity -->
<processor type="Sitecore.Analytics.Pipelines.StartTracking.ProcessItem, Sitecore.Analytics"/>
</startTracking>

This processor actually just runs another pipeline defined in Sitecore.Analytics.config, and the last processor in this pipeline is where things start to happen:


<processItem>
<!-- other processors removed for brevity -->
<processor type="Sitecore.Analytics.Pipelines.ProcessItem.ProcessProfiles, Sitecore.Analytics" />
</processItem>

When Sitecore items are associated with profiles the association is stored in the __Tracking field as xml.


<?xml version="1.0" encoding="UTF-8"?>
<tracking>
   <profile id="{ABCD566E-264B-4E73-8A67-970CFCCAA82A}" name="Sales Cycle" presets="evaluate|100||">
      <key name="Search and Inspiration" value="0" />
      <key name="Evaluate" value="10" />
      <key name="Enquire" value="0" />
   </profile>
</tracking>

The ProcessProfiles processor iterates over the TrackingField instances added to the pipeline args by previous processors and passes each TrackingField into the TrackingFieldProcessor class to read the xml, extract the profiles and scores associated with each profile key and store those values against the current contact, ready to be flushed into xDb at the end of the session.


public override void Process(ProcessItemArgs args)
{
    Assert.ArgumentNotNull(args, "args");
    
    foreach (TrackingField trackingParameter in args.TrackingParameters)
    {
        TrackingFieldProcessor.ProcessProfiles(args.Interaction, trackingParameter);
    }
}

Sitecore’s CollectParameters processor comes first in the processItem pipeline and simply adds the TrackingField of the context item to the list of tracking fields on the pipeline args.


public override void Process(ProcessItemArgs args)
{
    Assert.ArgumentNotNull(args, "args");

    if (!string.IsNullOrEmpty(args.Item["__Tracking"]))
    {
        Field item = args.Item.Fields["__Tracking"];

        if (item != null)
        {
            args.TrackingParameters.Add(new TrackingField(item));
        }
    }
    
    // source code removed for brevity
}

The following approach circumvents the content tagging process by mapping profiles to site taxonomies that can be evaluated inside the processItem pipeline.

Let’s use the example of leveraging existing content tagging for a site that sells cars. Each car on the site is already tagged as Luxury, Family, Economic or Utility. This is a classic example of existing taxonomies mapping 1 to 1 with profiles. 

Profile map items can be created in the content tree (a profile map repository) that provide fields that allow the setup of rules that say:

“where the context item (Page) is linked to the item X (Family) in the field Y (Vehicle Type), associate the profile Z (Family Cars) with the current user”

Profile Map Item

To allow the selection of a specific profile on a profile map item we simply need to add a field of type Profile Cards which under the hood uses a TrackingField, yep, the same field that Sitecore stores profile information in when tagging pages with profiles. This means we can also take advantage of the TrackingFieldProcessor class in a custom processor in the processItem pipeline.

To add our pipeline we need to patch it in after the CollectParameters processor, not instead of it so that traditional content tagging continues to work if needed.


<pipelines>
  <processItem>
    <processor 
      type="Sitecore.Feature.ProfileMapper.Analytics.Pipelines.ProcessItem.CollectMappedParameters, Sitecore.Feature.ProfileMapper" 
      patch:after="*[@type='Sitecore.Analytics.Pipelines.ProcessItem.CollectParameters,Sitecore.Analytics']" />
  </processItem>
</pipelines>

Then the magic happens here:


public class CollectMappedParameters : ProcessItemProcessor
{
    public override void Process(ProcessItemArgs args)
    {
        foreach (var profileMapSet in GetProfileMapsSets())
        {

            // source code removed for brevity

            foreach (var profileMap in profileMapSet)
            {
                var trackingField = new ProfileMap(profileMap).EvaluateProfileMap(args.Item);

                if (trackingField != null)
                {
                    args.TrackingParameters.Add(trackingField);

                    if (matchSingle)
                        break;
                }
            }
        }
    }

    public virtual IEnumerable<IGrouping<ID, Item>> GetProfileMapsSets()
    {
        var sets = new ProfileMapRepository().GetProfileMapSets();

        return sets; 
    }
}

To summarise the code above:

  • A collection of profile map sets (a set could be market, price ranges etc.) is retrieved from a profile map repository (which uses a custom index for performance)
  • Each profile map within each set is evaluated against the current context item (Page) and if it’s a match returns its tracking field (containing the matching profile)
  • The tracking field is added to the TrackingParameters collection on the pipeline args, which means it will be used by the ProcessProfiles processor and passed to the TrackingFieldProcessor described above

Et voila! Without having to tag any Sitecore items with profiles we are associating profiles with our site users based on their browsing behaviour and based on taxonomies that already exist in the site.

We can extend profile mapping concepts, for example range maps that say:

“where the value of the field X (Price) on the context item (Page) is between LowerBounds (70000) and UpperBounds (1000000), associate the profile Y (Luxury) with the current user”.

Or really anything that applies to the business domain you are working with and of course we can use Sitecore’s rules field to take advantage of the rules engine.

This approach still requires us to create the profiles, profile keys, profile cards and pattern cards ahead of time and for these types of 1 to 1 mapping scenarios each profile and pattern card is typically a 1 to 1 mapping with a profile key and a score of 10 for its respective key and 0 in all others. For example:

1 to 1 profile key to cards

Happy profiling!

Creating Localized PDFs in the Sitecore Media Library

Sitecore makes it very simple to create multiple language versions of page and component items but it’s not so obvious how to do the same with PDFs. By default Sitecore will create PDF items using the Unversioned PDF template meaning that the uploaded media file is shared across all languages. Here are three ways to get around this.

Upload Files (Advanced)

By far the simplest method is to train/instruct your content authors to use the Upload Files (Advanced) option when uploading files. This dialog provides a checkbox titled “Make uploaded media items versionable”, checking this box will create the media items using the Versioned PDF template allowing authors to then upload media files per language version of the media item.

Upload Files (Advanced)

Extend the uiUpload Pipeline

Option one is the simplest option but what if you want to automatically enforce that all PDFs uploaded can be translated without having to rely on content authors remembering to use the Upload Files (Advanced) option and then remembering to check the versionable checkbox? the uiUpload pipeline runs every time a media file is uploaded to the media library. To do this we can add a custom processor to the upUpload pipeline that runs after Sitecore’s Save processor.

<sitecore>
  <processors>
    <uiUpload>
      <processor patch:after="processor[@type='Sitecore.Pipelines.Upload.Save, Sitecore.Kernel']" mode="on" type="YourClass, Your.Assembly"/>
    </uiUpload>
  </processors>
</sitecore>

In your custom class you can then detect whether the media item just uploaded is based on the Unversioned PDF template and switch it to the Versioned PDF template.

    public class ChangeMediaTemplate
    {
        public void Process(UploadArgs args)
        {
            if (args.UploadedItems == null || !args.UploadedItems.Any())
                return;

            var db = Database.GetDatabase("master");

            foreach (var item in args.UploadedItems)
            {
                if (item.TemplateID != ID.Parse("{Unversioned PDF Guid}"))
                    continue;

                item.ChangeTemplate(new TemplateItem(db.GetItem(ID.Parse("{Versioned PDF Guid}"))));
            }
        }
    }

Custom Media Templates

Option 3 is an option you might want to consider if you also want to customize the fields on your PDF template. For example you might want to allow a content author to assign a document type to a PDF. Create a new template and add Versioned PDF to the list of the templates base templates. Add your custom fields to the template.

You can now add your custom template as an insert option. I would recommend doing this using the rules engine. Under the item /sitecore/system/Settings/Rules/Insert Options/Rules add a new item based on the Insert Options Rule template and edit the rule like so (replacing “specific template” with your custom template):

Insert Option Rule

Now content authors can create items under media folders using your custom template. The next step for the author is to select which language version they want to edit and then attach the media file to that version.

So there you have it, 3 options for localized PDFs in the Sitecore content management system.