//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * FormsAuthenticationTicket class * * Copyright (c) 1999 Microsoft Corporation */ namespace System.Web.Security { using System.Security.Principal; using System.Security.Permissions; using System.Web.Configuration; using System.Runtime.Serialization; /// /// This class encapsulates the information represented in /// an authentication cookie as used by FormsAuthenticationModule. /// [Serializable] public sealed class FormsAuthenticationTicket { /// /// A one byte version number for future /// use. /// public int Version { get { return _Version;}} /// /// The user name associated with the /// authentication cookie. Note that, at most, 32 bytes are stored in the /// cookie. /// public String Name { get { return _Name;}} /// /// The date/time at which the cookie /// expires. /// public DateTime Expiration { get { return _Expiration;}} /// /// The time at which the cookie was originally /// issued. This can be used for custom expiration schemes. /// public DateTime IssueDate { get { return _IssueDate;}} /// /// True if a durable cookie was issued. /// Otherwise, the authentication cookie is scoped to the browser lifetime. /// public bool IsPersistent { get { return _IsPersistent;}} /// /// [To be supplied.] /// public bool Expired { get { /* * Two DateTime instances can only be compared if they are of the same DateTimeKind. * Therefore we normalize everything to UTC to do the comparison. See comments on * the ExpirationUtc property for more information */ return (ExpirationUtc < DateTime.UtcNow); } } /// /// [To be supplied.] /// public String UserData { get { return _UserData;}} /// /// [To be supplied.] /// public String CookiePath { get { return _CookiePath;}} /* * We always prefer UTC expiration dates to work around issues like a daylight * saving time changes between the time the ticket was issued and the time the * ticket was checked. If we have a firm UTC expiration date, just use it * directly. * * If we don't have a firm UTC expiration date, try converting the developer- * provided date to UTC before doing the comparison. There are three types of * DateTime, and the .NET Framework converts as so: * * - The DateTime is already UTC, in which case it is returned unmodified. * - The DateTime is local, in which case the .NET Framework converts it to * UTC. There is also a hidden bit in the DateTime struct which essentially * states whether daylight saving time was active when this DateTime was * generated, i.e. whether this was 2:02 AM PDT or 2:02 AM PST. The .NET * framework handles round-tripping Local <-> UTC correctly, but comparisons * can still fail as described in detail below. * - The DateTime is of an undefined type, in which case it is implicitly * treated as local in a manner consistent with .NET 1.1. * * However, this alone is insufficient to work around DST issues when comparing * local dates. For example, assume that a ticket is issued on Nov 6, 2011 at * 1:30 AM PDT (UTC -0700) with a timeout period of 20 minutes. The expiration * date is thus calculated to be Nov 6, 2011 at 1:50 AM PDT (UTC -0700). Now * say a request comes in 25 minutes after expiration; the time is currently * 1:15 AM PST (UTC -0800). [A DST boundary has been crossed.] Since this * request came in *after* the ticket expiration date, the ticket should be * rejected. And if we were doing all of our comparisons in UTC, this would * indeed be the case. However, since the DateTime struct doesn't have UTC * offset information embedded in it, comparisons of their dates are taken at * face value as simple wall time comparisons. Thus the current time is * interpreted just as "1:15 AM" and the expiration time is intepreted just as * "1:50 AM", and from this simple comparison the token is considered unexpired * and is accepted by the system. * * To see this incorrect behavior in action, run the following on a machine * in the Pacific Time Zone. Contrast the behavior of the DateTimeOffset type * (which is designed to handle UTC offsets correctly) with the DateTime type, in * which the FromFileTime method implicitly does a local time conversion. * * long ft1 = 129650430000000000; // Nov 6, 2011 1:50 AM PDT (UTC -0700) * long ft2 = 129650445000000000; // Nov 6, 2011 1:15 AM PST (UTC -0800) * DateTimeOffset.FromFileTime(ft1) < DateTimeOffset.FromFileTime(ft2) = true * DateTime.FromFileTime(ft1) < DateTime.FromFileTime(ft2) = false (INCORRECT!) * * To be absolutely safe, we must perform comparisons *only* on DateTime instances * we know to have correct UTC information, or we must use an offset-aware type * like DateTimeOffset which just does the right thing automatically. * * More info: http://msdn.microsoft.com/en-us/library/bb546099.aspx */ internal DateTime ExpirationUtc { get { return (_ExpirationUtcHasValue) ? _ExpirationUtc : Expiration.ToUniversalTime(); } } internal DateTime IssueDateUtc { get { return (_IssueDateUtcHasValue) ? _IssueDateUtc : IssueDate.ToUniversalTime(); } } private int _Version; private String _Name; private DateTime _Expiration; private DateTime _IssueDate; private bool _IsPersistent; private String _UserData; private String _CookiePath; #pragma warning disable 0169 // unused field // These fields were added in .NET 4 but weren't actually used anywhere. // We can't remove them since they're part of the serialization contract. [OptionalField(VersionAdded = 2)] private int _InternalVersion; [OptionalField(VersionAdded = 2)] private Byte[] _InternalData; #pragma warning restore 0169 // Issue and expiration times as UTC. // We can't use nullable types since they didn't exist in v1.1, and this assists backporting fixes downlevel. [NonSerialized] private bool _ExpirationUtcHasValue; [NonSerialized] private DateTime _ExpirationUtc; [NonSerialized] private bool _IssueDateUtcHasValue; [NonSerialized] private DateTime _IssueDateUtc; /// /// This constructor creates a /// FormsAuthenticationTicket instance with explicit values. /// public FormsAuthenticationTicket(int version, String name, DateTime issueDate, DateTime expiration, bool isPersistent, String userData) { _Version = version; _Name = name; _Expiration = expiration; _IssueDate = issueDate; _IsPersistent = isPersistent; _UserData = userData; _CookiePath = FormsAuthentication.FormsCookiePath; } public FormsAuthenticationTicket(int version, String name, DateTime issueDate, DateTime expiration, bool isPersistent, String userData, String cookiePath) { _Version = version; _Name = name; _Expiration = expiration; _IssueDate = issueDate; _IsPersistent = isPersistent; _UserData = userData; _CookiePath = cookiePath; } /// /// This constructor creates /// a FormsAuthenticationTicket instance with the specified name and cookie durability, /// and default values for the other settings. /// public FormsAuthenticationTicket(String name, bool isPersistent, Int32 timeout) { _Version = 2; _Name = name; _IssueDateUtcHasValue = true; _IssueDateUtc = DateTime.UtcNow; _IssueDate = DateTime.Now; _IsPersistent = isPersistent; _UserData = ""; _ExpirationUtcHasValue = true; _ExpirationUtc = _IssueDateUtc.AddMinutes(timeout); _Expiration = _IssueDate.AddMinutes(timeout); _CookiePath = FormsAuthentication.FormsCookiePath; } internal static FormsAuthenticationTicket FromUtc(int version, String name, DateTime issueDateUtc, DateTime expirationUtc, bool isPersistent, String userData, String cookiePath) { FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(version, name, issueDateUtc.ToLocalTime(), expirationUtc.ToLocalTime(), isPersistent, userData, cookiePath); ticket._IssueDateUtcHasValue = true; ticket._IssueDateUtc = issueDateUtc; ticket._ExpirationUtcHasValue = true; ticket._ExpirationUtc = expirationUtc; return ticket; } } }