Skip to content
This repository has been archived by the owner on Nov 22, 2018. It is now read-only.

Add Secure cookie flag option to session cookie #28

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/Microsoft.AspNet.Session/CookieSecureOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW we just changed the copyright notice to:

Copyright (c) .NET Foundation. All rights reserved.

// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Microsoft.AspNet.Session
{
/// <summary>
/// Determines how the identity cookie's security property is set.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW a lot of this new code mentions "identity" yet this is all about session. Is that deliberate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should rename that too then

/// </summary>
public enum CookieSecureOption
{
/// <summary>
/// If the URI that provides the cookie is HTTPS, then the cookie will only be returned to the server on
/// subsequent HTTPS requests. Otherwise if the URI that provides the cookie is HTTP, then the cookie will
/// be returned to the server on all HTTP and HTTPS requests. This is the default value because it ensures
/// HTTPS for all authenticated requests on deployed servers, and also supports HTTP for localhost development
/// and for servers that do not have HTTPS support.
/// </summary>
SameAsRequest,

/// <summary>
/// CookieOptions.Secure is never marked true. Use this value when your login page is HTTPS, but other pages
/// on the site which are HTTP also require authentication information. This setting is not recommended because
/// the authentication information provided with an HTTP request may be observed and used by other computers
/// on your local network or wireless connection.
/// </summary>
Never,

/// <summary>
/// CookieOptions.Secure is always marked true. Use this value when your login page and all subsequent pages
/// requiring the authenticated identity are HTTPS. Local development will also need to be done with HTTPS urls.
/// </summary>
Always,
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.AspNet.Session/SessionDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public static class SessionDefaults
{
public static string CookieName = ".AspNet.Session";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tratcher are all these suppose to be readonly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

public static string CookiePath = "/";
public static CookieSecureOption CookieSecure = CookieSecureOption.SameAsRequest;
public static bool CookieHTTPOnly = true;
}
}
9 changes: 9 additions & 0 deletions src/Microsoft.AspNet.Session/SessionMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ private void SetCookie()
Path = _options.CookiePath ?? SessionDefaults.CookiePath,
};

if (_options.CookieSecure == CookieSecureOption.SameAsRequest)
{
cookieOptions.Secure = _context.Request.IsHttps;
}
else
{
cookieOptions.Secure = _options.CookieSecure == CookieSecureOption.Always;
}

_context.Response.Cookies.Append(_options.CookieName, _sessionKey, cookieOptions);

_context.Response.Headers.Set(
Expand Down
9 changes: 8 additions & 1 deletion src/Microsoft.AspNet.Session/SessionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@ public class SessionOptions
/// default is true, which means the cookie will only be passed to HTTP requests and is not made available
/// to script on the page.
/// </summary>
public bool CookieHttpOnly { get; set; } = true;
public bool CookieHttpOnly { get; set; } = SessionDefaults.CookieHTTPOnly;

/// <summary>
/// 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.
/// </summary>
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(20);

/// <summary>
/// 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.
/// </summary>
public CookieSecureOption CookieSecure { get; set; } = SessionDefaults.CookieSecure;

/// <summary>
/// Gets or sets the session storage manager. This overrides any session store passed into the middleware constructor.
/// </summary>
Expand Down
135 changes: 135 additions & 0 deletions test/Microsoft.AspNet.Session.Tests/SessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,140 @@ public async Task ExpiredSession_LogsWarning()
Assert.Equal(LogLevel.Warning, sink.Writes[1].LogLevel);
}
}

[Fact]
public async Task SettingSecureCookieOptionToNeverWillEnsureSecureFlagNotSet()
{
using (var server = TestServer.Create(app =>
{
app.UseInMemorySession(null, cookieoption => cookieoption.CookieSecure = CookieSecureOption.Never);
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);
});
},
services => services.AddOptions()))
{
var client = server.CreateClient();
var response = await client.GetAsync(string.Empty);
response.EnsureSuccessStatusCode();
IEnumerable<string> values;
Assert.True(response.Headers.TryGetValues("Set-Cookie", out values));
Assert.Equal(1, values.Count());
Assert.True(!string.IsNullOrWhiteSpace(values.First()));
Assert.DoesNotContain("secure", values.First());
}
}

[Fact]
public async Task SettingSecureCookieOptionToAlwaysWillEnsureSecureFlagSet()
{
using (var server = TestServer.Create(app =>
{
app.UseInMemorySession(null, cookieoption => cookieoption.CookieSecure = CookieSecureOption.Always);
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);
});
},
services => services.AddOptions()))
{
var client = server.CreateClient();
var response = await client.GetAsync(string.Empty);
response.EnsureSuccessStatusCode();
IEnumerable<string> values;
Assert.True(response.Headers.TryGetValues("Set-Cookie", out values));
Assert.Equal(1, values.Count());
Assert.True(!string.IsNullOrWhiteSpace(values.First()));
Assert.Contains("secure", values.First());
}
}

[Fact]
public async Task SettingSecureCookieOptionToSameAsRequestWillEnsureSecureFlagNotSetWhileNotUsingSSL()
{
using (var server = TestServer.Create(app =>
{
app.UseInMemorySession(null, cookieoption => cookieoption.CookieSecure = CookieSecureOption.SameAsRequest);
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);
});
},
services => services.AddOptions()))
{
var client = server.CreateClient();
var response = await client.GetAsync(string.Empty);
response.EnsureSuccessStatusCode();
IEnumerable<string> values;
Assert.True(response.Headers.TryGetValues("Set-Cookie", out values));
Assert.Equal(1, values.Count());
Assert.True(!string.IsNullOrWhiteSpace(values.First()));
Assert.DoesNotContain("secure", values.First());
}
}

[Fact]
public async Task SettingHTTPOnlyCookieOptionToTrueWillEnsureHTTPOnlyFlagSet()
{
using (var server = TestServer.Create(app =>
{
app.UseInMemorySession(null, cookieoption => cookieoption.CookieHttpOnly = true);
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);
});
},
services => services.AddOptions()))
{
var client = server.CreateClient();
var response = await client.GetAsync(string.Empty);
response.EnsureSuccessStatusCode();
IEnumerable<string> values;
Assert.True(response.Headers.TryGetValues("Set-Cookie", out values));
Assert.Equal(1, values.Count());
Assert.True(!string.IsNullOrWhiteSpace(values.First()));
Assert.Contains("httponly", values.First());
}
}

[Fact]
public async Task SettingHTTPOnlyCookieOptionToFalseWillEnsureHTTPOnlyFlagNotSet()
{
using (var server = TestServer.Create(app =>
{
app.UseInMemorySession(null, cookieoption => cookieoption.CookieHttpOnly = false);
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);
});
},
services => services.AddOptions()))
{
var client = server.CreateClient();
var response = await client.GetAsync(string.Empty);
response.EnsureSuccessStatusCode();
IEnumerable<string> values;
Assert.True(response.Headers.TryGetValues("Set-Cookie", out values));
Assert.Equal(1, values.Count());
Assert.True(!string.IsNullOrWhiteSpace(values.First()));
Assert.DoesNotContain("httponly", values.First());
}
}
}
}