Skip to content
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

Clarify behavior of CookieAuthenticationOptions.Cookie.Expiration #4671

Closed
natemcmaster opened this issue Jul 5, 2017 · 18 comments
Closed
Assignees
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer breaking-change This issue / pr will introduce a breaking change, when resolved / merged. Done This issue has been fixed

Comments

@natemcmaster
Copy link
Contributor

aspnet/Security#1285 added CookieAuthenticationOptions.Cookie.Expiration, a nullable TimeSpan, but we still have CookieAuthenticationOptions.ExpireTimeSpan. Cookie.Expiration is ignored. We should clarify what Cookie.Expiration is meant to do and how it interacts with ExpireTimeSpan

@natemcmaster natemcmaster changed the title Clarify behavior when CookieAuthenticationOptions.Cookie.Expiration is set to null Clarify behavior of CookieAuthenticationOptions.Cookie.Expiration Jul 5, 2017
@Tratcher
Copy link
Member

Where? In doc comments? Samples? Or make it throw?

@natemcmaster
Copy link
Contributor Author

For others new to this thread, let me add a little context. The reason for the ExpireTimeSpan API is that it represents the expiration of the ticket, not the cookie that contains the ticket.

Example: we haven't figure out how to handle something like this.

Cookie.Expiration = TimeSpan.FromDays(9999);
ExpiresTimeSpan = TimeSpan.FromMinutes(30);

This would create an auth ticket valid for the next 30 minutes, but the cookie will be stored by the browser 9999 days. That means in a month, the browser will still send the cookie, but auth won't accept the ticket contained in the cookie. Until 2.0.0, this wasn't a problem because users couldn't configure cookie expiration manually. We always inferred cookie expiration from ticket expiration.

@natemcmaster
Copy link
Contributor Author

@Tratcher I think it would be useful to assert that Cookie.Expiration >= ExpireTimeSpan. If not, throw. There is nothing wrong with Cookie.Expiration >= ExpireTimeSpan, it's just means you have a cookie that will never work and doesn't get cleaned up right away. But on the other hand, if someone configures Cookie.Expiration but not ExpireTimeSpan, they will get an error.

@Tratcher
Copy link
Member

Tratcher commented Sep 13, 2017

Cookie.Expiration is currently ignored. It never makes it to the browser. https://github.com/aspnet/Security/blob/a53bf093a7d86b35e019c80515c92d7626982325/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs#L181

Auth cookies have a separate flag for persisting cookies (via setting their expiration) and it's intentionally only available per request because it requires user consent.
https://github.com/aspnet/Security/blob/a53bf093a7d86b35e019c80515c92d7626982325/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs#L222-L225

Setting Cookie.Expiration is an error in the current design, we just don't validate it.

@HaoK
Copy link
Member

HaoK commented Sep 13, 2017

So in 2.1, we should just update CookieAuthenticationHandler to throw if expiration is ever set before using cookies?

@Tratcher
Copy link
Member

Yes, that's an option. I don't know if @Eilon will take kindly to throwing new exceptions though.

@natemcmaster
Copy link
Contributor Author

The way I see it, we have 3 options:

  1. Break. Throw a new exception or change the API surface. Remove Cookie.Expiration or throw when it's used.
  2. Try to reason about the interaction ExpireTimeSpan and Cookie.Expiration. One implies the other? Take the shorter of the two? Idk. Some heuristic that seems reasonable. We couldn't agree on one in the initial implementation so we left it out.
  3. Do nothing. Users will inevitably end up with cookies that don't expire because they used the wrong one.

@HaoK
Copy link
Member

HaoK commented Sep 13, 2017

I like option 1, throwing an exception (pointing them at the right setting) instead of just ignoring it seems way better. It would be a really easy breaking change for people to fix, and its not like setting it actually did what they wanted anyways...

@scottsauber
Copy link
Contributor

Random user here but my vote in order of preference is:

  1. Throw an exception as early as possible like on Startup with the pointer to the right setting. Preferably not have it throw on issuing of a cookie which would be less discoverable post-upgrade.
  2. Mark as Obsolete with docs to new setting.
  3. Flat out removal. Don't love this bc no pointer to correct behavior then.

Sidenote - It'd be cool if MS or the community provided upgrade Codemods in the way of Roslyn analyzers to autofix your code on upgrades. The React team at Facebook does this for instance..

@Eilon
Copy link
Member

Eilon commented Sep 14, 2017

I of course don't like the idea of throwing from the setter because of the breaking change aspect.

But, worse than that, property setters that throw on condition of another property's value are a Very Bad Idea™️ because you can only set the properties in a particular order, and that's a serious usability pattern.

So, that leaves throwing at "runtime" (either in some sort of property validation phase, or during a request), but that's still a breaking change, so I'm still not a fan.

I'm ok with marking something as obsolete if we think that's an appropriate choice, then we can delete in 3.0. Is that reasonable?

@Tratcher
Copy link
Member

On a related note, that property is coming from a shared library (RequestPathBaseCookieBuilder), CookieAuth doesn't actually own it. We'd have to derive and override it to add an exception. I don't think an obsolete attribute would even surface to the user, we're not obsoleting it on the base type. And it can't be removed from the base type as it's valid for other uses.

Back to the drawing board.

https://github.com/aspnet/Security/blob/a53bf093a7d86b35e019c80515c92d7626982325/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs#L65
https://github.com/aspnet/Security/blob/a53bf093a7d86b35e019c80515c92d7626982325/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs#L16

@scottsauber
Copy link
Contributor

scottsauber commented Sep 14, 2017

There is a virtual Validate method off of the base class AuthenticationSchemeOptions that's not being overridden on CookieAuthenticationOptions

Not sure how many people would actually call that... but... it's there.

https://github.com/aspnet/Security/blob/b3e4bf382c9676e2d912636b7ee0255cad244e11/src/Microsoft.AspNetCore.Authentication/AuthenticationSchemeOptions.cs#L16

Edit: Still a breaking change though I guess.

@Eilon
Copy link
Member

Eilon commented Sep 14, 2017

Indeed, more fundamentally, the question is: How bad is this really?

We can very easily document this in the /// comments for Intellisense and the docs site. I'm guessing a lot of people never set this stuff at all and just use the defaults (which are hopefully somewhat sensible). And hopefully those who set it will see the docs - at least if they see some odd behavior and need to get more info...

@HaoK
Copy link
Member

HaoK commented Sep 14, 2017

You could also argue that as far as breaking changes go, its not too bad, since this is actually better behavior. Setting the property does nothing, and is basically always developer error Anyone who sets this really needed to set cookie.ExpiresTimeSpan instead, so the exception would let them know their cookie actually isn't expiring when they expect, instead of silently being ignored...

@RickBlouch
Copy link

For what it's worth, from an outside perspective I think what lead to my confusion was mostly the fact that the same property when set on the SessionOptions.CookieBuilder does have an impact on the resulting cookie expiration. I Initially had session set the way I wanted it, then applied the same settings to the auth cookie only to find that the session cookie was being created as anticipated, however the auth cookie was not.

After I noticed the auth cookie's expiration was a year out I looked more closely at the CookieAuthenticationOptions and figured it out so it wasn't a huge deal but it does seem inconsistent at a higher level.

 builder.UseSession(new SessionOptions
 {
      IdleTimeout = new TimeSpan(0, 20, 0), // server side timeout
      Cookie = new CookieBuilder
      {
           Expiration = new TimeSpan(0, 2, 0) // cookie expiration
      }
});

vs

.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
    o.ExpireTimeSpan = new TimeSpan(0, 20, 0); //auth ticket valid for on server
    o.Cookie = new CookieBuilder()
    {
        //Expiration = doesn't matter - Not used
    };
});

I guess my question is why not let developers use the CookieBuilder.Expiration for the auth cookie expiration, and just issue a warning when the configuration doesn't make sense like the EF stack does in various scenarios where they recommend a better way.

As it stands today is there any way to set the auth cookie itself to expire in 20 minutes while IsPersistent = false ?

@Tratcher
Copy link
Member

@RickBlouch It's funny you compare it to Session as that has most of the same issues but possibly worse.
A) Setting the cookie expiration violates the definition of a session cookie. Cookies with explicit expiration will not be cleared when the browser is closed.
B) Session has no sliding expiration implementation for its cookie. Your cookie will always expire in the specified two minutes regardless of user activity. Session only has sliding expiration for the server cache.

Prior to 2.0 there was no way to set the Session cookie expiration. It was only added because it was part of the shared CookieBuilder infrastructure and we weren't sure it was worth blocking. Now I think the sliding expiration issue makes it worth blocking.

And no, you cannot directly control the auth cookie's expiration value without enabling IsPersistent (per login). That's because you're supposed to get user consent before persisting auth cookies beyond the current browser session (e.g. "remember me").

@Eilon
Copy link
Member

Eilon commented Jul 17, 2018

Marking as a breaking change for 3.0. We should add options validation that throws if the incorrect cookie property was set.

@aspnet-hello aspnet-hello transferred this issue from aspnet/Security Dec 13, 2018
@aspnet-hello aspnet-hello added this to the 3.0.0-preview2 milestone Dec 13, 2018
@aspnet-hello aspnet-hello added 1 - Ready area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer breaking-change This issue / pr will introduce a breaking change, when resolved / merged. labels Dec 13, 2018
@Eilon Eilon modified the milestones: 3.0.0-preview2, 3.0.0-preview3 Dec 20, 2018
@winzig
Copy link

winzig commented Feb 8, 2019

So is this going to be resolved in .NET 3.0? I just spent an hour trying to figure out why my cookies were always expiring in 2 weeks even though I was setting options.Cookie.Expiration to another timespan. I searched for a while, and then finally decided to search the source code where I finally found a clue:

https://github.com/aspnet/AspNetCore/blob/32da2679f86ec0b19c6bcad25ed500ddf5ff845f/src/Security/Authentication/test/CookieTests.cs#L148

@HaoK HaoK closed this as completed in #8967 Apr 2, 2019
@HaoK HaoK added Done This issue has been fixed and removed 1 - Ready labels Apr 2, 2019
@ghost ghost locked as resolved and limited conversation to collaborators Dec 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer breaking-change This issue / pr will introduce a breaking change, when resolved / merged. Done This issue has been fixed
Projects
None yet
Development

No branches or pull requests

8 participants