Work with SameSite cookies in ASP.NET Core
SameSite is an IETF draft standard designed to provide some protection against cross-site request forgery (CSRF) attacks. Originally drafted in 2016, the draft standard was updated in 2019. The updated standard is not backward compatible with the previous standard, with the following being the most noticeable differences:
- Cookies without SameSite header are treated as
SameSite=Lax
by default. SameSite=None
must be used to allow cross-site cookie use.- Cookies that assert
SameSite=None
must also be marked asSecure
. - Applications that use
<iframe>
may experience issues withsameSite=Lax
orsameSite=Strict
cookies because<iframe>
is treated as cross-site scenarios. - The value
SameSite=None
is not allowed by the 2016 standard and causes some implementations to treat such cookies asSameSite=Strict
. See Supporting older browsers in this document.
The SameSite=Lax
setting works for most application cookies. Some forms of authentication like OpenID Connect (OIDC) and WS-Federation default to POST based redirects. The POST based redirects trigger the SameSite browser protections, so SameSite is disabled for these components. Most OAuth logins are not affected due to differences in how the request flows.
Each ASP.NET Core component that emits cookies needs to decide if SameSite is appropriate.
SameSite and Identity
ASP.NET Core Identity is largely unaffected by SameSite cookies except for advanced scenarios like IFrames
or OpenIdConnect
integration.
When using Identity
, do not add any cookie providers or call services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
, Identity
takes care of that.
SameSite test sample code
The following sample can be downloaded and tested:
Sample | Document |
---|---|
.NET Core Razor Pages | ASP.NET Core 3.1 Razor Pages SameSite cookie sample |
.NET Core support for the sameSite attribute
.NET Core supports the 2019 draft standard for SameSite. Developers are able to programmatically control the value of the sameSite attribute using the HttpCookie.SameSite
property. Setting the SameSite
property to Strict
, Lax
, or None
results in those values being written on the network with the cookie. Setting to SameSiteMode.Unspecified
indicates no sameSite should be sent with the cookie.
var cookieOptions = new CookieOptions
{
// Set the secure flag, which Chrome's changes will require for SameSite none.
// Note this will also require you to be running on HTTPS.
Secure = true,
// Set the cookie to HTTP only which is good practice unless you really do need
// to access it client side in scripts.
HttpOnly = true,
// Add the SameSite attribute, this will emit the attribute with a value of none.
SameSite = SameSiteMode.None
// The client should follow its default cookie policy.
// SameSite = SameSiteMode.Unspecified
};
// Add the cookie to the response cookie collection
Response.Cookies.Append("MyCookie", "cookieValue", cookieOptions);
}
API usage with SameSite
HttpContext.Response.Cookies.Append defaults to Unspecified
, meaning no SameSite attribute added to the cookie and the client will use its default behavior (Lax for new browsers, None for old ones). The following code shows how to change the cookie SameSite value to SameSiteMode.Lax
:
HttpContext.Response.Cookies.Append(
"name", "value",
new CookieOptions() { SameSite = SameSiteMode.Lax });
All ASP.NET Core components that emit cookies override the preceding defaults with settings appropriate for their scenarios. The overridden preceding default values haven't changed.
ASP.NET Core 3.1 and later provides the following SameSite support:
- Redefines the behavior of
SameSiteMode.None
to emitSameSite=None
- Adds a new value
SameSiteMode.Unspecified
to omit the SameSite attribute. - All cookies APIs default to
Unspecified
. Some components that use cookies set values more specific to their scenarios. See the table above for examples.
In ASP.NET Core 3.0 and later the SameSite defaults were changed to avoid conflicting with inconsistent client defaults. The following APIs have changed the default from SameSiteMode.Lax
to -1
to avoid emitting a SameSite attribute for these cookies:
- CookieOptions used with HttpContext.Response.Cookies.Append
- CookieBuilder used as a factory for
CookieOptions
- CookiePolicyOptions.MinimumSameSitePolicy
History and changes
SameSite support was first implemented in ASP.NET Core in 2.0 using the 2016 draft standard. The 2016 standard was opt-in. ASP.NET Core opted-in by setting several cookies to Lax
by default. After encountering several issues with authentication, most SameSite usage was disabled.
Patches were issued in November 2019 to update from the 2016 standard to the 2019 standard. The 2019 draft of the SameSite specification:
- Is not backwards compatible with the 2016 draft. For more information, see Supporting older browsers in this document.
- Specifies cookies are treated as
SameSite=Lax
by default. - Specifies cookies that explicitly assert
SameSite=None
in order to enable cross-site delivery should be marked asSecure
.None
is a new entry to opt out. - Is supported by patches issued for ASP.NET Core 2.1, 2.2, and 3.0. ASP.NET Core 3.1 and later has additional SameSite support.
- Is scheduled to be enabled by Chrome by default in Feb 2020. Browsers started moving to this standard in 2019.
APIs impacted by the change from the 2016 SameSite draft standard to the 2019 draft standard
- Http.SameSiteMode
- CookieOptions.SameSite
- CookieBuilder.SameSite
- CookiePolicyOptions.MinimumSameSitePolicy
- Microsoft.Net.Http.Headers.SameSiteMode
- Microsoft.Net.Http.Headers.SetCookieHeaderValue.SameSite
Supporting older browsers
The 2016 SameSite standard mandated that unknown values must be treated as SameSite=Strict
values. Apps accessed from older browsers which support the 2016 SameSite standard may break when they get a SameSite property with a value of None
. Web apps must implement browser detection if they intend to support older browsers. ASP.NET Core doesn't implement browser detection because User-Agents values are highly volatile and change frequently. An extension point in Microsoft.AspNetCore.CookiePolicy allows plugging in User-Agent specific logic.
In Program.cs
, add code that calls UseCookiePolicy before calling UseAuthentication or any method that writes cookies:
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
options.SameSite = SameSiteMode.Unspecified;
}
}
}
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
In Program.cs
, add code similar to the following highlighted code:
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
options.SameSite = SameSiteMode.Unspecified;
}
}
}
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
In the preceding sample, MyUserAgentDetectionLib.DisallowsSameSiteNone
is a user supplied library that detects if the user agent doesn't support SameSite None
:
if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
options.SameSite = SameSiteMode.Unspecified;
}
The following code shows a sample DisallowsSameSiteNone
method:
Warning
The following code is for demonstration only:
- It should not be considered complete.
- It is not maintained or supported.
public static bool DisallowsSameSiteNone(string userAgent)
{
// Check if a null or empty string has been passed in, since this
// will cause further interrogation of the useragent to fail.
if (String.IsNullOrWhiteSpace(userAgent))
return false;
// Cover all iOS based browsers here. This includes:
// - Safari on iOS 12 for iPhone, iPod Touch, iPad
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad
// All of which are broken by SameSite=None, because they use the iOS networking
// stack.
if (userAgent.Contains("CPU iPhone OS 12") ||
userAgent.Contains("iPad; CPU OS 12"))
{
return true;
}
// Cover Mac OS X based browsers that use the Mac OS networking stack.
// This includes:
// - Safari on Mac OS X.
// This does not include:
// - Chrome on Mac OS X
// Because they do not use the Mac OS networking stack.
if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
userAgent.Contains("Version/") && userAgent.Contains("Safari"))
{
return true;
}
// Cover Chrome 50-69, because some versions are broken by SameSite=None,
// and none in this range require it.
// Note: this covers some pre-Chromium Edge versions,
// but pre-Chromium Edge does not require SameSite=None.
if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
{
return true;
}
return false;
}
Test apps for SameSite problems
Apps that interact with remote sites such as through third-party login need to:
- Test the interaction on multiple browsers.
- Apply the CookiePolicy browser detection and mitigation discussed in this document.
Test web apps using a client version that can opt-in to the new SameSite behavior. Chrome, Firefox, and Chromium Edge all have new opt-in feature flags that can be used for testing. After your app applies the SameSite patches, test it with older client versions, especially Safari. For more information, see Supporting older browsers in this document.
Test with Chrome
Chrome 78+ gives misleading results because it has a temporary mitigation in place. The Chrome 78+ temporary mitigation allows cookies less than two minutes old. Chrome 76 or 77 with the appropriate test flags enabled provides more accurate results. To test the new SameSite behavior toggle chrome://flags/#same-site-by-default-cookies
to Enabled. Older versions of Chrome (75 and below) are reported to fail with the new None
setting. See Supporting older browsers in this document.
Google does not make older chrome versions available. Follow the instructions at Download Chromium to test older versions of Chrome. Do not download Chrome from links provided by searching for older versions of chrome.
Starting in Canary version 80.0.3975.0
, the Lax+POST temporary mitigation can be disabled for testing purposes using the new flag --enable-features=SameSiteDefaultChecksMethodRigorously
to allow testing of sites and services in the eventual end state of the feature where the mitigation has been removed. For more information, see The Chromium Projects SameSite Updates
Test with Safari
Safari 12 strictly implemented the prior draft and fails when the new None
value is in a cookie. None
is avoided via the browser detection code Supporting older browsers in this document. Test Safari 12, Safari 13, and WebKit based OS style logins using MSAL, ADAL or whatever library you are using. The problem is dependent on the underlying OS version. OSX Mojave (10.14) and iOS 12 are known to have compatibility problems with the new SameSite behavior. Upgrading the OS to OSX Catalina (10.15) or iOS 13 fixes the problem. Safari does not currently have an opt-in flag for testing the new spec behavior.
Test with Firefox
Firefox support for the new standard can be tested on version 68+ by opting in on the about:config
page with the feature flag network.cookie.sameSite.laxByDefault
. There haven't been reports of compatibility issues with older versions of Firefox.
Test with Edge browser
Edge supports the old SameSite standard. Edge version 44 doesn't have any known compatibility problems with the new standard.
Test with Edge (Chromium)
SameSite flags are set on the edge://flags/#same-site-by-default-cookies
page. No compatibility issues were discovered with Edge Chromium.
Test with Electron
Versions of Electron include older versions of Chromium. For example, the version of Electron used by Teams is Chromium 66, which exhibits the older behavior. You must perform your own compatibility testing with the version of Electron your product uses. See Supporting older browsers in the following section.
Additional resources
- Chromium Blog:Developers: Get Ready for New SameSite=None; Secure Cookie Settings
- SameSite cookies explained
- November 2019 Patches
Sample | Document |
---|---|
.NET Core Razor Pages | ASP.NET Core 3.1 Razor Pages SameSite cookie sample |
The following sample can be downloaded and tested:
Sample | Document |
---|---|
.NET Core Razor Pages | ASP.NET Core 3.1 Razor Pages SameSite cookie sample |
.NET Core support for the sameSite attribute
.NET Core 3.1 and later support the 2019 draft standard for SameSite. Developers are able to programmatically control the value of the sameSite attribute using the HttpCookie.SameSite
property. Setting the SameSite
property to Strict, Lax, or None results in those values being written on the network with the cookie. Setting it equal to (SameSiteMode)(-1)
indicates that no sameSite attribute should be included on the network with the cookie
var cookieOptions = new CookieOptions
{
// Set the secure flag, which Chrome's changes will require for SameSite none.
// Note this will also require you to be running on HTTPS.
Secure = true,
// Set the cookie to HTTP only which is good practice unless you really do need
// to access it client side in scripts.
HttpOnly = true,
// Add the SameSite attribute, this will emit the attribute with a value of none.
// To not emit the attribute at all set
// SameSite = (SameSiteMode)(-1)
SameSite = SameSiteMode.None
};
// Add the cookie to the response cookie collection
Response.Cookies.Append("MyCookie", "cookieValue", cookieOptions);
.NET Core 3.1 and later support the updated SameSite values and adds an extra enum value, SameSiteMode.Unspecified
to the SameSiteMode
enum.
This new value indicates no sameSite should be sent with the cookie.
API usage with SameSite
HttpContext.Response.Cookies.Append defaults to Unspecified
, meaning no SameSite attribute added to the cookie and the client will use its default behavior (Lax for new browsers, None for old ones). The following code shows how to change the cookie SameSite value to SameSiteMode.Lax
:
HttpContext.Response.Cookies.Append(
"name", "value",
new CookieOptions() { SameSite = SameSiteMode.Lax });
All ASP.NET Core components that emit cookies override the preceding defaults with settings appropriate for their scenarios. The overridden preceding default values haven't changed.
ASP.NET Core 3.1 and later provides the following SameSite support:
- Redefines the behavior of
SameSiteMode.None
to emitSameSite=None
- Adds a new value
SameSiteMode.Unspecified
to omit the SameSite attribute. - All cookies APIs default to
Unspecified
. Some components that use cookies set values more specific to their scenarios. See the table above for examples.
In ASP.NET Core 3.0 and later the SameSite defaults were changed to avoid conflicting with inconsistent client defaults. The following APIs have changed the default from SameSiteMode.Lax
to -1
to avoid emitting a SameSite attribute for these cookies:
- CookieOptions used with HttpContext.Response.Cookies.Append
- CookieBuilder used as a factory for
CookieOptions
- CookiePolicyOptions.MinimumSameSitePolicy
History and changes
SameSite support was first implemented in ASP.NET Core in 2.0 using the 2016 draft standard. The 2016 standard was opt-in. ASP.NET Core opted-in by setting several cookies to Lax
by default. After encountering several issues with authentication, most SameSite usage was disabled.
Patches were issued in November 2019 to update from the 2016 standard to the 2019 standard. The 2019 draft of the SameSite specification:
- Is not backwards compatible with the 2016 draft. For more information, see Supporting older browsers in this document.
- Specifies cookies are treated as
SameSite=Lax
by default. - Specifies cookies that explicitly assert
SameSite=None
in order to enable cross-site delivery should be marked asSecure
.None
is a new entry to opt out. - Is supported by patches issued for ASP.NET Core 2.1, 2.2, and 3.0. ASP.NET Core 3.1 has additional SameSite support.
- Is scheduled to be enabled by Chrome by default in Feb 2020. Browsers started moving to this standard in 2019.
APIs impacted by the change from the 2016 SameSite draft standard to the 2019 draft standard
- Http.SameSiteMode
- CookieOptions.SameSite
- CookieBuilder.SameSite
- CookiePolicyOptions.MinimumSameSitePolicy
- Microsoft.Net.Http.Headers.SameSiteMode
- Microsoft.Net.Http.Headers.SetCookieHeaderValue.SameSite
Supporting older browsers
The 2016 SameSite standard mandated that unknown values must be treated as SameSite=Strict
values. Apps accessed from older browsers which support the 2016 SameSite standard may break when they get a SameSite property with a value of None
. Web apps must implement browser detection if they intend to support older browsers. ASP.NET Core doesn't implement browser detection because User-Agents values are highly volatile and change frequently. An extension point in Microsoft.AspNetCore.CookiePolicy allows plugging in User-Agent specific logic.
In Startup.Configure
, add code that calls UseCookiePolicy before calling UseAuthentication or any method that writes cookies:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
In Startup.ConfigureServices
, add code similar to the following:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
services.AddRazorPages();
}
private void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
options.SameSite = SameSiteMode.Unspecified;
}
}
}
In the preceding sample, MyUserAgentDetectionLib.DisallowsSameSiteNone
is a user supplied library that detects if the user agent doesn't support SameSite None
:
if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
options.SameSite = SameSiteMode.Unspecified;
}
The following code shows a sample DisallowsSameSiteNone
method:
Warning
The following code is for demonstration only:
- It should not be considered complete.
- It is not maintained or supported.
public static bool DisallowsSameSiteNone(string userAgent)
{
// Check if a null or empty string has been passed in, since this
// will cause further interrogation of the useragent to fail.
if (String.IsNullOrWhiteSpace(userAgent))
return false;
// Cover all iOS based browsers here. This includes:
// - Safari on iOS 12 for iPhone, iPod Touch, iPad
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad
// All of which are broken by SameSite=None, because they use the iOS networking
// stack.
if (userAgent.Contains("CPU iPhone OS 12") ||
userAgent.Contains("iPad; CPU OS 12"))
{
return true;
}
// Cover Mac OS X based browsers that use the Mac OS networking stack.
// This includes:
// - Safari on Mac OS X.
// This does not include:
// - Chrome on Mac OS X
// Because they do not use the Mac OS networking stack.
if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
userAgent.Contains("Version/") && userAgent.Contains("Safari"))
{
return true;
}
// Cover Chrome 50-69, because some versions are broken by SameSite=None,
// and none in this range require it.
// Note: this covers some pre-Chromium Edge versions,
// but pre-Chromium Edge does not require SameSite=None.
if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
{
return true;
}
return false;
}
Test apps for SameSite problems
Apps that interact with remote sites such as through third-party login need to:
- Test the interaction on multiple browsers.
- Apply the CookiePolicy browser detection and mitigation discussed in this document.
Test web apps using a client version that can opt-in to the new SameSite behavior. Chrome, Firefox, and Chromium Edge all have new opt-in feature flags that can be used for testing. After your app applies the SameSite patches, test it with older client versions, especially Safari. For more information, see Supporting older browsers in this document.
Test with Chrome
Chrome 78+ gives misleading results because it has a temporary mitigation in place. The Chrome 78+ temporary mitigation allows cookies less than two minutes old. Chrome 76 or 77 with the appropriate test flags enabled provides more accurate results. To test the new SameSite behavior toggle chrome://flags/#same-site-by-default-cookies
to Enabled. Older versions of Chrome (75 and below) are reported to fail with the new None
setting. See Supporting older browsers in this document.
Google does not make older chrome versions available. Follow the instructions at Download Chromium to test older versions of Chrome. Do not download Chrome from links provided by searching for older versions of chrome.
Starting in Canary version 80.0.3975.0
, the Lax+POST temporary mitigation can be disabled for testing purposes using the new flag --enable-features=SameSiteDefaultChecksMethodRigorously
to allow testing of sites and services in the eventual end state of the feature where the mitigation has been removed. For more information, see The Chromium Projects SameSite Updates
Test with Safari
Safari 12 strictly implemented the prior draft and fails when the new None
value is in a cookie. None
is avoided via the browser detection code Supporting older browsers in this document. Test Safari 12, Safari 13, and WebKit based OS style logins using MSAL, ADAL or whatever library you are using. The problem is dependent on the underlying OS version. OSX Mojave (10.14) and iOS 12 are known to have compatibility problems with the new SameSite behavior. Upgrading the OS to OSX Catalina (10.15) or iOS 13 fixes the problem. Safari does not currently have an opt-in flag for testing the new spec behavior.
Test with Firefox
Firefox support for the new standard can be tested on version 68+ by opting in on the about:config
page with the feature flag network.cookie.sameSite.laxByDefault
. There haven't been reports of compatibility issues with older versions of Firefox.
Test with Edge browser
Edge supports the old SameSite standard. Edge version 44 doesn't have any known compatibility problems with the new standard.
Test with Edge (Chromium)
SameSite flags are set on the edge://flags/#same-site-by-default-cookies
page. No compatibility issues were discovered with Edge Chromium.
Test with Electron
Versions of Electron include older versions of Chromium. For example, the version of Electron used by Teams is Chromium 66, which exhibits the older behavior. You must perform your own compatibility testing with the version of Electron your product uses. See Supporting older browsers in the following section.
Additional resources
- Chromium Blog:Developers: Get Ready for New SameSite=None; Secure Cookie Settings
- SameSite cookies explained
- November 2019 Patches
Sample | Document |
---|---|
.NET Core Razor Pages | ASP.NET Core 3.1 Razor Pages SameSite cookie sample |
The following samples can be downloaded and tested:
Sample | Document |
---|---|
.NET Core MVC | ASP.NET Core 2.1 MVC SameSite cookie sample |
.NET Core Razor Pages | ASP.NET Core 2.1 Razor Pages SameSite cookie sample |
December patch behavior changes
The specific behavior change for .NET Framework and .NET Core 2.1 is how the SameSite
property interprets the None
value. Before the patch a value of None
meant "Do not emit the attribute at all", after the patch it means "Emit the attribute with a value of None
". After the patch a SameSite
value of (SameSiteMode)(-1)
causes the attribute not to be emitted.
The default SameSite value for forms authentication and session state cookies was changed from None
to Lax
.
API usage with SameSite
HttpContext.Response.Cookies.Append defaults to Unspecified
, meaning no SameSite attribute added to the cookie and the client will use its default behavior (Lax for new browsers, None for old ones). The following code shows how to change the cookie SameSite value to SameSiteMode.Lax
:
HttpContext.Response.Cookies.Append(
"name", "value",
new CookieOptions() { SameSite = SameSiteMode.Lax });
All ASP.NET Core components that emit cookies override the preceding defaults with settings appropriate for their scenarios. The overridden preceding default values haven't changed.
History and changes
SameSite support was first implemented in ASP.NET Core in 2.0 using the 2016 draft standard. The 2016 standard was opt-in. ASP.NET Core opted-in by setting several cookies to Lax
by default. After encountering several issues with authentication, most SameSite usage was disabled.
Patches were issued in November 2019 to update from the 2016 standard to the 2019 standard. The 2019 draft of the SameSite specification:
- Is not backwards compatible with the 2016 draft. For more information, see Supporting older browsers in this document.
- Specifies cookies are treated as
SameSite=Lax
by default. - Specifies cookies that explicitly assert
SameSite=None
in order to enable cross-site delivery should be marked asSecure
.None
is a new entry to opt out. - Is supported by patches issued for ASP.NET Core 2.1, 2.2, and 3.0. ASP.NET Core 3.1 has additional SameSite support.
- Is scheduled to be enabled by Chrome by default in Feb 2020. Browsers started moving to this standard in 2019.
APIs impacted by the change from the 2016 SameSite draft standard to the 2019 draft standard
- Http.SameSiteMode
- CookieOptions.SameSite
- CookieBuilder.SameSite
- CookiePolicyOptions.MinimumSameSitePolicy
- Microsoft.Net.Http.Headers.SameSiteMode
- Microsoft.Net.Http.Headers.SetCookieHeaderValue.SameSite
Supporting older browsers
The 2016 SameSite standard mandated that unknown values must be treated as SameSite=Strict
values. Apps accessed from older browsers which support the 2016 SameSite standard may break when they get a SameSite property with a value of None
. Web apps must implement browser detection if they intend to support older browsers. ASP.NET Core doesn't implement browser detection because User-Agents values are highly volatile and change frequently. An extension point in Microsoft.AspNetCore.CookiePolicy allows plugging in User-Agent specific logic.
In Startup.Configure
, add code that calls UseCookiePolicy before calling UseAuthentication or any method that writes cookies:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
In Startup.ConfigureServices
, add code similar to the following:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = (SameSiteMode)(-1);
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
services.AddRazorPages();
}
private void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
options.SameSite = (SameSiteMode)(-1);
}
}
}
In the preceding sample, MyUserAgentDetectionLib.DisallowsSameSiteNone
is a user supplied library that detects if the user agent doesn't support SameSite None
:
if (MyUserAgentDetectionLib.DisallowsSameSiteNone(userAgent))
{
options.SameSite = SameSiteMode.Unspecified;
}
The following code shows a sample DisallowsSameSiteNone
method:
Warning
The following code is for demonstration only:
- It should not be considered complete.
- It is not maintained or supported.
public static bool DisallowsSameSiteNone(string userAgent)
{
// Check if a null or empty string has been passed in, since this
// will cause further interrogation of the useragent to fail.
if (String.IsNullOrWhiteSpace(userAgent))
return false;
// Cover all iOS based browsers here. This includes:
// - Safari on iOS 12 for iPhone, iPod Touch, iPad
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad
// All of which are broken by SameSite=None, because they use the iOS networking
// stack.
if (userAgent.Contains("CPU iPhone OS 12") ||
userAgent.Contains("iPad; CPU OS 12"))
{
return true;
}
// Cover Mac OS X based browsers that use the Mac OS networking stack.
// This includes:
// - Safari on Mac OS X.
// This does not include:
// - Chrome on Mac OS X
// Because they do not use the Mac OS networking stack.
if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
userAgent.Contains("Version/") && userAgent.Contains("Safari"))
{
return true;
}
// Cover Chrome 50-69, because some versions are broken by SameSite=None,
// and none in this range require it.
// Note: this covers some pre-Chromium Edge versions,
// but pre-Chromium Edge does not require SameSite=None.
if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
{
return true;
}
return false;
}
Test apps for SameSite problems
Apps that interact with remote sites such as through third-party login need to:
- Test the interaction on multiple browsers.
- Apply the CookiePolicy browser detection and mitigation discussed in this document.
Test web apps using a client version that can opt-in to the new SameSite behavior. Chrome, Firefox, and Chromium Edge all have new opt-in feature flags that can be used for testing. After your app applies the SameSite patches, test it with older client versions, especially Safari. For more information, see Supporting older browsers in this document.
Test with Chrome
Chrome 78+ gives misleading results because it has a temporary mitigation in place. The Chrome 78+ temporary mitigation allows cookies less than two minutes old. Chrome 76 or 77 with the appropriate test flags enabled provides more accurate results. To test the new SameSite behavior toggle chrome://flags/#same-site-by-default-cookies
to Enabled. Older versions of Chrome (75 and below) are reported to fail with the new None
setting. See Supporting older browsers in this document.
Google does not make older chrome versions available. Follow the instructions at Download Chromium to test older versions of Chrome. Do not download Chrome from links provided by searching for older versions of chrome.
Starting in Canary version 80.0.3975.0
, the Lax+POST temporary mitigation can be disabled for testing purposes using the new flag --enable-features=SameSiteDefaultChecksMethodRigorously
to allow testing of sites and services in the eventual end state of the feature where the mitigation has been removed. For more information, see The Chromium Projects SameSite Updates
Test with Safari
Safari 12 strictly implemented the prior draft and fails when the new None
value is in a cookie. None
is avoided via the browser detection code Supporting older browsers in this document. Test Safari 12, Safari 13, and WebKit based OS style logins using MSAL, ADAL or whatever library you are using. The problem is dependent on the underlying OS version. OSX Mojave (10.14) and iOS 12 are known to have compatibility problems with the new SameSite behavior. Upgrading the OS to OSX Catalina (10.15) or iOS 13 fixes the problem. Safari does not currently have an opt-in flag for testing the new spec behavior.
Test with Firefox
Firefox support for the new standard can be tested on version 68+ by opting in on the about:config
page with the feature flag network.cookie.sameSite.laxByDefault
. There haven't been reports of compatibility issues with older versions of Firefox.
Test with Edge browser
Edge supports the old SameSite standard. Edge version 44 doesn't have any known compatibility problems with the new standard.
Test with Edge (Chromium)
SameSite flags are set on the edge://flags/#same-site-by-default-cookies
page. No compatibility issues were discovered with Edge Chromium.
Test with Electron
Versions of Electron include older versions of Chromium. For example, the version of Electron used by Teams is Chromium 66, which exhibits the older behavior. You must perform your own compatibility testing with the version of Electron your product uses. See Supporting older browsers in the following section.
Additional resources
- Chromium Blog:Developers: Get Ready for New SameSite=None; Secure Cookie Settings
- SameSite cookies explained
- November 2019 Patches
Sample | Document |
---|---|
.NET Core MVC | ASP.NET Core 2.1 MVC SameSite cookie sample |
.NET Core Razor Pages | ASP.NET Core 2.1 Razor Pages SameSite cookie sample |