When a user authenticates in ASP.NET using forms authentication, the forms authentication system writes an authentication ticket to the user's cookies that allows the site to remember that the user has already been authenticated. This authentication ticket either lasts for the duration of the user's visit, for a specified duration, or can be persisted on the user's machine. If a timeout is specified, it can be a sliding or absolute duration. To specify this duration, from Web.config use the <forms> element's timeout and slidingExpiration attributes. It's a little confusing because the rules governing the cookie expiry changed between ASP.NET 1.x and 2.0.
In ASP.NET 1.x, the timeout settings are ignored for persistent cookies. For ASP.NET 2.0, the timeout settings only apply to the persistent cookies. Moreover, in ASP.NET 1.x the expiry defaults to be sliding, while in ASP.NET 2.0 it defaults to be absolute.
What this means, in English, is that in ASP.NET 1.x if you created a non-persistent authentication ticket cookie, it would last for the specified timeout (default is 30 minutes) with the timeout automatically being refreshed each time the user visited a new page in the site. In short, if a user visited a site, logged on (but did not choose to have their credentials remembered for subsequent visits), the cookie would be good until the user let 30 minutes (or whatever) lapse between visiting the site. If the user logged on with a persistent cookie, the cookie would have a duration of 50 years! Crazy. This, as we'll discuss shortly, carries with it some security concerns.
ASP.NET 2.0 takes a much more conservative approach toward authentication ticket cookie expiries. For non-persistent cookies, the cookie lasts for the duration that the browser's open. There's no timeout, but once the browser closes, the user will need to re-authenticate when the next visit the site. Persistent cookies have an expiry based on the timeout value (again, a default of 30) and it defaults to an absolute timeout, meaning that after 30 minutes, regardless of how often they hit the site, the user will have to reauthenticate. This settings, of course, can be changed through the <forms> element as noted above.
The authentication ticket is persisted on the user's machine across browser restarts (but only for the specified timeout duration) in ASP.NET 2.0 if the Login control's “Remember me next time” checkbox is checked. One potential security hole here is introduced because of the disconnect between authentication and the user's underlying information in the Membership system (or in some custom user store system). Consider the following scenario:
- User A logs into site and checks “Remember me next time“. Imagine that the site has a timeout of three days.
- User A then posts messages to the forum on this site and blasts a lot of spam and other garbage
- Mr. Administrator sets User A's IsApproved value to False. If the user goes to Login.aspx, they won't be able to validate their credentials, but............
- User A has a persistent authentication ticket cookie on his machine that is good for three days. So if User A visits tomorrow, he's still able to pass the authorization checks. If there's no additional logic on making posts that ensures the poster is approved, blam, User A is back to making his garbage posts.
Any suggestions/recommendations on preventing this? I see two options:
- The application should not allow posts from unapproved users. Such a check could be done right before the post is committed to the database, for instance. This would prevent an unapproved user from making a post, but would still let them get on the site via the persistent authentication ticket cookie (until it expired after three days).
- Use a Session variable to indicate if a user has had their Approved status validated. If so, do nothing. If not, then check Membership.GetUser().IsApproved to make sure it has a value of True. If it does, set the Session variable to indicate that the check has been performed. In a base page class, override OnInit and perform this logic. Then have all ASP.NET pages in sections where only authenticated users are allowed derive from this base page class so that all pages make sure that the users visiting the site are approved.
Any other suggestions or workarounds for this issue? Granted, one could simply shorten the persistent cookie timeout, but that kind of defeats the utility of “Remember me next time.” The good news is that because ASP.NET 2.0 is much more conservative than ASP.NET 1.x by default, the window with which an unapproved user has to access the site is greatly reduced.