Working with consents
Enterprise license required
Features described on this page require the Kentico Xperience Enterprise license.
In Xperience, consents are records used to inform website visitors about the means of collecting and manipulating their personal data by the system, site administrators, marketers and anyone else who has access to said data, including third-parties to whom this data is forwarded.
You can use consents to comply with the requirements of the GDPR and other personal data regulations.
Whenever any personal data of a visitor is obtained, it is necessary to have a consent agreement from the visitor to legally process this data. This includes tracking of contacts and their activities on the live site. Every consent agreement of a visitor is directly connected to a corresponding contact. Deleting contacts from the system therefore also causes deletion of the contact’s consent agreements.
Prerequisite
Since consent agreements are directly connected to contacts, you need to enable contact tracking in your connected Xperience instance and in the live site application.
This page describes how to set up consent functionality on MVC sites. To learn how to create consents in the Xperience administration interface, see Creating consents.
Tip: To view the full code of a functional example, you can inspect and download the LearningKit project on GitHub. You can also run the LearningKit website by connecting the project to a Xperience database.
Setting up tracking consent
When creating a consent for the tracking of contacts and activities, you need to provide a way for visitors to adjust their allowed cookie level. The system only performs the tracking for visitors whose cookie level is Visitoror higher.
To ensure that visitors are not tracked until they give the tracking consent, set the default cookie level for the website in your connected Xperience instance:
Open the Settings application.
Navigate to the System category in the settings tree.
Set the value of the Default cookie level setting to System or Essential.
Note: When setting this value, keep in mind the cookie level configured for custom cookies required by your project (for example the Identity authentication cookie).
Click Save.
To allow visitors to give and revoke agreements with the tracking consent on your MVC site, you need to develop corresponding components.
Creating the consent
Start by defining the tracking consent in the Xperience administration interface (in the Data protection application on the Consents tab).
Creating the consent model
We recommend creating a view model class in your MVC project to represent data related to the tracking consent. Include the following properties:
- A string property for storing the consent’s short text
- A boolean property to indicate whether an agreement with the consent was given
public class TrackingConsentViewModel
{
public string ShortText { get; set; }
public bool IsAgreed { get; set; }
}
Creating the controller
Create a new controller class in your MVC project or edit an existing one:
Add using statements for the following namespace from the Xperience API (and any other namespaces that you need):
using CMS.ContactManagement; using CMS.Core; using CMS.DataProtection; using CMS.Helpers;
Initialize instances of the following services, which are necessary to manage the tracking consent:
- IConsentAgreementService– to manage consent agreements.
- ICurrentCookieLevelProvider– to manage the current visitor’s cookie level.
We recommend using a dependency injection container to initialize service instances.
private readonly IConsentAgreementService consentAgreementService;
private readonly ICurrentCookieLevelProvider currentCookieLevelProvider;
private readonly IConsentInfoProvider consentInfoProvider;
public TrackingConsentController(IConsentAgreementService consentAgreementService,
ICurrentCookieLevelProvider currentCookieLevelProvider,
IConsentInfoProvider consentInfoProvider)
{
this.consentAgreementService = consentAgreementService;
this.currentCookieLevelProvider = currentCookieLevelProvider;
this.consentInfoProvider = consentInfoProvider;
}
Implement a GET action that displays information about the tracking consent:
public ActionResult DisplayConsent() { // Gets the related tracking consent // Fill in the code name of the appropriate consent object in Xperience ConsentInfo consent = consentInfoProvider.Get("SampleTrackingConsent"); // Gets the current contact ContactInfo currentContact = ContactManagementContext.GetCurrentContact(); // Sets the default cookie level for contacts who have revoked the tracking consent // Required for scenarios where one contact uses multiple browsers if ((currentContact != null) && !consentAgreementService.IsAgreed(currentContact, consent)) { var defaultCookieLevel = currentCookieLevelProvider.GetDefaultCookieLevel(); currentCookieLevelProvider.SetCurrentCookieLevel(defaultCookieLevel); } var consentModel = new TrackingConsentViewModel { // Adds the consent's short text to the model ShortText = consent.GetConsentText("en-US").ShortText, // Checks whether the current contact has given an agreement for the tracking consent IsAgreed = (currentContact != null) && consentAgreementService.IsAgreed(currentContact, consent) }; return View("Consent", consentModel); }
Implement POST actions for creating and revoking consent agreements:
Retrieve the visitor’s contact using the ContactManagementContext API (see Setting up contact tracking).
Modify the visitor’s cookie level according to the performed action – call the SetCurrentCookieLevel method of the ICurrentCookieLevelProvider instance.
- Set the All level when creating tracking consent agreements.
- Set the default cookie level from the site’s settings when revoking agreements.
Create or revoke the consent agreement using the Agree or Revoke methods of the IConsentAgreementService instance.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Accept()
{
// Gets the related tracking consent
ConsentInfo consent = consentInfoProvider.Get("SampleTrackingConsent");
// Sets the visitor's cookie level to 'All' (enables contact tracking)
currentCookieLevelProvider.SetCurrentCookieLevel(CookieLevel.All);
// Gets the current contact and creates a consent agreement
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
consentAgreementService.Agree(currentContact, consent);
return RedirectToAction("DisplayConsent");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Revoke()
{
// Gets the related tracking consent
ConsentInfo consent = consentInfoProvider.Get("SampleTrackingConsent");
// Gets the current contact and revokes the tracking consent agreement
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
consentAgreementService.Revoke(currentContact, consent);
// Sets the visitor's cookie level to the site's default cookie level (disables contact tracking)
int defaultCookieLevel = currentCookieLevelProvider.GetDefaultCookieLevel();
currentCookieLevelProvider.SetCurrentCookieLevel(defaultCookieLevel);
return RedirectToAction("DisplayConsent");
}
Creating the view
To display the tracking consent and inputs for giving or revoking agreements on your website, add an appropriate view to your MVC project. For example, you can add the consent presentation to a partial view that is included in your MVC website’s main layout. This ensures that the input to give the tracking consent is displayed on all pages.
Depending on your preferences and legal requirements, you can either hide the view’s output for visitors who have given the tracking consent or display content that allows the consent to be revoked.
Visitors on your website now have an option to give agreements with your tracking consent. New visitors are not tracked as contacts until they positively confirm that they agree with the tracking.
Adding consents to forms
Your MVC site may contain forms that allow visitors to submit personal data. To collect consent agreements regarding the processing of this data, you need to add a form field based on the Consent agreement form component.
- Create an appropriate consent in the Data protection application.
- Open the Forms application and edit the form for which you want to collect consent agreements.
- Switch to the Form builder tab.
- Add a new field and select the Consent agreement form component (see Composing forms for detailed instructions).
- Select the related Consent in the field’s Properties panel.
- Click Apply.
- Move the consent field to the appropriate location within the form.
- Select the Contact mapping tab and map the form’s fields to the appropriate contact attributes (see Mapping fields to contact attributes).
- To ensure that consent agreements are recorded correctly in all cases, include an email address field in your form and map it to the email address contact attribute.
The consent field in the resulting form displays a check box, followed by the Short text of the given consent. When a visitor selects the consent checkbox and submits the form, the system automatically creates a corresponding consent agreement for the given contact and stores the agreement’s identifier (GUID) into the corresponding form field.
Note: The system does not support consent fields that are required. Validation would always fail for required consent fields (the component sets the field’s value only after the form is submitted).
Consent agreement storage
The system stores consent agreements in the CMS_ConsentAgreement database table.
If a user submits a form with a consent agreement field multiple times, a separate agreement record is created for every submission. For submissions where the consent checkbox is not selected, the created agreement represents a revoked consent (the value in the ConsentAgreementRevoked column is set to 1). The consent status of a contact is evaluated according to their most recent agreement record for a given consent – the consent is considered as revoked if the latest agreement has the revoked flag.
Adding registration and subscription consents
You can collect consent agreements in any scenarios where visitors submit an input form. For example, when registering as users on the website or subscribing to a newsletter.
To set up the consent functionality:
- Prepare an appropriate consent in the Xperience administration interface (in the Data protection application on the Consents tab).
- Expand the related controllers, models, and views in your MVC project:
Display the consent text in the appropriate form (registration or subscription), along with an input that allows visitors to give their consent.
Initialize an instance of the IFormConsentAgreementService interface and make it available in the controller’s code.
We recommend using a dependency injection container to initialize service instances.
Get the visitor’s contact (see Setting up contact tracking) and the appropriate consent object (by calling the ConsentInfoProvider.GetConsentInfo method).
In the post action that handles the submitted data, evaluate whether the consent was given and call the Agree method of the IFormConsentAgreementService instance to create a consent agreement.
IFormConsentAgreementService and IConsentAgreementService
The IFormConsentAgreementService.Agree method automatically handles scenarios where the contact parameter is null (for example for visitors who have not given an agreement with the tracking consent). In these cases, the method creates a new contact object with the related data and connects it with the consent agreement.
If you only wish to create the consent in cases where contact tracking is enabled, you can add a null condition for the visitor’s contact object and use the IConsentAgreementService.Agree method instead.
Visitors can now give their consent with personal data processing when submitting the corresponding forms (e.g. during registration or when subscribing to a newsletter).
To ensure that consent agreements are recorded correctly in all cases:
- The related form must contain an email address field (e.g. your registration or newsletter subscription form).
- The corresponding class in Xperience must be configured to map the email address field to the email address contact attribute. You can configure the mapping for the User class (registration) and Newsletter - Subscriber class (newsletter subscription).
Example – Registration with consent
The following code samples demonstrate how to adjust the model, controller and view that provide your MVC site’s user registration functionality.
public class RegisterWithConsentViewModel : RegisterViewModel
{
public string ConsentShortText { get; set; }
public bool ConsentIsAgreed { get; set; }
}
using System;
using System.Web;
using System.Web.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Kentico.Membership;
using CMS.ContactManagement;
using CMS.Core;
using CMS.DataProtection;
using CMS.Membership;
namespace LearningKit.Controllers
{
public class RegisterWithConsentController : Controller
{
private readonly IEventLogService eventLogService;
private readonly IFormConsentAgreementService formConsentAgreementService;
private readonly IUserInfoProvider userInfoProvider;
private readonly ConsentInfo consent;
/// <summary>
/// Constructor.
/// You can use a dependency injection container to initialize required services and providers.
/// </summary>
public RegisterWithConsentController(IEventLogService eventLogService,
IFormConsentAgreementService formConsentAgreementService,
IUserInfoProvider userInfoProvider,
IConsentInfoProvider consentInfoProvider)
{
this.eventLogService = eventLogService;
this.formConsentAgreementService = formConsentAgreementService;
this.userInfoProvider = userInfoProvider;
// Gets the related consent
// Fill in the code name of the appropriate consent object in Kentico
consent = consentInfoProvider.Get("SampleRegistrationConsent");
}
/// <summary>
/// Provides access to the Kentico.Membership.KenticoSignInManager instance.
/// </summary>
public KenticoSignInManager KenticoSignInManager
{
get
{
return HttpContext.GetOwinContext().Get<KenticoSignInManager>();
}
}
/// <summary>
/// Provides access to the Kentico.Membership.KenticoUserManager instance.
/// </summary>
public KenticoUserManager KenticoUserManager
{
get
{
return HttpContext.GetOwinContext().Get<KenticoUserManager>();
}
}
/// <summary>
/// Basic action that displays the registration form.
/// </summary>
public ActionResult Register()
{
var model = new RegisterWithConsentViewModel
{
// Adds the consent text to the registration model
ConsentShortText = consent.GetConsentText("en-US").ShortText,
ConsentIsAgreed = false
};
return View("RegisterWithConsent", model);
}
/// <summary>
/// Handles creation of new users and consent agreements when the registration form is submitted.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public async Task<ActionResult> Register(RegisterWithConsentViewModel model)
{
// Validates the received user data based on the view model
if (!ModelState.IsValid)
{
model.ConsentShortText = consent.GetConsentText("en-US").ShortText;
return View("RegisterWithConsent", model);
}
// Prepares a new user entity using the posted registration data
Kentico.Membership.User user = new User
{
UserName = model.UserName,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
Enabled = true // Enables the new user directly
};
// Attempts to create the user in the Kentico database
IdentityResult registerResult = IdentityResult.Failed();
try
{
registerResult = await KenticoUserManager.CreateAsync(user, model.Password);
}
catch (Exception ex)
{
// Logs an error into the Kentico event log if the creation of the user fails
eventLogService.LogException("MvcApplication", "UserRegistration", ex);
ModelState.AddModelError(String.Empty, "Registration failed");
}
// If the registration was not successful, displays the registration form with an error message
if (!registerResult.Succeeded)
{
foreach (string error in registerResult.Errors)
{
ModelState.AddModelError(String.Empty, error);
}
model.ConsentShortText = consent.GetConsentText("en-US").ShortText;
return View("RegisterWithConsent", model);
}
// Creates a consent agreement if the consent checkbox was selected in the registration form
if (model.ConsentIsAgreed)
{
// Gets the current contact
var currentContact = ContactManagementContext.GetCurrentContact();
// Creates an agreement for the specified consent and contact
// Passes the UserInfo object of the new user as a parameter, which is used to map the user's values
// to a new contact in cases where the contact parameter is null,
// e.g. for visitors who have not given an agreement with the site's tracking consent.
formConsentAgreementService.Agree(currentContact, consent, userInfoProvider.Get(user.Id));
}
// If the registration was successful, signs in the user and redirects to a different action
await KenticoSignInManager.SignInAsync(user, true, false);
return RedirectToAction("Index", "Home");
}
}
}
<div>
@Html.EditorFor(model => model.ConsentIsAgreed)
@Html.LabelFor(model => model.ConsentIsAgreed, Model.ConsentShortText)
</div>
Creating conditions based on consents
If you have any functionality that stores or processes personal data, you can create conditions to ensure that it only runs for contacts who have given consent.
- Initialize an instance of the IConsentAgreementService interface and make it available in your code.
- Obtain the related contact and consent objects.
- Evaluate whether the contact has given an agreement with the given consent by calling the IsAgreed method of the IConsentAgreementService instance.
Revoking consent agreements
To ensure compliance of your website with the GDPR or other data protection regulations, you need to give visitors the option to revoke their previously given consent agreements. We recommend creating a privacy page, where visitors can view the consents for which they have given agreements and potentially revoke them.
“It shall be as easy to withdraw as to give consent.”
(Source: GDPR Article 7, Paragraph 3)
The following sections describe how to create a privacy page on MVC sites. The privacy page lists accepted consents for visitors, and allows them to view the full consent details or revoke their agreements.
- Create a model representing the list of consents for which visitors have given agreements.
- Create a model representing consent details.
- Create a controller that provides actions for displaying and revoking consents.
- Create views for the consent list and details pages.
Creating the models
Add a model class that represents the list of consents for which visitors have given agreements.
using System.Collections.Generic; using System.Linq; using CMS.DataProtection; namespace LearningKit.Models.PrivacyPage { public class ConsentListingModel { public IEnumerable<Consent> Consents { get; set; } = Enumerable.Empty<Consent>(); } }
Consent and ConsentInfo objects
The Xperience API uses ConsentInfo objects to work with consents in general. However, when you load consents for which a contact has given an agreement, the ConsentAgreementService.GetAgreedConsents method returns a collection of Consent objects. Instances of the Consent class provide the GetConsentText method, which automatically retrieves either the current texts of the given consent or the texts of the archived consent version for which the agreement was given.
When displaying accepted consents to visitors, always work with Consent objects to ensure that the correct consent text version is used (for scenarios where the consent text was changed after agreements had been given).
For more information, see the Viewing archived consents section of the Creating consents page.
Tip: You can directly use an IEnumerable<Consent> collection as a model if you do not need to work with any other data in your controller or views. However, having a dedicated model class allows you to extend the model based on future requirements.
Add a model class that represents the full details of a consent, including properties for the short text, full text, and any other information about the consent you may need.
namespace LearningKit.Models.PrivacyPage { public class ConsentDetailsModel { public string ConsentShortText { get; set; } public string ConsentFullText { get; set; } public string ConsentDisplayName { get; set; } } }
Creating the controller
Create a new controller class in your MVC project or edit an existing one:
Add using statements for the following namespaces from the Xperience API (and any other namespaces that you need):
using CMS.ContactManagement; using CMS.DataProtection; using CMS.Helpers;
Initialize instances of the IConsentAgreementService and ICurrentCookieLevelProvider interfaces, and make them available in the controller’s code:
private readonly IConsentAgreementService consentAgreementService; private readonly ICurrentCookieLevelProvider currentCookieLevelProvider; private readonly IConsentInfoProvider consentInfoProvider; public PrivacyPageController(IConsentAgreementService consentAgreementService, ICurrentCookieLevelProvider currentCookieLevelProvider, IConsentInfoProvider consentInfoProvider) { this.consentAgreementService = consentAgreementService; this.currentCookieLevelProvider = currentCookieLevelProvider; this.consentInfoProvider = consentInfoProvider; }
We recommend using a dependency injection container to initialize service instances.
Implement a GET action which retrieves a list of all consents accepted by the visitor:
- Retrieve the visitor’s contact using the ContactManagementContext API (see Setting up contact tracking).
- Get the consents for which the contact has given an agreement by calling the GetAgreedConsents method of the IConsentAgreementService instance.
- Create an instance of the consent list model class and pass it to the view.
/// <summary>
/// Loads and displays consents for which visitors have given agreements.
/// </summary>
public ActionResult Index()
{
// Gets the current visitor's contact
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
var consentListingModel = new ConsentListingModel();
// Does not attempt to load consent data if the current contact is not available
// This occurs if contact tracking is disabled, or for visitors who have not given an agreement with the tracking consent
if (currentContact != null)
{
// Gets all consents for which the current contact has given an agreement
consentListingModel.Consents = consentAgreementService.GetAgreedConsents(currentContact);
}
return View(consentListingModel);
}
Implement a GET action which retrieves the full details of a specified consent:
- Retrieve the current contact.
- Get the consents for which the contact has given an agreement using the consent agreement service, and select the required consent based on the action’s parameter. This approach ensures that the correct consent text version is used in cases where the text was changed after the agreement had been given.
- Create an instance of the consent details model class, set its properties, and pass it to the view.
/// <summary>
/// Display details of the specified consent.
/// </summary>
public ActionResult ConsentDetails(int consentId)
{
// Gets a list of consents for which the current contact has given an agreement
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
IEnumerable<Consent> consents = consentAgreementService.GetAgreedConsents(currentContact);
// Gets the consent matching the identifier for which the details were requested
// Using this approach to get objects of the 'Consent' class is necessary to ensure that the correct consent text
// is displayed, either from the current consent text or the archived consent version for which the agreement was given
Consent consent = consents.FirstOrDefault(c => c.Id == consentId);
// Displays the privacy page (consent list) if the specified consent identifier is not valid
if (consent == null)
{
return View("Index");
}
// Sets the consent's details into the view model
var model = new ConsentDetailsModel
{
ConsentDisplayName = consent.DisplayName,
ConsentShortText = consent.GetConsentText("en-US").ShortText,
ConsentFullText = consent.GetConsentText("en-US").FullText
};
return View(model);
}
Implement a POST action that revokes agreements for a specified consent:
Retrieve the current contact and the required consent (by calling the ConsentInfoProvider.GetConsentInfo method).
Create a revocation for the consent agreement using the Revoke method of the IConsentAgreementService instance.
When revoking the tracking consent, you also need to lower the visitor’s cookie level to ensure that contact tracking is disabled. Call the SetCurrentCookieLevel method of the ICurrentCookieLevelProvider instance and set the default cookie level from the site’s settings.
/// <summary>
/// Revokes the current contact's agreement with the specified consent.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Revoke(int consentId)
{
// Gets the related consent object
ConsentInfo consent = consentInfoProvider.Get(consentId);
// Gets the current visitor's contact
ContactInfo currentContact = ContactManagementContext.GetCurrentContact();
// For the tracking consent, lowers the cookie level to the site's default in order to disable tracking
if (consent.ConsentName == "SampleTrackingConsent")
{
currentCookieLevelProvider.SetCurrentCookieLevel(currentCookieLevelProvider.GetDefaultCookieLevel());
}
// Revokes the consent agreement
consentAgreementService.Revoke(currentContact, consent);
return RedirectToAction("Index");
}
Clearing personal data for revoked consents
When a visitor revokes a consent agreement, you may also need to delete or anonymize certain types of personal data stored by the system.
To perform additional actions of this type whenever a consent agreement is revoked, assign a custom handler to the system’s DataProtectionEvents.RevokeConsentAgreement global event.
See also: Implementing personal data erasure
Creating the views
Add appropriate views to your MVC project to display the consent list (privacy page) and the consent details.
Consent text values may contain HTML tags added via the editor in the Xperience administration interface (for formatting or content such as links). To ensure that the text is displayed correctly, disable the HTML encoding for the values using the Html.Raw method or the Html.Kentico().ResolveUrls extension method (also handles correct resolving of link URLs).
For example:
<h3>Short text</h3>
<p>@Html.Raw(Model.ConsentShortText)</p>
<h3>Full text</h3>
<p>@Html.Kentico().ResolveUrls(Model.ConsentFullText)</p>
Visitors on your site are now able to view a list of all consents for which they have given an agreement. They can also access the full details of the given consents and revoke their agreements if required.