Skip to content

ResponseCookies.Delete(key, cookieOptions) doesn't delete cookie if the caller provides a non-null max-age #52159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
rjpowers10 opened this issue Nov 17, 2023 · 11 comments
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug. help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team
Milestone

Comments

@rjpowers10
Copy link

rjpowers10 commented Nov 17, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I've run into a subtle change in behavior in ResponseCookies.Delete(key, cookieOptions) where the cookie isn't deleted if the caller provides a non-null MaxAge.

.NET 6 behavior

Relevant line of code

Append(key, string.Empty, new CookieOptions
{
    Path = options.Path,
    Domain = options.Domain,
    Expires = DateTimeOffset.UnixEpoch,
    Secure = options.Secure,
    HttpOnly = options.HttpOnly,
    SameSite = options.SameSite
});

A new CookieOptions object is created, copying the values for Path, Domain, Secure, HttpOnly, and SameSite. Expires is forced to DateTimeOffset.UnixEpoch. MaxAge is left with its default value of null.

End result: cookie is deleted regardless of what the caller provides for Expires and MaxAge.

.NET 8 behavior

Relevant line of code

Append(key, string.Empty, new CookieOptions(options)
{
    Expires = DateTimeOffset.UnixEpoch,
});

The new CookieOptions is created using the copy constructor. This copies all the properties, including MaxAge.

End result: if the caller happens to provide a non-null value of MaxAge the cookie will not be deleted. Or at least, this is the behavior I'm seeing in Chrome 119.0.6045.160. I believe browsers give max-age precedence over expires.

I'm running into this because I have a cached CookieOptions that I'm using for both the set and the delete (mostly to ensure the same Path is used for both calls). But as a side effect, I'm sending a non-null MaxAge to the delete and the cookie is not being deleted.

Workaround

Callers can ensure that MaxAge is null before calling Delete.

Expected Behavior

The cookie should be deleted even if the caller happens to provide a non-null MaxAge.

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

No response

Anything else?

No response

@ghost ghost added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Nov 17, 2023
@Tratcher
Copy link
Member

Tratcher commented Nov 17, 2023

Ah, yeah, I can see how that would be a problem. Delete should set MaxAge to null, 0, or -1.

The good thing is that the value is set to string.Empty so that even if the cookie isn't deleted, at least the value is. Most apps should treat the empty value as a missing cookie. I'm a bit surprised the browser doesn't.

@Tratcher Tratcher added bug This issue describes a behavior which is not expected - a bug. help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team labels Nov 17, 2023
@rjpowers10
Copy link
Author

rjpowers10 commented Nov 20, 2023

Most apps should treat the empty value as a missing cookie.

Actually, I've found another subtle change in behavior (arguably should be a new issue but I think it's relevant to this discussion).

In my app I'm intentionally setting a cookie with a blank value and just using the presence / absence of the cookie as a flag. In .NET 6 this worked. In .NET 8 my app can't see the cookie at all (it's not present in HttpRequest.Cookies), even though I can see it (with its blank value) in the Chrome debugger.

It's easy enough for me to change my app to set a dummy value, so I don't have strong feelings on what the behavior should be. Perhaps this one is my own fault for relying on a blank value. Just something I've run into and others might run into as well.

@carlreinke
Copy link

carlreinke commented Dec 2, 2023

I also stumbled over empty cookies being missing. RFC 6265 permits cookie-value to be empty, so I'd expect the cookie to exist with an empty value in HttpRequest.Cookies. I can always fish the cookie name/existence out of HttpRequest.Headers, of course, but that's not very nice.

@Tratcher
Copy link
Member

Tratcher commented Dec 4, 2023

@rjpowers10 @carlreinke Please open a separate issue for that scenario.

@danirzv
Copy link
Contributor

danirzv commented Jan 10, 2024

Do we need something like this?

namespace Microsoft.AspNetCore.Http;

public class CookieOptions
{
     public DateTimeOffset? Expires 
     { 
        get; 
        set
        {
+           MaxAge = default;
+           _expires = value;
           } 
      }
}

@Tratcher
Copy link
Member

@danirzv No, that depends on the order properties are set in. It's better to have the Delete method reason about what shape the cookie should take.

@ddjerqq
Copy link

ddjerqq commented Jan 21, 2024

Is this issue still relevant? I could try tackling it

@Tratcher
Copy link
Member

@ddjerqq Yes please.

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@MattyLeslie
Copy link
Contributor

MattyLeslie commented Apr 15, 2024

@Tratcher The current call on append() at the bottom of the delete method is as follows:

Append(key, string.Empty, new CookieOptions(options)
        {
            Expires = DateTimeOffset.UnixEpoch,
        });

Would amending it to force maxAge to null be satisfactory?

Append(key, string.Empty, new CookieOptions
        {
            Path = options.Path,
            Domain = options.Domain,
            Expires = DateTimeOffset.UnixEpoch,
            MaxAge = null, // Explicitly set MaxAge to zero to ensure deletion.
            Secure = options.Secure,
            HttpOnly = options.HttpOnly,
            SameSite = options.SameSite
        });

If that is the case, I'd be happy to contribute a PR.

@Tratcher
Copy link
Member

Yes, that would likely work.

@MattyLeslie
Copy link
Contributor

This should be closed after merged changes in #55169

@Tratcher Tratcher added this to the 9.0-preview4 milestone May 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug. help candidate Indicates that the issues may be a good fit for community to help with. Requires work from eng. team
Projects
None yet
Development

No branches or pull requests

7 participants