I’ve seen a few questions recently regarding Virtual Users within Sitecore and asking how their data is persisted (if it is at all), so I thought I’d try to clear up any confusion around this.

What are virtual users?

A Virtual User within Sitecore is a transient entity used to represent an authenticated user on your site, unlike a regular user that is persisted within the Sitecore Core database (by default!). A common use-case for this scenario would be if you are using an external system for authentication. A Virtual User allows you to create an authenticated session for that user, without needing to create them in Sitecore. They don’t get fully persisted, and you won’t see them in the User Mananger. However, Sitecore treats them like any other user when it comes to roles + access rules. You’re even able to specify custom properties for virtual users.

Logging in a Virtual User is very straightforward. Here is a typical example for creating a new Virtual User and setting some roles + properties:

1
2
3
4
5
6
7
8
9
10
11
12
User virtualUser = Sitecore.Security.Authentication.AuthenticationManager.BuildVirtualUser("extranet\\[email protected]", true);
// Optional: Add the user to an existing extranet role
virtualUser.Roles.Add(Sitecore.Security.Accounts.Role.FromName("extranet\\CustomRole"));
// Optional: Set some custom properties
virtualUser.Profile.FullName = "Bertie Bassett";
virtualUser.Profile.Email = "[email protected]";
virtualUser.Profile.SetCustomProperty("DateOfBirth", "1980-01-01");
// Login the virtual user
Sitecore.Security.Authentication.AuthenticationManager.LoginVirtualUser(virtualUser);

After the LoginVirtualUser is called, an authenticated session is established and on future page requests, the Sitecore.Context.User will be extranet\[email protected].

How is the user authenticated?

A Virtual User is often referred to as an “in-memory” user, suggesting that the user data is held within memory and won’t survive a server restart. This isn’t really true, as we’ll see.

If you are using Forms Authentication, when the LoginVirtualUser method is called, a standard Forms Authentication cookie is created. By default, this has the name .ASPXAUTH, and will have an encrypted value such as this:

1
2B84953608A213A6D644F37F4723DCA1DC7CF534DCA0214342B00C8751AD667FC2FCA31E043074EC74C8AFC69EA7012DA9A70B622D011FA029632A09E8ED20B1906DE1FE4E597F96E547126ADF93867888EF34A64383E6870517559A5779A861B9F77ADC5EE3FE52B9FE15554B70C7F36C2A88A2B96484A3C5171DF174CC3CDD3EDC63D88C798886045E20B2B95FA40734D533C3AC9FAC76BF07C3397B4889E06F9FB4B3279506F2D2F26083024B5514310B4EEE

During an HTTP request, this data is decrypted into a FormsAuthenticationTicket. In this demo instance, it contains the following data:

1
2
3
4
5
6
7
8
9
10
{
"Version": 2,
"Name": "extranet\\[email protected]",
"Expiration": "2017-11-22T12:16:29.5653712+00:00",
"IssueDate": "2017-11-22T11:46:29.5653712+00:00",
"IsPersistent": false,
"Expired": false,
"UserData": "cj5qfethsr0z4ej1xjxkh4ox",
"CookiePath": "/"
}

This ticket alone is enough for Sitecore to consider the user authenticated as the specified user and allow access to any pages you may have set as restricted to anonymous users.

What about roles and properties?

In the above code example, we added the virtual user into the extranet\CustomRole role and also added some profile properties. As you can see, this information isn’t found within the ticket. So where is it? Well, note that there is a UserData property which contains a string, cj5qfethsr0z4ej1xjxkh4ox. That’s important.

If I take a look at the Core database for this instance, I find the following row in the ClientData table:

The value of SessionKey field refers to both the current ASP.NET Session ID and also the UserData value from the authentication ticket. The Data field is a Base64 encoding of a serialized Hashtable object. Within that, is a serialized instance of a Sitecore.SecurityModel.UserRuntimeSetings object that contains all of the Virtual User data, such as the roles they belong to, and their custom properties.

So as you can see, data is persisted for the Virtual User. Additionally, as the cookie is persisted on the user’s machine, the authenticated user and their data will survive a restart of the web process / server. If you have multiple load-balanced servers referencing the same Core database, then the Virtual User data will be available to each server, avoiding any load-balancing issues in this particular scenario.

It’s worth noting that this data survives if the ASP.NET Session ID is abandoned. In this instance, the session cookie is removed, but the authentication cookie remains. As this authentication cookie still retains the key within the UserData property, the data can still be pulled out of the Core database.

ClientData compacting

You should note that the ClientData table does get cleaned up on a regular basis. There is an agent responsible for this, and that executes every 4 hours by default:

1
2
3
4
5
<scheduling>
...
<agent type="Sitecore.Tasks.CompactClientDataAgent" method="Run" interval="04:00:00"/>
...
</scheduling>

This uses a parameter of the ClientData configuration to determine the object lifetime, which defaults to 20 minutes. Any rows which have not been accessed within that time in the table will be removed when the agent executes:

1
2
3
4
5
6
<clientDataStore type="Sitecore.Data.SqlServer.SqlServerClientDataStore, Sitecore.Kernel">
<param desc="connection string name" connectionStringName="core"/>
<param desc="object lifetime">00:20:00</param>
<param ref="eventing/eventQueueProvider/eventQueue[@name='core']" desc="event queue"/>
<param type="Sitecore.Abstractions.BaseEventManager, Sitecore.Kernel" desc="eventManager" resolve="true"/>
</clientDataStore>

So, without changes to this, you shouldn’t treat this storage of Virtual User data as a permenant. But then, it’s not intended to be - generally this would be storage of data you’re only interested in keeping for the duration of the user’s session. If you need something more than this you should look into an alternate method.

So… ClientData? Is that new?

The ClientData table has actually been within the Sitecore database for quite some time now, but the use of it to persist Virtual User information is relatively recent. In fact, it was introduced in Sitecore 8.1 rev. 160302 (8.1 Update-2).

So what happened before then? Well, in earlier versions of Sitecore, the user profile data was actually stored entirely within the authentication cookie:

1
2
3
4
5
6
7
8
9
10
{
"Version": 2,
"Name": "extranet\\[email protected]",
"Expiration": "2017-11-22T15:39:04.9367692+00:00",
"IssueDate": "2017-11-22T15:09:04.9367692+00:00",
"IsPersistent": false,
"Expired": false,
"UserData": "[{\"Key\":\"SC_USR_extranet\\\\[email protected]\",\"Value\":\"{\\\"AddedRoles\\\":[\\\"extranet\\\\\\\\CustomRole\\\"],\\\"IsAdministrator\\\":false,\\\"IsVirtual\\\":true,\\\"Properties\\\":[{\\\"Key\\\":\\\"SerializedData\\\",\\\"Value\\\":\\\"[{\\\\\\\"Key\\\\\\\":\\\\\\\"DateOfBirth\\\\\\\",\\\\\\\"Value\\\\\\\":\\\\\\\"1980-01-01\\\\\\\"}]\\\"},{\\\"Key\\\":\\\"Email\\\",\\\"Value\\\":\\\"[email protected]\\\"},{\\\"Key\\\":\\\"FullName\\\",\\\"Value\\\":\\\"Bertie Bassett\\\"}],\\\"RemovedRoles\\\":[]}\"}]",
"CookiePath": "/"
}

So, prior to Sitecore 8.1 Update 2, there was no dependency on the Core database for storing Virtual User profile data. As long as the cookie is in place, the Virtual User would be correctly restored.

Further information

That’s what I’ve found from my short dive into Virtual User operations. If you think I’ve stated anything inaccurate, please let me know! I’m going to be looking at this further and into any other common questions around Virtual Users that I see, so if needed I’ll provide a follow-up post with more detail.