Cookies and Consent in ASP .NET Core 3.1
Tags: dotnet, csharp, webdev
Published: 2020-01-20 17:07:16

---
title: Cookies and Consent in ASP .NET Core 3.1
published: true
date: 2020-01-20 15:00:00 UTC
tags: dotnet, csharp, webdev 
canonical_url: https://wakeupandcode.com/cookies-and-consent-in-asp-net-core-3-1/
---

![](https://wakeupandcode.com/wp-content/uploads/2020/01/aspnetcore-az-banner.png)

This is the third of a new [series of posts](https://wakeupandcode.com/aspnetcore/#aspnetcore2020) 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](https://wakeupandcode.com/aspnetcore/#aspnetcore2019), the 2020 series will mostly focus on a growing single codebase ([NetLearner!](https://wakeupandcode.com/netlearner-on-asp-net-core-3-1/)) instead of new unrelated code snippets week.

Previous post:

- [Blazor Full-Stack Web Dev in ASP .NET Core 3.1](https://wakeupandcode.com/blazor-full-stack-web-dev-in-asp-net-core-3-1/)

**NetLearner on GitHub** :

- Repository: [https://github.com/shahedc/NetLearnerApp](https://github.com/shahedc/NetLearnerApp)
- v0.3-alpha release: [https://github.com/shahedc/NetLearnerApp/releases/tag/v0.3-alpha](https://github.com/shahedc/NetLearnerApp/releases/tag/v0.3-alpha)

# In this Article:

- [C is for Cookies and Consent](#C)
- [Who Moved My Cookies?](#moved)
- [Browser Storage](#storage)
- [Partial Views for your cookie message](#message)
- [Blazor Implementation](#blazor)
- [Customizing your message](#customize)
- [Startup Configuration](#startup)
- [References](#refs)

# C is for Cookies and Consent

In this article, we’ll continue to look at the (in-progress) [NetLearner](https://wakeupandcode.com/netlearner-asp-net-core-internet-learning-helper/) 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](https://docs.microsoft.com/en-us/aspnet/core/security/gdpr?view=aspnetcore-3.1) 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](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](https://twitter.com/shahedC/status/1217528728290504704), I decided to implement this feature myself, in my [NetLearner](https://github.com/shahedc/NetLearnerApp) repository. I even had the opportunity to answer a [related question on Stack Overflow](https://twitter.com/shahedC/status/1217528741011841030). Take a look at the following Blazor web project:

- NetLearner.Blazor: [https://github.com/shahedc/NetLearnerApp/tree/master/src/NetLearner.Blazor](https://github.com/shahedc/NetLearnerApp/tree/master/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:

![Cookies in Chromium-based Edge](https://wakeupandcode.com/wp-content/uploads/2020/01/cookies-f12-edge-chromium-1024x599.png)<figcaption> Cookies in Chromium-based Edge </figcaption>

![](https://wakeupandcode.com/wp-content/uploads/2020/01/cookies-f12-chrome-1024x599.png)<figcaption>Cookies in Google Chrome</figcaption>

![Cookies in Mozilla Firefox](https://wakeupandcode.com/wp-content/uploads/2020/01/cookies-f12-firefox-1024x599.png)<figcaption> Cookies in Mozilla Firefox</figcaption>

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

![GDPR-compliant cookie message](https://wakeupandcode.com/wp-content/uploads/2020/01/cookies-message-1024x599.png)<figcaption>GDPR-compliant cookie message</figcaption>

First, take a look at the \_CookieConsentPartial.cshtml partial view in both the Razor Pages [shared pages folder](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Pages/Pages/Shared/_CookieConsentPartial.cshtml) and the MVC [shared views folder](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Mvc/Views/Shared/_CookieConsentPartial.cshtml). The CSS class names and [accessibility-friendly role attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_alert_role) 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:

1. The using statement at the top mentions the  **Microsoft.AspNetCore.Http.Features**  namespace, which is necessary to use  **ITrackingConsentFeature.**
2. The local variable  **consentFeature**  is used to get an instance  **ITrackingConsentFeature**  (or null if not present).
3. 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.
4. The local variable  **cookieString**  is used to store the “cookie string” value of the created cookie after a quick call to  **consentFeature.CreateConsentCookie** ().
5. The @ **if**  block that follows only gets executed if  **showBanner**  is set to true.

Next, let’s examine the HTML that follows:

1. The  **cookieConsent**  \<div\> is used to store and display a customized message for the end user.
2. This \<div\> also displays an  **Accept**  \<button\> that dismisses the popup.
3. 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.
4. 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:

1. Within the  **\<script\>**  tag, an anonymous function is defined and invoked immediately, by ending it with **();** after it’s defined.
2. A  **button**  variable is defined to represent the HTML button, by using an appropriate  **querySelector**  to retrieve it from the DOM.
3. An eventListener is added to respond to the button’s onclick event.
4. 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](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Pages/Pages/Shared/_Layout.cshtml) and the MVC [shared views folder](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Mvc/Views/Shared/_Layout.cshtml). 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](https://github.com/shahedc/NetLearnerApp/tree/BlazorCookieExperiment):

1. Update ConfigureServices() in [Startup.cs](https://github.com/shahedc/NetLearnerApp/blob/BlazorCookieExperiment/src/NetLearner.Blazor/Startup.cs) to set up cookie usage
2. Use JSInterop to set document.cookie in [netLearnerJSInterop.js](https://github.com/shahedc/NetLearnerApp/blob/BlazorCookieExperiment/src/NetLearner.Blazor/wwwroot/netLearnerJsInterop.js)
3. Update [\_Host.cshtml](https://github.com/shahedc/NetLearnerApp/blob/BlazorCookieExperiment/src/NetLearner.Blazor/Pages/_Host.cshtml) to include the .js file 
4. Observe code in [\_CookieConsentPartial.cshtml](https://github.com/shahedc/NetLearnerApp/blob/BlazorCookieExperiment/src/NetLearner.Blazor/Shared/_CookieConsentPartial.cshtml.txt) as reference
5. Add [\_CookieConsentPartial.razor](https://github.com/shahedc/NetLearnerApp/blob/BlazorCookieExperiment/src/NetLearner.Blazor/Shared/_CookieConsentPartial.razor) component in Shared folder
6. Update [MainLayout.razor](https://github.com/shahedc/NetLearnerApp/blob/BlazorCookieExperiment/src/NetLearner.Blazor/Shared/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](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features.itrackingconsentfeature?view=aspnetcore-3.1).

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](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Pages/Pages/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](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Pages/Startup.cs), [MVC](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Mvc/Startup.cs) and [Blazor](https://github.com/shahedc/NetLearnerApp/blob/b06c276e7f86709cad9c0d1dcc392be13c9e7a5c/src/NetLearner.Blazor/Startup.cs) 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.

![app.UseCookiePolicy() in Startup.cs](https://wakeupandcode.com/wp-content/uploads/2020/01/cookies-app-use-1024x306.png)<figcaption>app.UseCookiePolicy() in Startup.cs</figcaption>

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** \>();
```

![cookieString is null if Cookie Policy disabled](https://wakeupandcode.com/wp-content/uploads/2020/01/cookies-disabled-null-1024x324.png)<figcaption>cookieString is null if Cookie Policy disabled</figcaption>

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:

1. 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.
2. 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](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1) 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](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](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](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](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](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](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](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](https://www.w3schools.com/jsref/met_document_queryselector.asp)