diff --git a/shared/Microsoft.AspNetCore.ChunkingCookieManager.Sources/ChunkingCookieManager.cs b/shared/Microsoft.AspNetCore.ChunkingCookieManager.Sources/ChunkingCookieManager.cs index 16426507c..9b602383c 100644 --- a/shared/Microsoft.AspNetCore.ChunkingCookieManager.Sources/ChunkingCookieManager.cs +++ b/shared/Microsoft.AspNetCore.ChunkingCookieManager.Sources/ChunkingCookieManager.cs @@ -33,7 +33,7 @@ internal class ChunkingCookieManager /// /// The default maximum size of characters in a cookie to send back to the client. /// - public const int DefaultChunkSize = 4070; + public const int DefaultChunkSize = 4050; private const string ChunkKeySuffix = "C"; private const string ChunkCountPrefix = "chunks-"; @@ -42,7 +42,7 @@ public ChunkingCookieManager() { // Lowest common denominator. Safari has the lowest known limit (4093), and we leave little extra just in case. // See http://browsercookielimits.x64.me/. - // Leave at least 20 in case CookiePolicy tries to add 'secure' and/or 'httponly'. + // Leave at least 40 in case CookiePolicy tries to add 'secure', 'samesite=strict' and/or 'httponly'. ChunkSize = DefaultChunkSize; ThrowForPartialCookies = true; } @@ -166,6 +166,7 @@ public void AppendResponseCookie(HttpContext context, string key, string value, { Domain = options.Domain, Expires = options.Expires, + SameSite = (Net.Http.Headers.SameSiteMode)options.SameSite, HttpOnly = options.HttpOnly, Path = options.Path, Secure = options.Secure, @@ -284,6 +285,7 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options) { Path = options.Path, Domain = options.Domain, + SameSite = options.SameSite, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); @@ -297,6 +299,7 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options) { Path = options.Path, Domain = options.Domain, + SameSite = options.SameSite, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); } diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs index 5a4ffc547..13a20e55b 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -179,6 +179,7 @@ private CookieOptions BuildCookieOptions() var cookieOptions = new CookieOptions { Domain = Options.CookieDomain, + SameSite = Options.CookieSameSite, HttpOnly = Options.CookieHttpOnly, Path = Options.CookiePath ?? (OriginalPathBase.HasValue ? OriginalPathBase.ToString() : "/"), }; diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs index 9b6b51d8e..02d0361b7 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs @@ -4,7 +4,6 @@ using System; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Authentication.Cookies { @@ -23,6 +22,7 @@ public CookieAuthenticationOptions() ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter; ExpireTimeSpan = TimeSpan.FromDays(14); SlidingExpiration = true; + CookieSameSite = SameSiteMode.Strict; CookieHttpOnly = true; CookieSecure = CookieSecurePolicy.SameAsRequest; Events = new CookieAuthenticationEvents(); @@ -57,6 +57,12 @@ public string CookieName /// public string CookiePath { get; set; } + /// + /// Determines if the browser should allow the cookie to be attached to same-site or cross-site requests. The + /// default is Strict, which means the cookie is only allowed to be attached to same-site requests. + /// + public SameSiteMode CookieSameSite { get; set; } + /// /// Determines if the browser should allow the cookie to be accessed by client-side javascript. The /// default is true, which means the cookie will only be passed to http requests and is not made available diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index d51c324dc..04359142a 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -899,6 +899,7 @@ private void WriteNonceCookie(string nonce) new CookieOptions { HttpOnly = true, + SameSite = Http.SameSiteMode.Lax, Secure = Request.IsHttps, Expires = Clock.UtcNow.Add(Options.ProtocolValidator.NonceLifetime) }); @@ -930,6 +931,7 @@ private string ReadNonceCookie(string nonce) var cookieOptions = new CookieOptions { HttpOnly = true, + SameSite = Http.SameSiteMode.Lax, Secure = Request.IsHttps }; diff --git a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs index 7ae9f973c..ff054d3c6 100644 --- a/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs @@ -83,6 +83,7 @@ protected override async Task HandleRemoteAuthenticateAsync( var cookieOptions = new CookieOptions { HttpOnly = true, + SameSite = SameSiteMode.Lax, Secure = Request.IsHttps }; @@ -160,6 +161,7 @@ protected override async Task HandleUnauthorizedAsync(ChallengeContext context) var cookieOptions = new CookieOptions { HttpOnly = true, + SameSite = SameSiteMode.Lax, Secure = Request.IsHttps, Expires = Clock.UtcNow.Add(Options.RemoteAuthenticationTimeout), }; diff --git a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs index bf875a7a0..a7de95dfe 100644 --- a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs @@ -203,6 +203,7 @@ protected virtual void GenerateCorrelationId(AuthenticationProperties properties var cookieOptions = new CookieOptions { HttpOnly = true, + SameSite = SameSiteMode.Lax, Secure = Request.IsHttps, Expires = Clock.UtcNow.Add(Options.RemoteAuthenticationTimeout), }; @@ -242,6 +243,7 @@ protected virtual bool ValidateCorrelationId(AuthenticationProperties properties var cookieOptions = new CookieOptions { HttpOnly = true, + SameSite = SameSiteMode.Lax, Secure = Request.IsHttps }; Response.Cookies.Delete(cookieName, cookieOptions); diff --git a/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyMiddleware.cs b/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyMiddleware.cs index 46daaad81..92adac967 100644 --- a/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyMiddleware.cs +++ b/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyMiddleware.cs @@ -74,7 +74,7 @@ public IResponseCookies Cookies private bool PolicyRequiresCookieOptions() { - return Policy.HttpOnly != HttpOnlyPolicy.None || Policy.Secure != CookieSecurePolicy.None; + return Policy.MinimumSameSitePolicy != SameSiteMode.None || Policy.HttpOnly != HttpOnlyPolicy.None || Policy.Secure != CookieSecurePolicy.None; } public void Append(string key, string value) @@ -151,6 +151,22 @@ private void ApplyPolicy(CookieOptions options) default: throw new InvalidOperationException(); } + switch (Policy.MinimumSameSitePolicy) + { + case SameSiteMode.None: + break; + case SameSiteMode.Lax: + if (options.SameSite == SameSiteMode.None) + { + options.SameSite = SameSiteMode.Lax; + } + break; + case SameSiteMode.Strict: + options.SameSite = SameSiteMode.Strict; + break; + default: + throw new InvalidOperationException($"Unrecognized {nameof(SameSiteMode)} value {Policy.MinimumSameSitePolicy.ToString()}"); + } switch (Policy.HttpOnly) { case HttpOnlyPolicy.Always: @@ -159,7 +175,7 @@ private void ApplyPolicy(CookieOptions options) case HttpOnlyPolicy.None: break; default: - throw new InvalidOperationException(); + throw new InvalidOperationException($"Unrecognized {nameof(HttpOnlyPolicy)} value {Policy.HttpOnly.ToString()}"); } } } diff --git a/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyOptions.cs b/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyOptions.cs index 6aed18bfb..7203e73e6 100644 --- a/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyOptions.cs +++ b/src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyOptions.cs @@ -12,6 +12,11 @@ namespace Microsoft.AspNetCore.Builder /// public class CookiePolicyOptions { + /// + /// Affects the cookie's same site attribute. + /// + public SameSiteMode MinimumSameSitePolicy { get; set; } = SameSiteMode.Strict; + /// /// Affects whether cookies must be HttpOnly. /// diff --git a/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs index 419d82493..e38dc1587 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs @@ -136,6 +136,7 @@ public async Task SignInCausesDefaultCookieToBeCreated() Assert.StartsWith("TestCookie=", setCookie); Assert.Contains("; path=/", setCookie); Assert.Contains("; httponly", setCookie); + Assert.Contains("; samesite=", setCookie); Assert.DoesNotContain("; expires=", setCookie); Assert.DoesNotContain("; domain=", setCookie); Assert.DoesNotContain("; secure", setCookie); @@ -206,6 +207,7 @@ public async Task CookieOptionsAlterSetCookieHeader() o.CookiePath = "/foo"; o.CookieDomain = "another.com"; o.CookieSecure = CookieSecurePolicy.Always; + o.CookieSameSite = SameSiteMode.None; o.CookieHttpOnly = true; }, SignInAsAlice, baseAddress: new Uri("http://example.com/base")); @@ -217,12 +219,14 @@ public async Task CookieOptionsAlterSetCookieHeader() Assert.Contains(" path=/foo", setCookie1); Assert.Contains(" domain=another.com", setCookie1); Assert.Contains(" secure", setCookie1); + Assert.DoesNotContain(" samesite", setCookie1); Assert.Contains(" httponly", setCookie1); var server2 = CreateServer(o => { o.CookieName = "SecondCookie"; o.CookieSecure = CookieSecurePolicy.None; + o.CookieSameSite = SameSiteMode.Strict; o.CookieHttpOnly = false; }, SignInAsAlice, baseAddress: new Uri("http://example.com/base")); @@ -232,6 +236,7 @@ public async Task CookieOptionsAlterSetCookieHeader() Assert.Contains("SecondCookie=", setCookie2); Assert.Contains(" path=/base", setCookie2); + Assert.Contains(" samesite=strict", setCookie2); Assert.DoesNotContain(" domain=", setCookie2); Assert.DoesNotContain(" secure", setCookie2); Assert.DoesNotContain(" httponly", setCookie2); diff --git a/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/CookieChunkingTests.cs b/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/CookieChunkingTests.cs index c978d169e..143e1d254 100644 --- a/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/CookieChunkingTests.cs +++ b/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/CookieChunkingTests.cs @@ -18,7 +18,7 @@ public void AppendLargeCookie_Appended() new ChunkingCookieManager() { ChunkSize = null }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions()); var values = context.Response.Headers["Set-Cookie"]; Assert.Equal(1, values.Count); - Assert.Equal("TestCookie=" + testString + "; path=/", values[0]); + Assert.Equal("TestCookie=" + testString + "; path=/; samesite=lax", values[0]); } [Fact] @@ -27,20 +27,20 @@ public void AppendLargeCookieWithLimit_Chunked() HttpContext context = new DefaultHttpContext(); string testString = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - new ChunkingCookieManager() { ChunkSize = 30 }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions()); + new ChunkingCookieManager() { ChunkSize = 44 }.AppendResponseCookie(context, "TestCookie", testString, new CookieOptions()); var values = context.Response.Headers["Set-Cookie"]; Assert.Equal(9, values.Count); Assert.Equal(new[] { - "TestCookie=chunks-8; path=/", - "TestCookieC1=abcdefgh; path=/", - "TestCookieC2=ijklmnop; path=/", - "TestCookieC3=qrstuvwx; path=/", - "TestCookieC4=yz012345; path=/", - "TestCookieC5=6789ABCD; path=/", - "TestCookieC6=EFGHIJKL; path=/", - "TestCookieC7=MNOPQRST; path=/", - "TestCookieC8=UVWXYZ; path=/", + "TestCookie=chunks-8; path=/; samesite=lax", + "TestCookieC1=abcdefgh; path=/; samesite=lax", + "TestCookieC2=ijklmnop; path=/; samesite=lax", + "TestCookieC3=qrstuvwx; path=/; samesite=lax", + "TestCookieC4=yz012345; path=/; samesite=lax", + "TestCookieC5=6789ABCD; path=/; samesite=lax", + "TestCookieC6=EFGHIJKL; path=/; samesite=lax", + "TestCookieC7=MNOPQRST; path=/; samesite=lax", + "TestCookieC8=UVWXYZ; path=/; samesite=lax", }, values); } @@ -116,14 +116,14 @@ public void DeleteChunkedCookieWithOptions_AllDeleted() Assert.Equal(8, cookies.Count); Assert.Equal(new[] { - "TestCookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", - "TestCookieC1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", - "TestCookieC2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", - "TestCookieC3=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", - "TestCookieC4=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", - "TestCookieC5=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", - "TestCookieC6=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", - "TestCookieC7=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/", + "TestCookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", + "TestCookieC1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", + "TestCookieC2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", + "TestCookieC3=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", + "TestCookieC4=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", + "TestCookieC5=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", + "TestCookieC6=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", + "TestCookieC7=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=foo.com; path=/; samesite=lax", }, cookies); } } diff --git a/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs b/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs index e45c7f690..737c12dc3 100644 --- a/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs +++ b/test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs @@ -36,6 +36,15 @@ public class CookiePolicyTests context.Response.Cookies.Append("D", "D", new CookieOptions { HttpOnly = true }); return Task.FromResult(0); }; + private RequestDelegate SameSiteCookieAppends = context => + { + context.Response.Cookies.Append("A", "A"); + context.Response.Cookies.Append("B", "B", new CookieOptions { SameSite = Http.SameSiteMode.None }); + context.Response.Cookies.Append("C", "C", new CookieOptions()); + context.Response.Cookies.Append("D", "D", new CookieOptions { SameSite = Http.SameSiteMode.Lax }); + context.Response.Cookies.Append("E", "E", new CookieOptions { SameSite = Http.SameSiteMode.Strict }); + return Task.FromResult(0); + }; [Fact] public async Task SecureAlwaysSetsSecure() @@ -50,10 +59,10 @@ await RunTest("/secureAlways", transaction => { Assert.NotNull(transaction.SetCookie); - Assert.Equal("A=A; path=/; secure", transaction.SetCookie[0]); - Assert.Equal("B=B; path=/; secure", transaction.SetCookie[1]); - Assert.Equal("C=C; path=/; secure", transaction.SetCookie[2]); - Assert.Equal("D=D; path=/; secure", transaction.SetCookie[3]); + Assert.Equal("A=A; path=/; secure; samesite=strict", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; secure; samesite=strict", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; secure; samesite=strict", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; secure; samesite=strict", transaction.SetCookie[3]); })); } @@ -70,10 +79,10 @@ await RunTest("/secureNone", transaction => { Assert.NotNull(transaction.SetCookie); - Assert.Equal("A=A; path=/", transaction.SetCookie[0]); - Assert.Equal("B=B; path=/", transaction.SetCookie[1]); - Assert.Equal("C=C; path=/", transaction.SetCookie[2]); - Assert.Equal("D=D; path=/; secure", transaction.SetCookie[3]); + Assert.Equal("A=A; path=/; samesite=strict", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; samesite=strict", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; samesite=strict", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; secure; samesite=strict", transaction.SetCookie[3]); })); } @@ -90,19 +99,19 @@ await RunTest("/secureSame", transaction => { Assert.NotNull(transaction.SetCookie); - Assert.Equal("A=A; path=/", transaction.SetCookie[0]); - Assert.Equal("B=B; path=/", transaction.SetCookie[1]); - Assert.Equal("C=C; path=/", transaction.SetCookie[2]); - Assert.Equal("D=D; path=/", transaction.SetCookie[3]); + Assert.Equal("A=A; path=/; samesite=strict", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; samesite=strict", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; samesite=strict", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; samesite=strict", transaction.SetCookie[3]); }), new RequestTest("https://example.com/secureSame", transaction => { Assert.NotNull(transaction.SetCookie); - Assert.Equal("A=A; path=/; secure", transaction.SetCookie[0]); - Assert.Equal("B=B; path=/; secure", transaction.SetCookie[1]); - Assert.Equal("C=C; path=/; secure", transaction.SetCookie[2]); - Assert.Equal("D=D; path=/; secure", transaction.SetCookie[3]); + Assert.Equal("A=A; path=/; secure; samesite=strict", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; secure; samesite=strict", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; secure; samesite=strict", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; secure; samesite=strict", transaction.SetCookie[3]); })); } @@ -119,10 +128,10 @@ await RunTest("/httpOnlyAlways", transaction => { Assert.NotNull(transaction.SetCookie); - Assert.Equal("A=A; path=/; httponly", transaction.SetCookie[0]); - Assert.Equal("B=B; path=/; httponly", transaction.SetCookie[1]); - Assert.Equal("C=C; path=/; httponly", transaction.SetCookie[2]); - Assert.Equal("D=D; path=/; httponly", transaction.SetCookie[3]); + Assert.Equal("A=A; path=/; samesite=strict; httponly", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; samesite=strict; httponly", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; samesite=strict; httponly", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; samesite=strict; httponly", transaction.SetCookie[3]); })); } @@ -137,12 +146,75 @@ await RunTest("/httpOnlyNone", HttpCookieAppends, new RequestTest("http://example.com/httpOnlyNone", transaction => + { + Assert.NotNull(transaction.SetCookie); + Assert.Equal("A=A; path=/; samesite=strict", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; samesite=strict", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; samesite=strict", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; samesite=strict; httponly", transaction.SetCookie[3]); + })); + } + + [Fact] + public async Task SameSiteStrictSetsItAlways() + { + await RunTest("/sameSiteStrict", + new CookiePolicyOptions + { + MinimumSameSitePolicy = Http.SameSiteMode.Strict + }, + SameSiteCookieAppends, + new RequestTest("http://example.com/sameSiteStrict", + transaction => + { + Assert.NotNull(transaction.SetCookie); + Assert.Equal("A=A; path=/; samesite=strict", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; samesite=strict", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; samesite=strict", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; samesite=strict", transaction.SetCookie[3]); + Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]); + })); + } + + [Fact] + public async Task SameSiteLaxSetsItAlways() + { + await RunTest("/sameSiteLax", + new CookiePolicyOptions + { + MinimumSameSitePolicy = Http.SameSiteMode.Lax + }, + SameSiteCookieAppends, + new RequestTest("http://example.com/sameSiteLax", + transaction => + { + Assert.NotNull(transaction.SetCookie); + Assert.Equal("A=A; path=/; samesite=lax", transaction.SetCookie[0]); + Assert.Equal("B=B; path=/; samesite=lax", transaction.SetCookie[1]); + Assert.Equal("C=C; path=/; samesite=lax", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]); + Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]); + })); + } + + [Fact] + public async Task SameSiteNoneLeavesItAlone() + { + await RunTest("/sameSiteNone", + new CookiePolicyOptions + { + MinimumSameSitePolicy = Http.SameSiteMode.None + }, + SameSiteCookieAppends, + new RequestTest("http://example.com/sameSiteNone", + transaction => { Assert.NotNull(transaction.SetCookie); Assert.Equal("A=A; path=/", transaction.SetCookie[0]); Assert.Equal("B=B; path=/", transaction.SetCookie[1]); - Assert.Equal("C=C; path=/", transaction.SetCookie[2]); - Assert.Equal("D=D; path=/; httponly", transaction.SetCookie[3]); + Assert.Equal("C=C; path=/; samesite=lax", transaction.SetCookie[2]); + Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]); + Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]); })); } @@ -170,10 +242,10 @@ public async Task CookiePolicyCanHijackAppend() var transaction = await server.SendAsync("http://example.com/login"); Assert.NotNull(transaction.SetCookie); - Assert.Equal("Hao=Hao; path=/", transaction.SetCookie[0]); - Assert.Equal("Hao=Hao; path=/", transaction.SetCookie[1]); - Assert.Equal("Hao=Hao; path=/", transaction.SetCookie[2]); - Assert.Equal("Hao=Hao; path=/; secure", transaction.SetCookie[3]); + Assert.Equal("Hao=Hao; path=/; samesite=strict", transaction.SetCookie[0]); + Assert.Equal("Hao=Hao; path=/; samesite=strict", transaction.SetCookie[1]); + Assert.Equal("Hao=Hao; path=/; samesite=strict", transaction.SetCookie[2]); + Assert.Equal("Hao=Hao; path=/; secure; samesite=strict", transaction.SetCookie[3]); } [Fact] @@ -201,7 +273,7 @@ public async Task CookiePolicyCanHijackDelete() Assert.NotNull(transaction.SetCookie); Assert.Equal(1, transaction.SetCookie.Count); - Assert.Equal("A=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/", transaction.SetCookie[0]); + Assert.Equal("A=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax", transaction.SetCookie[0]); } [Fact]