Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit 4b722bb

Browse files
committed
Add SameSitePolicy to CookiePolicyMiddleware
1 parent 1f5a27e commit 4b722bb

File tree

7 files changed

+119
-2
lines changed

7 files changed

+119
-2
lines changed

src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ private CookieOptions BuildCookieOptions()
179179
var cookieOptions = new CookieOptions
180180
{
181181
Domain = Options.CookieDomain,
182+
SameSite = Options.CookieSameSite,
182183
HttpOnly = Options.CookieHttpOnly,
183184
Path = Options.CookiePath ?? (OriginalPathBase.HasValue ? OriginalPathBase.ToString() : "/"),
184185
};

src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using Microsoft.AspNetCore.DataProtection;
66
using Microsoft.AspNetCore.Http;
7-
using Microsoft.Extensions.Options;
87

98
namespace Microsoft.AspNetCore.Authentication.Cookies
109
{
@@ -23,6 +22,7 @@ public CookieAuthenticationOptions()
2322
ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
2423
ExpireTimeSpan = TimeSpan.FromDays(14);
2524
SlidingExpiration = true;
25+
CookieSameSite = SameSiteEnforcementMode.None;
2626
CookieHttpOnly = true;
2727
CookieSecure = CookieSecurePolicy.SameAsRequest;
2828
Events = new CookieAuthenticationEvents();
@@ -57,6 +57,12 @@ public string CookieName
5757
/// </summary>
5858
public string CookiePath { get; set; }
5959

60+
/// <summary>
61+
/// Determines if the browser should allow the cookie to be attached to same-site or cross-site requests. The
62+
/// default is None, which means the cookie is allowed to be attached to all same-site and cross-site requests.
63+
/// </summary>
64+
public SameSiteEnforcementMode CookieSameSite { get; set; }
65+
6066
/// <summary>
6167
/// Determines if the browser should allow the cookie to be accessed by client-side javascript. The
6268
/// default is true, which means the cookie will only be passed to http requests and is not made available

src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyMiddleware.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public IResponseCookies Cookies
7474

7575
private bool PolicyRequiresCookieOptions()
7676
{
77-
return Policy.HttpOnly != HttpOnlyPolicy.None || Policy.Secure != CookieSecurePolicy.None;
77+
return Policy.SameSite != SameSitePolicy.None || Policy.HttpOnly != HttpOnlyPolicy.None || Policy.Secure != CookieSecurePolicy.None;
7878
}
7979

8080
public void Append(string key, string value)
@@ -151,6 +151,22 @@ private void ApplyPolicy(CookieOptions options)
151151
default:
152152
throw new InvalidOperationException();
153153
}
154+
switch (Policy.SameSite)
155+
{
156+
case SameSitePolicy.None:
157+
break;
158+
case SameSitePolicy.LaxOrStrict:
159+
if (options.SameSite == SameSiteEnforcementMode.None)
160+
{
161+
options.SameSite = SameSiteEnforcementMode.Lax;
162+
}
163+
break;
164+
case SameSitePolicy.AlwaysStrict:
165+
options.SameSite = SameSiteEnforcementMode.Strict;
166+
break;
167+
default:
168+
throw new InvalidOperationException();
169+
}
154170
switch (Policy.HttpOnly)
155171
{
156172
case HttpOnlyPolicy.Always:

src/Microsoft.AspNetCore.CookiePolicy/CookiePolicyOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ namespace Microsoft.AspNetCore.Builder
1212
/// </summary>
1313
public class CookiePolicyOptions
1414
{
15+
/// <summary>
16+
/// Affects the cookie's same site attribute.
17+
/// </summary>
18+
public SameSitePolicy SameSite { get; set; } = SameSitePolicy.None;
19+
1520
/// <summary>
1621
/// Affects whether cookies must be HttpOnly.
1722
/// </summary>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.CookiePolicy
5+
{
6+
public enum SameSitePolicy
7+
{
8+
None = 0,
9+
LaxOrStrict,
10+
AlwaysStrict
11+
}
12+
}

test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public async Task SignInCausesDefaultCookieToBeCreated()
136136
Assert.StartsWith("TestCookie=", setCookie);
137137
Assert.Contains("; path=/", setCookie);
138138
Assert.Contains("; httponly", setCookie);
139+
Assert.DoesNotContain("; samesite=", setCookie);
139140
Assert.DoesNotContain("; expires=", setCookie);
140141
Assert.DoesNotContain("; domain=", setCookie);
141142
Assert.DoesNotContain("; secure", setCookie);
@@ -206,6 +207,7 @@ public async Task CookieOptionsAlterSetCookieHeader()
206207
o.CookiePath = "/foo";
207208
o.CookieDomain = "another.com";
208209
o.CookieSecure = CookieSecurePolicy.Always;
210+
o.CookieSameSite = SameSiteEnforcementMode.None;
209211
o.CookieHttpOnly = true;
210212
}, SignInAsAlice, baseAddress: new Uri("http://example.com/base"));
211213

@@ -217,12 +219,14 @@ public async Task CookieOptionsAlterSetCookieHeader()
217219
Assert.Contains(" path=/foo", setCookie1);
218220
Assert.Contains(" domain=another.com", setCookie1);
219221
Assert.Contains(" secure", setCookie1);
222+
Assert.DoesNotContain(" samesite", setCookie1);
220223
Assert.Contains(" httponly", setCookie1);
221224

222225
var server2 = CreateServer(o =>
223226
{
224227
o.CookieName = "SecondCookie";
225228
o.CookieSecure = CookieSecurePolicy.None;
229+
o.CookieSameSite = SameSiteEnforcementMode.Strict;
226230
o.CookieHttpOnly = false;
227231
}, SignInAsAlice, baseAddress: new Uri("http://example.com/base"));
228232

@@ -232,6 +236,7 @@ public async Task CookieOptionsAlterSetCookieHeader()
232236

233237
Assert.Contains("SecondCookie=", setCookie2);
234238
Assert.Contains(" path=/base", setCookie2);
239+
Assert.Contains(" samesite=strict", setCookie2);
235240
Assert.DoesNotContain(" domain=", setCookie2);
236241
Assert.DoesNotContain(" secure", setCookie2);
237242
Assert.DoesNotContain(" httponly", setCookie2);

test/Microsoft.AspNetCore.CookiePolicy.Test/CookiePolicyTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ public class CookiePolicyTests
3636
context.Response.Cookies.Append("D", "D", new CookieOptions { HttpOnly = true });
3737
return Task.FromResult(0);
3838
};
39+
private RequestDelegate SameSiteCookieAppends = context =>
40+
{
41+
context.Response.Cookies.Append("A", "A");
42+
context.Response.Cookies.Append("B", "B", new CookieOptions { SameSite = Http.SameSiteEnforcementMode.None });
43+
context.Response.Cookies.Append("C", "C", new CookieOptions());
44+
context.Response.Cookies.Append("D", "D", new CookieOptions { SameSite = Http.SameSiteEnforcementMode.Lax });
45+
context.Response.Cookies.Append("E", "E", new CookieOptions { SameSite = Http.SameSiteEnforcementMode.Strict });
46+
return Task.FromResult(0);
47+
};
3948

4049
[Fact]
4150
public async Task SecureAlwaysSetsSecure()
@@ -146,6 +155,69 @@ await RunTest("/httpOnlyNone",
146155
}));
147156
}
148157

158+
[Fact]
159+
public async Task SameSiteStrictSetsItAlways()
160+
{
161+
await RunTest("/sameSiteStrict",
162+
new CookiePolicyOptions
163+
{
164+
SameSite = SameSitePolicy.AlwaysStrict
165+
},
166+
SameSiteCookieAppends,
167+
new RequestTest("http://example.com/sameSiteStrict",
168+
transaction =>
169+
{
170+
Assert.NotNull(transaction.SetCookie);
171+
Assert.Equal("A=A; path=/; samesite=strict", transaction.SetCookie[0]);
172+
Assert.Equal("B=B; path=/; samesite=strict", transaction.SetCookie[1]);
173+
Assert.Equal("C=C; path=/; samesite=strict", transaction.SetCookie[2]);
174+
Assert.Equal("D=D; path=/; samesite=strict", transaction.SetCookie[3]);
175+
Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]);
176+
}));
177+
}
178+
179+
[Fact]
180+
public async Task SameSiteLaxSetsItAlways()
181+
{
182+
await RunTest("/sameSiteLax",
183+
new CookiePolicyOptions
184+
{
185+
SameSite = SameSitePolicy.LaxOrStrict
186+
},
187+
SameSiteCookieAppends,
188+
new RequestTest("http://example.com/sameSiteLax",
189+
transaction =>
190+
{
191+
Assert.NotNull(transaction.SetCookie);
192+
Assert.Equal("A=A; path=/; samesite=lax", transaction.SetCookie[0]);
193+
Assert.Equal("B=B; path=/; samesite=lax", transaction.SetCookie[1]);
194+
Assert.Equal("C=C; path=/; samesite=lax", transaction.SetCookie[2]);
195+
Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]);
196+
Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]);
197+
}));
198+
}
199+
200+
[Fact]
201+
public async Task SameSiteNoneLeavesItAlone()
202+
{
203+
await RunTest("/sameSiteNone",
204+
new CookiePolicyOptions
205+
{
206+
HttpOnly = HttpOnlyPolicy.None
207+
},
208+
SameSiteCookieAppends,
209+
new RequestTest("http://example.com/sameSiteNone",
210+
transaction =>
211+
{
212+
Assert.NotNull(transaction.SetCookie);
213+
Assert.Equal("A=A; path=/", transaction.SetCookie[0]);
214+
Assert.Equal("B=B; path=/", transaction.SetCookie[1]);
215+
Assert.Equal("C=C; path=/", transaction.SetCookie[2]);
216+
Assert.Equal("D=D; path=/; samesite=lax", transaction.SetCookie[3]);
217+
Assert.Equal("E=E; path=/; samesite=strict", transaction.SetCookie[4]);
218+
}));
219+
}
220+
149221
[Fact]
150222
public async Task CookiePolicyCanHijackAppend()
151223
{

0 commit comments

Comments
 (0)