diff --git a/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs b/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs index 8f6ff45..23386f2 100644 --- a/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs +++ b/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs @@ -156,7 +156,15 @@ private void SetCookie() HttpOnly = _options.CookieHttpOnly, Path = _options.CookiePath ?? SessionDefaults.CookiePath, }; - + if (_options.CookieSecure == CookieSecurePolicy.SameAsRequest) + { + cookieOptions.Secure = _context.Request.IsHttps; + } + else + { + cookieOptions.Secure = _options.CookieSecure == CookieSecurePolicy.Always; + } + _context.Response.Cookies.Append(_options.CookieName, _cookieValue, cookieOptions); _context.Response.Headers["Cache-Control"] = "no-cache"; diff --git a/src/Microsoft.AspNetCore.Session/SessionOptions.cs b/src/Microsoft.AspNetCore.Session/SessionOptions.cs index 12caa67..7240ece 100644 --- a/src/Microsoft.AspNetCore.Session/SessionOptions.cs +++ b/src/Microsoft.AspNetCore.Session/SessionOptions.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Session; +using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Builder { @@ -35,6 +36,13 @@ public class SessionOptions /// public bool CookieHttpOnly { get; set; } = true; + /// + /// Determines if the cookie should only be transmitted on HTTPS request. The default is to limit the cookie + /// to HTTPS requests if the page which is doing the SignIn is also HTTPS. If you have an HTTPS sign in page + /// and portions of your site are HTTP you may need to change this value. + /// + public CookieSecurePolicy CookieSecure { get; set; } + /// /// The IdleTimeout indicates how long the session can be idle before its contents are abandoned. Each session access /// resets the timeout. Note this only applies to the content of the session, not the cookie. diff --git a/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs b/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs index 7c81e78..f061f28 100644 --- a/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs +++ b/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs @@ -87,6 +87,58 @@ public async Task SettingAValueCausesTheCookieToBeCreated() } } + [Theory] + [InlineData(CookieSecurePolicy.Always, "http://example.com/testpath", true)] + [InlineData(CookieSecurePolicy.Always, "https://example.com/testpath", true)] + [InlineData(CookieSecurePolicy.None, "http://example.com/testpath", false)] + [InlineData(CookieSecurePolicy.None, "https://example.com/testpath", false)] + [InlineData(CookieSecurePolicy.SameAsRequest, "http://example.com/testpath", false)] + [InlineData(CookieSecurePolicy.SameAsRequest, "https://example.com/testpath", true)] + public async Task SecureSignInCausesSecureOnlyCookieByDefault( + CookieSecurePolicy cookieSecurePolicy, + string requestUri, + bool shouldBeSecureOnly) + { + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseSession(new SessionOptions + { + CookieName = "TestCookie", + CookieSecure = cookieSecurePolicy + }); + app.Run(context => + { + Assert.Null(context.Session.GetString("Key")); + context.Session.SetString("Key", "Value"); + Assert.Equal("Value", context.Session.GetString("Key")); + return Task.FromResult(0); + }); + }) + .ConfigureServices(services => + { + services.AddDistributedMemoryCache(); + services.AddSession(); + }); + using (var server = new TestServer(builder)) + { + var client = server.CreateClient(); + var response = await client.GetAsync(requestUri); + response.EnsureSuccessStatusCode(); + IEnumerable values; + Assert.True(response.Headers.TryGetValues("Set-Cookie", out values)); + Assert.Equal(1, values.Count()); + if (shouldBeSecureOnly) + { + Assert.Contains("; secure", values.First()); + } + else + { + Assert.DoesNotContain("; secure", values.First()); + } + } + } + [Fact] public async Task SessionCanBeAccessedOnTheNextRequest() {