diff --git a/src/Microsoft.AspNetCore.Http.Abstractions/CookieBuilder.cs b/src/Microsoft.AspNetCore.Http.Abstractions/CookieBuilder.cs index f0cdc3ad..203a7a04 100644 --- a/src/Microsoft.AspNetCore.Http.Abstractions/CookieBuilder.cs +++ b/src/Microsoft.AspNetCore.Http.Abstractions/CookieBuilder.cs @@ -67,6 +67,11 @@ public virtual string Name /// public virtual TimeSpan? Expiration { get; set; } + /// + /// Gets or sets the max-age for the cookie. + /// + public virtual TimeSpan? MaxAge { get; set; } + /// /// Creates the cookie options from the given . /// @@ -92,6 +97,7 @@ public virtual CookieOptions Build(HttpContext context, DateTimeOffset expiresFr Path = Path ?? "/", SameSite = SameSite, HttpOnly = HttpOnly, + MaxAge = MaxAge, Domain = Domain, Secure = SecurePolicy == CookieSecurePolicy.Always || (SecurePolicy == CookieSecurePolicy.SameAsRequest && context.Request.IsHttps), Expires = Expiration.HasValue ? expiresFrom.Add(Expiration.Value) : default(DateTimeOffset?) diff --git a/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs b/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs index 017b6520..e01a759c 100644 --- a/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs +++ b/src/Microsoft.AspNetCore.Http.Features/CookieOptions.cs @@ -53,5 +53,11 @@ public CookieOptions() /// /// true if a cookie must not be accessible by client-side script; otherwise, false. public bool HttpOnly { get; set; } + + /// + /// Gets or sets the max-age for the cookie. + /// + /// The max-age date and time for the cookie. + public TimeSpan? MaxAge { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Http/Internal/ResponseCookies.cs b/src/Microsoft.AspNetCore.Http/Internal/ResponseCookies.cs index 85e006dc..7c6e3e03 100644 --- a/src/Microsoft.AspNetCore.Http/Internal/ResponseCookies.cs +++ b/src/Microsoft.AspNetCore.Http/Internal/ResponseCookies.cs @@ -61,6 +61,7 @@ public void Append(string key, string value, CookieOptions options) Domain = options.Domain, Path = options.Path, Expires = options.Expires, + MaxAge = options.MaxAge, Secure = options.Secure, SameSite = (Net.Http.Headers.SameSiteMode)options.SameSite, HttpOnly = options.HttpOnly diff --git a/test/Microsoft.AspNetCore.Http.Abstractions.Tests/CookieBuilderTests.cs b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/CookieBuilderTests.cs index 386374b2..dd540ccc 100644 --- a/test/Microsoft.AspNetCore.Http.Abstractions.Tests/CookieBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Http.Abstractions.Tests/CookieBuilderTests.cs @@ -38,6 +38,16 @@ public void ComputesExpiration() Assert.Equal(now.AddHours(1), options.Expires); } + [Fact] + public void ComputesMaxAge() + { + Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).MaxAge); + + var now = TimeSpan.FromHours(1); + var options = new CookieBuilder { MaxAge = now }.Build(new DefaultHttpContext()); + Assert.Equal(now, options.MaxAge); + } + [Fact] public void CookieBuilderPreservesDefaultPath() { diff --git a/test/Microsoft.AspNetCore.Http.Tests/ResponseCookiesTest.cs b/test/Microsoft.AspNetCore.Http.Tests/ResponseCookiesTest.cs index 3693a428..8ab9dd1c 100644 --- a/test/Microsoft.AspNetCore.Http.Tests/ResponseCookiesTest.cs +++ b/test/Microsoft.AspNetCore.Http.Tests/ResponseCookiesTest.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Text; using Microsoft.AspNetCore.Http.Internal; -using Microsoft.Extensions.ObjectPool; using Microsoft.Net.Http.Headers; using Xunit; @@ -74,6 +72,23 @@ public void NoParamsDeleteRemovesCookieCreatedByAdd() Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]); } + [Fact] + public void ProvidesMaxAgeWithCookieOptionsArgumentExpectMaxAgeToBeSet() + { + var headers = new HeaderDictionary(); + var cookies = new ResponseCookies(headers, null); + var cookieOptions = new CookieOptions(); + var maxAgeTime = TimeSpan.FromHours(1); + cookieOptions.MaxAge = TimeSpan.FromHours(1); + var testcookie = "TestCookie"; + + cookies.Append(testcookie, testcookie, cookieOptions); + + var cookieHeaderValues = headers[HeaderNames.SetCookie]; + Assert.Equal(1, cookieHeaderValues.Count); + Assert.Contains($"max-age={maxAgeTime.TotalSeconds.ToString()}", cookieHeaderValues[0]); + } + public static TheoryData EscapesKeyValuesBeforeSettingCookieData { get