This is the third of a new series of posts on ASP .NET Core 3.1 for 2020. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2020, titled ASP .NET Core A-Z! To differentiate from the 2019 series, the 2020 series will mostly focus on a growing single codebase (NetLearner!) instead of new unrelated code snippets week.
Previous post:
NetLearner on GitHub:
- Repository: https://github.com/shahedc/NetLearnerApp
- v0.3-alpha release: https://github.com/shahedc/NetLearnerApp/releases/tag/v0.3-alpha
In this Article:
- C is for Cookies and Consent
- Who Moved My Cookies?
- Browser Storage
- Partial Views for your cookie message
- Blazor Implementation
- Customizing your message
- Startup Configuration
- References
C is for Cookies and Consent
In this article, we’ll continue to look at the (in-progress) NetLearner application, which was generated using multiple ASP .NET Core web app project (3.1) templates. In previous releases, the template made it very easy for you to store cookies and display a cookie policy. However, the latest version doesn’t include cookie usage or a GDPR-compliant message out of the box.
Unless you’ve been living under a rock in the past couple of years, you’ve no doubt noticed all the GDPR-related emails and website popups since 2018. Whether or not you’re required by law to disclose your cookie policies, it’s good practice to reveal it to the end user so that they can choose to accept your cookies (or not).
Who Moved My Cookies?
In ASP .NET Core 2.x, the standard web app project templates provided by Microsoft included GDPR-friendly popup messages, that could be accepted by the end user to set a consent cookie. As of ASP .NET Core 3.x, this is no longer provided out of the box. However, you can still add this feature back into your project manually if needed.
Follow the instructions provided in the official docs to add this feature to your ASP .NET MVC or Razor Pages project:
- GDPR Support in ASP .NET Core: https://docs.microsoft.com/en-us/aspnet/core/security/gdpr?view=aspnetcore-3.1
But wait… how about Blazor web app projects? After asking a few other developers on Twitter, I decided to implement this feature myself, in my NetLearner repository. I even had the opportunity to answer a related question on Stack Overflow. Take a look at the following Blazor web project:
- NetLearner.Blazor: https://github.com/shahedc/NetLearnerApp/tree/main/src/NetLearner.Blazor
Browser Storage
As you probably know, cookies are attached to a specific browser installation and can be deleted by a user at an any time. Some new developers may not be aware of where these cookies are actually stored.
Click F12 in your browser to view the Developer Tools to see cookies grouped by website/domain.
- In (pre-Chromum) Edge/Firefox, expand Cookies under the Storage tab.
- In (Chromium-based) Edge/Chrome, expand Storage | Cookies under the Application tab .
See screenshots below for a couple of examples how AspNet.Consent in stored, along with a boolean Yes/No value:
Partial Views for your cookie message
The first time you launch a new template-generated ASP .NET Core 2.x web app, you should expect to see a cookie popup that appears on every page that can be dismissed by clicking Accept. Since we added it manually in our 3.x project, let’s explore the code to dig in a little further.
First, take a look at the _CookieConsentPartial.cshtml partial view in both the Razor Pages shared pages folder and the MVC shared views folder. The CSS class names and accessibility-friendly role attributes have been removed for brevity in the snippet below. For Razor Pages (in this example), this file should be in the /Pages/Shared/ folder by default. For MVC, this file should be in the /Views/Shared/ folder by default.
@using Microsoft.AspNetCore.Http.Features @{ var consentFeature = Context.Features.Get<ITrackingConsentFeature>(); var showBanner = !consentFeature?.CanTrack ?? false; var cookieString = consentFeature?.CreateConsentCookie(); } @if (showBanner) { <div id="cookieConsent"> <!-- CUSTOMIZED MESSAGE IN COOKIE POPUP --> <button type="button" data-dismiss="alert" data-cookie-string="@cookieString"> <span aria-hidden="true">Accept</span> </button> </div> <script> (function () { var button = document.querySelector("#cookieConsent button[data-cookie-string]"); button.addEventListener("click", function (event) { document.cookie = button.dataset.cookieString; }, false); })(); </script> }
This partial view has a combination of server-side C# code and client-side HTML/CSS/JavaScript code. First, let’s examine the C# code at the very top:
- The using statement at the top mentions the Microsoft.AspNetCore.Http.Features namespace, which is necessary to use ITrackingConsentFeature.
- The local variable consentFeature is used to get an instance ITrackingConsentFeature (or null if not present).
- The local variable showBanner is used to store the boolean result from the property consentFeature.CanTrack to check whether the user has consented or not.
- The local variable cookieString is used to store the “cookie string” value of the created cookie after a quick call to consentFeature.CreateConsentCookie().
- The @if block that follows only gets executed if showBanner is set to true.
Next, let’s examine the HTML that follows:
- The cookieConsent <div> is used to store and display a customized message for the end user.
- This <div> also displays an Accept <button> that dismisses the popup.
- The data-dismiss attribute ensures that the modal popup is closed when you click on it. This feature is available because we are using Bootstrap in our project.
- The data- attribute for “data-cookie-string” is set using the server-side variable value for @cookieString.
The full value for cookieString may look something like this, beginning with the .AspNet.Consent boolean value, followed by an expiration date.
".AspNet.Consent=yes; expires=Mon, 18 Jan 2021 21:55:01 GMT; path=/; secure; samesite=none"
Finally, let’s examine the JavaScript that follows within a <script> tag:
- Within the <script> tag, an anonymous function is defined and invoked immediately, by ending it with (); after it’s defined.
- A button variable is defined to represent the HTML button, by using an appropriate querySelector to retrieve it from the DOM.
- An eventListener is added to respond to the button’s onclick event.
- If accepted, a new cookie is created using the button’s aforementioned cookieString value.
To use the partial view in your application, simply insert it into the _Layout.cshtml page defined among both the Razor Pages shared pages folder and the MVC shared views folder. The partial view can be inserted above the call to RenderBody() as shown below.
<div class="container"> <partial name="_CookieConsentPartial" /> <main role="main" class="pb-3"> @RenderBody() </main> </div>
In an MVC application, the partial view can be inserted the same way, using the <partial> tag helper.
Blazor Implementation
Here are the steps I used in the Blazor project, tracked in the BlazorCookieExperiment branch:
- Update ConfigureServices() in Startup.cs to set up cookie usage
- Use JSInterop to set document.cookie in netLearnerJSInterop.js
- Update _Host.cshtml to include the .js file
- Observe code in _CookieConsentPartial.cshtml as reference
- Add _CookieConsentPartial.razor component in Shared folder
- Update MainLayout.razor to include above component
<div class="main"> <div class="top-row px-4 auth"> <LoginDisplay /> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> </div> <_CookieConsentPartial /> <div class="content px-4"> @Body </div> </div>
Customizing your message
You may have noticed that there is only an Accept option in the default cookie popup generated by the template’s Partial View. This ensures that the only way to store a cookie with the user’s consent is to click Accept in the popup.
You may be wondering whether you should also display a Decline option in the cookie popup. But that would be a bad idea, because that would require you to store the user’s “No” response in the cookie itself, thus going against their wishes. If you wish to allow the user to withdraw consent at a later time, take a look at the GrantConsent() and WithdrawConsent() methods provided by ITrackingConsentFeature.
But you can still change the message in the cookie popup and your website’s privacy policy. To change the cookie’s displayed message, simply change the text that appears in the _CookieConsentPartial.cshtml partial view (or equivalent Razor component for ther Blazor project), within the <div> of the client-side HTML. In the excerpt shown in the previous section, this region is identified by the <!– CUSTOMIZED MESSAGE IN COOKIE POPUP –> placeholder comment.
<div id="cookieConsent"> <!-- CUSTOMIZED MESSAGE IN COOKIE POPUP --> <button type="button" data-dismiss="alert" data-cookie-string="@cookieString"> <span aria-hidden="true">Accept</span> </button> </div>
Your message text is also a great place to provide a link to your website’s privacy policy. In the Razor Pages template, the <a> link is generated using a tag helper shown below. The /Privacy path points to the Privacy.cshtml Razor page in the /Pages folder.
<a asp-page="/Privacy">Learn More</a>
In a similar MVC application, you would find the Privacy.cshtml view within the /Views/Home/ folder, accessible via the Home controller’s Privacy() action method. In the MVC template, the <a> is link is generated using the following tag helper:
<a asp-area="" asp-controller="Home" asp-action="Privacy">Learn More</a>
Startup Configuration
None of the above would be possible without the necessary configuration. The cookie policy can be used by simply calling the extension method app.UseCookiePolicy() in the Configure() method of your Startup.cs file, in the root location of Razor Pages, MVC and Blazor projects.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ... app.UseCookiePolicy(); ... }
According to the official documentation, this “Adds the Microsoft.AspNetCore.CookiePolicy.CookiePolicyMiddleware handler to the specified Microsoft.AspNetCore.Builder.IApplicationBuilder, which enables cookie policy capabilities.”
The cool thing about ASP .NET Core middleware is that there are many IApplicationBuilder extension methods for the necessary Middleware components you may need to use. Instead of hunting down each Middleware component, you can simply type app.Use in the Configure() method to discover what is available for you to use.
If you remove the call to app.UseCookiePolicy(), this will cause the aforementioned consentFeature value to be set to null in the C# code of your cookie popup.
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
There is also some minimal configuration that happens in the ConfigureServices() method which is called before the Configure() method in your Startup.cs file.
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); ... }
The above code does a couple of things:
- As explained by the comment, the lambda (context => true) “determines whether user consent for non-essential cookies is needed for a given request” and then the CheckConsentNeeded boolean property for the options object is set to true or false.
- The property MinimumSameSitePolicy is set to SameSiteMode.None, which is an enumerator with the following possible values:
- Unspecified = -1
- None = 0
- Lax = 1
- Strict = 2
From the official documentation on cookie authentication, “ The Cookie Policy Middleware setting for MinimumSameSitePolicy
can affect the setting of Cookie.SameSite
in CookieAuthenticationOptions
settings. For more information, check out the documentation at:
- Cookie Authentication: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1
- SameSiteMode Enum: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.samesitemode?view=aspnetcore-3.1
References
- General Data Protection Regulation (GDPR) support in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/security/gdpr?view=aspnetcore-3.1
- Use cookie authentication without ASP.NET Core Identity: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1
- ITrackingConsentFeature Interface (Microsoft.AspNetCore.Http.Features): https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features.itrackingconsentfeature?view=aspnetcore-3.1
- HTMLElement.dataset: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset
- Using the alert role: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_alert_role
- HTML DOM querySelector() Method: https://www.w3schools.com/jsref/met_document_queryselector.asp