diff --git a/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs b/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs index 8f6ff452f6..a9160e4a67 100644 --- a/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs +++ b/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs @@ -156,6 +156,14 @@ namespace Microsoft.AspNetCore.Session 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); diff --git a/src/Microsoft.AspNetCore.Session/SessionOptions.cs b/src/Microsoft.AspNetCore.Session/SessionOptions.cs index 12caa678ad..41c13a8a06 100644 --- a/src/Microsoft.AspNetCore.Session/SessionOptions.cs +++ b/src/Microsoft.AspNetCore.Session/SessionOptions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Session; namespace Microsoft.AspNetCore.Builder @@ -35,6 +36,10 @@ namespace Microsoft.AspNetCore.Builder /// public bool CookieHttpOnly { get; set; } = true; + /// + /// Determines if the cookie should only be transmitted on HTTPS requests. + public CookieSecurePolicy CookieSecure { get; set; } = CookieSecurePolicy.None; + /// /// 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 7c81e78383..b2a43f27d6 100644 --- a/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs +++ b/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs @@ -87,6 +87,59 @@ namespace Microsoft.AspNetCore.Session } } + [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 SecureSessionBasedOnHttpsAndSecurePolicy( + 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() {