Working with consents on MVC sites

Kentico EMS required

Features described on this page require the Kentico EMS license.

In Kentico, 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 Kentico instance and in the MVC application.

This page describes how to set up consent functionality on MVC sites. To learn how to create consents in the Kentico 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 Kentico database.

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 Kentico instance:

  1. Open the Settings application.

  2. Navigate to the System category in the settings tree.

  3. Set the value of the Default cookie level setting to System or Essential.

    Note: With the System cookie level, basic functionality such as authentication may not work for visitors by default (if your authentication cookie is registered with the Essential level). See Integrating Kentico membership for more information.

  4. Click Save.

To allow visitors to give and revoke agreements with the tracking consent on your MVC site, you need to develop corresponding components.

Start by defining the tracking consent in the Kentico administration interface (in the Data protection application on the Consents tab).

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 Kentico 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;

        public TrackingConsentController()
        {
            consentAgreementService = Service.Resolve<IConsentAgreementService>();
            currentCookieLevelProvider = Service.Resolve<ICurrentCookieLevelProvider>();
        }



  • 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 Kentico
                  ConsentInfo consent = ConsentInfoProvider.GetConsentInfo("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 Tracking contacts on MVC sites).

    • 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.GetConsentInfo("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.GetConsentInfo("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.

  1. Create an appropriate consent in the Data protection application.
  2. Open the Forms application and edit the form for which you want to collect consent agreements.
  3. Switch to the Form builder tab.
  4. Add a new field and select the Consent agreement form component (see Composing forms for detailed instructions).
  5. Select the related Consent in the field’s Properties panel.
  6. Click Apply.
  7. Move the consent field to the appropriate location within the form.
    Adding a consent agreement field to a form
  8. 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:

  1. Prepare an appropriate consent in the Kentico administration interface (in the Data protection application on the Consents tab).
  2. Expand the related controllers, models, and views in your MVC project:
    1. Display the consent text in the appropriate form (registration or subscription), along with an input that allows visitors to give their consent.

    2. 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.

    3. Get the visitor’s contact (see Tracking contacts on MVC sites) and the appropriate consent object (by calling the ConsentInfoProvider.GetConsentInfo method).

    4. 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 Kentico 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).

The following code samples demonstrate how to adjust the model, controller and view that provide your MVC site’s user registration functionality.

Example - Extended registration view model



public class RegisterWithConsentViewModel : RegisterViewModel
{
    public string ConsentShortText { get; set; }

    public bool ConsentIsAgreed { get; set; }
}


Example - Registration controller with consent handling



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.EventLog;
using CMS.Membership;

namespace LearningKit.Controllers
{
    public class RegisterWithConsentController : Controller
    {
        private readonly IFormConsentAgreementService formConsentAgreementService;
        private readonly ConsentInfo consent;

        /// <summary>
        /// Constructor.
        /// You can use a dependency injection container to initialize the consent agreement service.
        /// </summary>
        public RegisterWithConsentController()
        {
            formConsentAgreementService = Service.Resolve<IFormConsentAgreementService>();

            // Gets the related consent
            // Fill in the code name of the appropriate consent object in Kentico
            consent = ConsentInfoProvider.GetConsentInfo("SampleRegistrationConsent");
        }

        /// <summary>
        /// Provides access to the Kentico.Membership.SignInManager instance.
        /// </summary>
        public SignInManager SignInManager
        {
            get
            {
                return HttpContext.GetOwinContext().Get<SignInManager>();
            }
        }

        /// <summary>
        /// Provides access to the Kentico.Membership.UserManager instance.
        /// </summary>
        public UserManager UserManager
        {
            get
            {
                return HttpContext.GetOwinContext().Get<UserManager>();
            }
        }

        /// <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 UserManager.CreateAsync(user, model.Password);
            }
            catch (Exception ex)
            {
                // Logs an error into the Kentico event log if the creation of the user fails
                EventLogProvider.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.GetUserInfo(user.Id));
            }

            // If the registration was successful, signs in the user and redirects to a different action
            await SignInManager.SignInAsync(user, true, false);
            return RedirectToAction("Index", "Home");
        }
    }
}


Example - Consent checkbox and text label in a registration form view



    <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.

  1. Initialize an instance of the IConsentAgreementService interface and make it available in your code.
  2. Obtain the related contact and consent objects.
  3. Evaluate whether the contact has given an agreement with the given consent by calling the IsAgreed method of the IConsentAgreementService instance.

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 Kentico 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 Working with 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 Kentico API (and any other namespaces that you need):

    
    
    
      using CMS.ContactManagement;
      using CMS.Core;
      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;
    
              public PrivacyPageController()
              {         
                  consentAgreementService = Service.Resolve<IConsentAgreementService>();
                  currentCookieLevelProvider = Service.Resolve<ICurrentCookieLevelProvider>();
              }
    
    
    
      

    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:

    1. Retrieve the visitor’s contact using the ContactManagementContext API (see Tracking contacts on MVC sites).
    2. Get the consents for which the contact has given an agreement by calling the GetAgreedConsents method of the IConsentAgreementService instance.
    3. 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:

    1. Retrieve the current contact.
    2. 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.
    3. 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:

    1. Retrieve the current contact and the required consent (by calling the ConsentInfoProvider.GetConsentInfo method).

    2. 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.GetConsentInfo(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 Kentico 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.