1 //------------------------------------------------------------------------------
2 // <copyright file="FormsAuthentication.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 * FormsAuthentication class
10 * Copyright (c) 1999 Microsoft Corporation
13 namespace System
.Web
.Security
{
17 using System
.Web
.Configuration
;
18 using System
.Web
.Caching
;
19 using System
.Collections
;
20 using System
.Web
.Util
;
21 using System
.Security
.Cryptography
;
22 using System
.Security
.Principal
;
23 using System
.Threading
;
24 using System
.Globalization
;
25 using System
.Security
.Permissions
;
26 using System
.Web
.Management
;
27 using System
.Collections
.Specialized
;
28 using System
.Web
.Compilation
;
29 using System
.Web
.Security
.Cryptography
;
34 /// This class consists of static methods that
35 /// provides helper utilities for manipulating authentication tickets.
37 public sealed class FormsAuthentication
{
38 private const int MAX_TICKET_LENGTH
= 4096;
39 private static object _lockObject
= new object();
41 public FormsAuthentication() { }
43 /////////////////////////////////////////////////////////////////////////////
44 /////////////////////////////////////////////////////////////////////////////
45 /////////////////////////////////////////////////////////////////////////////
46 // Helper functions: Hash a password
49 /// Initializes FormsAuthentication by reading
50 /// configuration and getting the cookie values and encryption keys for the given
53 [Obsolete("The recommended alternative is to use the Membership APIs, such as Membership.CreateUser. For more information, see http://go.microsoft.com/fwlink/?LinkId=252463.")]
54 public static String
HashPasswordForStoringInConfigFile(String password
, String passwordFormat
) {
55 if (password
== null) {
56 throw new ArgumentNullException("password");
58 if (passwordFormat
== null) {
59 throw new ArgumentNullException("passwordFormat");
61 HashAlgorithm hashAlgorithm
;
62 if (StringUtil
.EqualsIgnoreCase(passwordFormat
, "sha1"))
63 hashAlgorithm
= CryptoAlgorithms
.CreateSHA1();
64 else if (StringUtil
.EqualsIgnoreCase(passwordFormat
, "md5"))
65 hashAlgorithm
= CryptoAlgorithms
.CreateMD5();
66 else if (StringUtil
.EqualsIgnoreCase(passwordFormat
, "sha256"))
67 hashAlgorithm
= CryptoAlgorithms
.CreateSHA256();
68 else if (StringUtil
.EqualsIgnoreCase(passwordFormat
, "sha384"))
69 hashAlgorithm
= CryptoAlgorithms
.CreateSHA384();
70 else if (StringUtil
.EqualsIgnoreCase(passwordFormat
, "sha512"))
71 hashAlgorithm
= CryptoAlgorithms
.CreateSHA512();
73 throw new ArgumentException(SR
.GetString(SR
.InvalidArgumentValue
, "passwordFormat"));
75 using (hashAlgorithm
) {
76 return CryptoUtil
.BinaryToHex(hashAlgorithm
.ComputeHash(Encoding
.UTF8
.GetBytes(password
)));
80 /////////////////////////////////////////////////////////////////////////////
81 /////////////////////////////////////////////////////////////////////////////
82 /////////////////////////////////////////////////////////////////////////////
86 /// Initializes FormsAuthentication by reading
87 /// configuration and getting the cookie values and encryption keys for the given
90 public static void Initialize() {
98 AuthenticationSection settings
= RuntimeConfig
.GetAppConfig().Authentication
;
99 settings
.ValidateAuthenticationMode();
100 _FormsName
= settings
.Forms
.Name
;
101 _RequireSSL
= settings
.Forms
.RequireSSL
;
102 _SlidingExpiration
= settings
.Forms
.SlidingExpiration
;
103 if (_FormsName
== null)
104 _FormsName
= CONFIG_DEFAULT_COOKIE
;
106 _Protection
= settings
.Forms
.Protection
;
107 _Timeout
= (int) settings
.Forms
.Timeout
.TotalMinutes
;
108 _FormsCookiePath
= settings
.Forms
.Path
;
109 _LoginUrl
= settings
.Forms
.LoginUrl
;
110 if (_LoginUrl
== null)
111 _LoginUrl
= "login.aspx";
112 _DefaultUrl
= settings
.Forms
.DefaultUrl
;
113 if (_DefaultUrl
== null)
114 _DefaultUrl
= "default.aspx";
115 _CookieMode
= settings
.Forms
.Cookieless
;
116 _CookieDomain
= settings
.Forms
.Domain
;
117 _EnableCrossAppRedirects
= settings
.Forms
.EnableCrossAppRedirects
;
118 _TicketCompatibilityMode
= settings
.Forms
.TicketCompatibilityMode
;
124 /////////////////////////////////////////////////////////////////////////////
125 /////////////////////////////////////////////////////////////////////////////
126 /////////////////////////////////////////////////////////////////////////////
127 // Decrypt and get the auth ticket
130 /// <para>Given an encrypted authenitcation ticket as
131 /// obtained from an HTTP cookie, this method returns an instance of a
132 /// FormsAuthenticationTicket class.</para>
134 public static FormsAuthenticationTicket
Decrypt(string encryptedTicket
) {
135 if (String
.IsNullOrEmpty(encryptedTicket
) || encryptedTicket
.Length
> MAX_TICKET_LENGTH
)
136 throw new ArgumentException(SR
.GetString(SR
.InvalidArgumentValue
, "encryptedTicket"));
140 if ((encryptedTicket
.Length
% 2) == 0) { // Could be a hex string
142 bBlob
= CryptoUtil
.HexToBinary(encryptedTicket
);
146 bBlob
= HttpServerUtility
.UrlTokenDecode(encryptedTicket
);
147 if (bBlob
== null || bBlob
.Length
< 1)
148 throw new ArgumentException(SR
.GetString(SR
.InvalidArgumentValue
, "encryptedTicket"));
152 if (AspNetCryptoServiceProvider
.Instance
.IsDefaultProvider
) {
153 // If new crypto routines are enabled, call them instead.
154 ICryptoService cryptoService
= AspNetCryptoServiceProvider
.Instance
.GetCryptoService(Purpose
.FormsAuthentication_Ticket
);
155 byte[] unprotectedData
= cryptoService
.Unprotect(bBlob
);
156 ticketLength
= unprotectedData
.Length
;
157 bBlob
= unprotectedData
;
159 #pragma warning disable 618 // calling obsolete methods
160 // Otherwise call into MachineKeySection routines.
162 if (_Protection
== FormsProtectionEnum
.All
|| _Protection
== FormsProtectionEnum
.Encryption
)
164 // DevDiv Bugs 137864: Include a random IV if under the right compat mode
165 // for improved encryption semantics
166 bBlob
= MachineKeySection
.EncryptOrDecryptData(false, bBlob
, null, 0, bBlob
.Length
, false, false, IVType
.Random
);
171 ticketLength
= bBlob
.Length
;
173 if (_Protection
== FormsProtectionEnum
.All
|| _Protection
== FormsProtectionEnum
.Validation
)
175 if (!MachineKeySection
.VerifyHashedData(bBlob
))
177 ticketLength
-= MachineKeySection
.HashSize
;
179 #pragma warning restore 618 // calling obsolete methods
182 //////////////////////////////////////////////////////////////////////
183 // Step 4: Change binary ticket to managed struct
186 // Framework20 / Framework40 ticket generation modes are insecure. We should use a
187 // secure serialization mode by default.
188 if (!AppSettings
.UseLegacyFormsAuthenticationTicketCompatibility
) {
189 return FormsAuthenticationTicketSerializer
.Deserialize(bBlob
, ticketLength
);
193 // If we have reached this point of execution, the developer has explicitly elected
194 // to continue using the insecure code path instead of the secure one. We removed
195 // the Framework40 serialization mode, so everybody using the legacy code path is
196 // forced to Framework20.
198 int iSize
= ((ticketLength
> MAX_TICKET_LENGTH
) ? MAX_TICKET_LENGTH
: ticketLength
);
199 StringBuilder name
= new StringBuilder(iSize
);
200 StringBuilder data
= new StringBuilder(iSize
);
201 StringBuilder path
= new StringBuilder(iSize
);
202 byte [] pBin
= new byte[4];
203 long [] pDates
= new long[2];
205 int iRet
= UnsafeNativeMethods
.CookieAuthParseTicket(bBlob
, ticketLength
,
214 DateTime dt1
= DateTime
.FromFileTime(pDates
[0]);
215 DateTime dt2
= DateTime
.FromFileTime(pDates
[1]);
217 FormsAuthenticationTicket ticket
= new FormsAuthenticationTicket((int) pBin
[0],
221 (bool) (pBin
[1] != 0),
228 /////////////////////////////////////////////////////////////////////////////
229 /////////////////////////////////////////////////////////////////////////////
230 /////////////////////////////////////////////////////////////////////////////
234 /// Given a FormsAuthenticationTicket, this
235 /// method produces a string containing an encrypted authentication ticket suitable
236 /// for use in an HTTP cookie.
238 public static String
Encrypt(FormsAuthenticationTicket ticket
) {
239 return Encrypt(ticket
, true);
241 internal static String
Encrypt(FormsAuthenticationTicket ticket
, bool hexEncodedTicket
) {
243 throw new ArgumentNullException("ticket");
246 //////////////////////////////////////////////////////////////////////
247 // Step 1a: Make it into a binary blob
248 byte[] bBlob
= MakeTicketIntoBinaryBlob(ticket
);
252 //////////////////////////////////////////////////////////////////////
253 // Step 1b: If new crypto routines are enabled, call them instead.
254 if (AspNetCryptoServiceProvider
.Instance
.IsDefaultProvider
) {
255 ICryptoService cryptoService
= AspNetCryptoServiceProvider
.Instance
.GetCryptoService(Purpose
.FormsAuthentication_Ticket
);
256 byte[] protectedData
= cryptoService
.Protect(bBlob
);
257 bBlob
= protectedData
;
260 #pragma warning disable 618 // calling obsolete methods
263 //////////////////////////////////////////////////////////////////////
264 // Step 2: Get the MAC and add to the blob
265 if (_Protection
== FormsProtectionEnum
.All
|| _Protection
== FormsProtectionEnum
.Validation
) {
266 byte[] bMac
= MachineKeySection
.HashData(bBlob
, null, 0, bBlob
.Length
);
269 byte[] bAll
= new byte[bMac
.Length
+ bBlob
.Length
];
270 Buffer
.BlockCopy(bBlob
, 0, bAll
, 0, bBlob
.Length
);
271 Buffer
.BlockCopy(bMac
, 0, bAll
, bBlob
.Length
, bMac
.Length
);
275 if (_Protection
== FormsProtectionEnum
.All
|| _Protection
== FormsProtectionEnum
.Encryption
) {
276 //////////////////////////////////////////////////////////////////////
277 // Step 3: Do the actual encryption
278 // DevDiv Bugs 137864: Include a random IV if under the right compat mode
279 // for improved encryption semantics
280 bBlob
= MachineKeySection
.EncryptOrDecryptData(true, bBlob
, null, 0, bBlob
.Length
, false, false, IVType
.Random
);
282 #pragma warning restore 618 // calling obsolete methods
285 if (!hexEncodedTicket
)
286 return HttpServerUtility
.UrlTokenEncode(bBlob
);
288 return CryptoUtil
.BinaryToHex(bBlob
);
291 /////////////////////////////////////////////////////////////////////////////
292 /////////////////////////////////////////////////////////////////////////////
293 /////////////////////////////////////////////////////////////////////////////
294 // Verify User name and Password
297 /// Given the supplied credentials, this method
298 /// attempts to validate the credentials against those contained in the configured
299 /// credential store.
301 [Obsolete("The recommended alternative is to use the Membership APIs, such as Membership.ValidateUser. For more information, see http://go.microsoft.com/fwlink/?LinkId=252463.")]
302 public static bool Authenticate(String name
, String password
) {
303 bool retVal
= InternalAuthenticate(name
, password
);
306 PerfCounters
.IncrementCounter(AppPerfCounter
.FORMS_AUTH_SUCCESS
);
307 WebBaseEvent
.RaiseSystemEvent(null, WebEventCodes
.AuditFormsAuthenticationSuccess
, name
);
310 PerfCounters
.IncrementCounter(AppPerfCounter
.FORMS_AUTH_FAIL
);
311 WebBaseEvent
.RaiseSystemEvent(null, WebEventCodes
.AuditFormsAuthenticationFailure
, name
);
317 private static bool InternalAuthenticate(String name
, String password
) {
318 //////////////////////////////////////////////////////////////////////
319 // Step 1: Make sure we are initialized
320 if (name
== null || password
== null)
324 //////////////////////////////////////////////////////////////////////
325 // Step 2: Get the user database
326 AuthenticationSection settings
= RuntimeConfig
.GetAppConfig().Authentication
;
327 settings
.ValidateAuthenticationMode();
328 FormsAuthenticationUserCollection Users
= settings
.Forms
.Credentials
.Users
;
330 // Hashtable hTable = settings.Credentials;
336 //////////////////////////////////////////////////////////////////////
337 // Step 3: Get the (hashed) password for this user
338 FormsAuthenticationUser u
= Users
[name
.ToLower(CultureInfo
.InvariantCulture
)];
342 String pass
= (String
)u
.Password
;
348 //////////////////////////////////////////////////////////////////////
349 // Step 4: Hash the given password
352 #pragma warning disable 618 // HashPasswordForStorignInConfigFile is now obsolete
353 switch (settings
.Forms
.Credentials
.PasswordFormat
)
355 case FormsAuthPasswordFormat
.SHA256
:
356 encPassword
= HashPasswordForStoringInConfigFile(password
, "sha256");
358 case FormsAuthPasswordFormat
.SHA384
:
359 encPassword
= HashPasswordForStoringInConfigFile(password
, "sha384");
361 case FormsAuthPasswordFormat
.SHA512
:
362 encPassword
= HashPasswordForStoringInConfigFile(password
, "sha512");
364 case FormsAuthPasswordFormat
.SHA1
:
365 encPassword
= HashPasswordForStoringInConfigFile(password
, "sha1");
368 case FormsAuthPasswordFormat
.MD5
:
369 encPassword
= HashPasswordForStoringInConfigFile(password
, "md5");
372 case FormsAuthPasswordFormat
.Clear
:
373 encPassword
= password
;
379 #pragma warning restore 618
381 //////////////////////////////////////////////////////////////////////
382 // Step 5: Compare the hashes
383 return(String
.Compare(encPassword
,
385 ((settings
.Forms
.Credentials
.PasswordFormat
!= FormsAuthPasswordFormat
.Clear
)
386 ? StringComparison
.OrdinalIgnoreCase
: StringComparison
.Ordinal
))
390 /////////////////////////////////////////////////////////////////////////////
391 /////////////////////////////////////////////////////////////////////////////
392 /////////////////////////////////////////////////////////////////////////////
395 /// Given an authenticated user, calling SignOut
396 /// removes the authentication ticket by doing a SetForms with an empty value. This
397 /// removes either durable or session cookies.
399 public static void SignOut() {
402 HttpContext context
= HttpContext
.Current
;
403 bool needToRedirect
= context
.CookielessHelper
.DoesCookieValueExistInOriginal('F');
405 context
.CookielessHelper
.SetCookieValue('F', null); // Always clear the uri-cookie
407 if (!CookielessHelperClass
.UseCookieless(context
, false, CookieMode
) || context
.Request
.Browser
.Cookies
)
408 { // clear cookie if required
409 string cookieValue
= String
.Empty
;
410 if (context
.Request
.Browser
["supportsEmptyStringInCookieValue"] == "false")
411 cookieValue
= "NoCookie";
412 HttpCookie cookie
= new HttpCookie(FormsCookieName
, cookieValue
);
413 cookie
.HttpOnly
= true;
414 cookie
.Path
= _FormsCookiePath
;
415 cookie
.Expires
= new System
.DateTime(1999, 10, 12);
416 cookie
.Secure
= _RequireSSL
;
417 if (_CookieDomain
!= null)
418 cookie
.Domain
= _CookieDomain
;
419 context
.Response
.Cookies
.RemoveCookie(FormsCookieName
);
420 context
.Response
.Cookies
.Add(cookie
);
423 context
.Response
.Redirect(GetLoginPage(null), false);
425 /////////////////////////////////////////////////////////////////////////////
426 /////////////////////////////////////////////////////////////////////////////
427 /////////////////////////////////////////////////////////////////////////////
430 /// This method creates an authentication ticket
431 /// for the given userName and attaches it to the cookies collection of the outgoing
432 /// response. It does not perform a redirect.
434 public static void SetAuthCookie(String userName
, bool createPersistentCookie
) {
436 SetAuthCookie(userName
, createPersistentCookie
, FormsAuthentication
.FormsCookiePath
);
440 /// This method creates an authentication ticket
441 /// for the given userName and attaches it to the cookies collection of the outgoing
442 /// response. It does not perform a redirect.
444 public static void SetAuthCookie(String userName
, bool createPersistentCookie
, String strCookiePath
) {
446 HttpContext context
= HttpContext
.Current
;
448 if (!context
.Request
.IsSecureConnection
&& RequireSSL
)
449 throw new HttpException(SR
.GetString(SR
.Connection_not_secure_creating_secure_cookie
));
450 bool cookieless
= CookielessHelperClass
.UseCookieless(context
, false, CookieMode
);
451 HttpCookie cookie
= GetAuthCookie(userName
, createPersistentCookie
, cookieless
? "/" : strCookiePath
, !cookieless
);
454 HttpContext
.Current
.Response
.Cookies
.Add(cookie
);
455 context
.CookielessHelper
.SetCookieValue('F', null);
458 context
.CookielessHelper
.SetCookieValue('F', cookie
.Value
);
462 /////////////////////////////////////////////////////////////////////////////
463 /////////////////////////////////////////////////////////////////////////////
464 /////////////////////////////////////////////////////////////////////////////
467 /// Creates an authentication cookie for a given
468 /// user name. This does not set the cookie as part of the outgoing response, so
469 /// that an application can have more control over how the cookie is issued.
471 public static HttpCookie
GetAuthCookie(String userName
, bool createPersistentCookie
) {
473 return GetAuthCookie(userName
, createPersistentCookie
, FormsAuthentication
.FormsCookiePath
);
476 public static HttpCookie
GetAuthCookie(String userName
, bool createPersistentCookie
, String strCookiePath
) {
477 return GetAuthCookie(userName
, createPersistentCookie
, strCookiePath
, true);
479 private static HttpCookie
GetAuthCookie(String userName
, bool createPersistentCookie
, String strCookiePath
, bool hexEncodedTicket
) {
481 if (userName
== null)
482 userName
= String
.Empty
;
484 if (strCookiePath
== null || strCookiePath
.Length
< 1)
485 strCookiePath
= FormsCookiePath
;
487 DateTime issueDateUtc
= DateTime
.UtcNow
;
488 DateTime expirationUtc
= issueDateUtc
.AddMinutes(_Timeout
);
490 FormsAuthenticationTicket ticket
= FormsAuthenticationTicket
.FromUtc(
492 userName
, // User-Name
493 issueDateUtc
, // Issue-Date
494 expirationUtc
, // Expiration
495 createPersistentCookie
, // IsPersistent
496 String
.Empty
, // User-Data
497 strCookiePath
// Cookie Path
500 String strTicket
= Encrypt(ticket
, hexEncodedTicket
);
501 if (strTicket
== null || strTicket
.Length
< 1)
502 throw new HttpException(
503 SR
.GetString(SR
.Unable_to_encrypt_cookie_ticket
));
506 HttpCookie cookie
= new HttpCookie(FormsCookieName
, strTicket
);
508 cookie
.HttpOnly
= true;
509 cookie
.Path
= strCookiePath
;
510 cookie
.Secure
= _RequireSSL
;
511 if (_CookieDomain
!= null)
512 cookie
.Domain
= _CookieDomain
;
513 if (ticket
.IsPersistent
)
514 cookie
.Expires
= ticket
.Expiration
;
518 /////////////////////////////////////////////////////////////////////////////
519 /////////////////////////////////////////////////////////////////////////////
520 /////////////////////////////////////////////////////////////////////////////
522 internal static String
GetReturnUrl(bool useDefaultIfAbsent
)
526 HttpContext context
= HttpContext
.Current
;
527 String returnUrl
= context
.Request
.QueryString
[ReturnUrlVar
];
529 // If it is not in the QueryString, look in the Posted-body
530 if (returnUrl
== null) {
531 returnUrl
= context
.Request
.Form
[ReturnUrlVar
];
532 if (!string.IsNullOrEmpty(returnUrl
) && !returnUrl
.Contains("/") && returnUrl
.Contains("%"))
533 returnUrl
= HttpUtility
.UrlDecode(returnUrl
);
536 // Make sure it is on the current server if EnableCrossAppRedirects is false
537 if (!string.IsNullOrEmpty(returnUrl
) && !EnableCrossAppRedirects
) {
538 if (!UrlPath
.IsPathOnSameServer(returnUrl
, context
.Request
.Url
))
542 // Make sure it is not dangerous, i.e. does not contain script, etc.
543 if (!string.IsNullOrEmpty(returnUrl
) && CrossSiteScriptingValidation
.IsDangerousUrl(returnUrl
))
544 throw new HttpException(SR
.GetString(SR
.Invalid_redirect_return_url
));
546 return ((returnUrl
== null && useDefaultIfAbsent
) ? DefaultUrl
: returnUrl
);
550 /// Returns the redirect URL for the original
551 /// request that caused the redirect to the login page.
553 public static String
GetRedirectUrl(String userName
, bool createPersistentCookie
)
555 if (userName
== null)
557 return GetReturnUrl(true);
559 /////////////////////////////////////////////////////////////////////////////
560 /////////////////////////////////////////////////////////////////////////////
561 /////////////////////////////////////////////////////////////////////////////
562 // Redirect from logon page to orignal page
565 /// This method redirects an authenticated user
566 /// back to the original URL that they requested.
568 public static void RedirectFromLoginPage(String userName
, bool createPersistentCookie
) {
570 RedirectFromLoginPage(userName
, createPersistentCookie
, FormsAuthentication
.FormsCookiePath
);
573 public static void RedirectFromLoginPage(String userName
, bool createPersistentCookie
, String strCookiePath
) {
575 if (userName
== null)
578 HttpContext context
= HttpContext
.Current
;
579 string strUrl
= GetReturnUrl(true);
580 if ( CookiesSupported
|| // Cookies-supported: Most common scenario
581 IsPathWithinAppRoot(context
, strUrl
)) { // Cookies not suported, so add it to the current app URL
583 SetAuthCookie(userName
, createPersistentCookie
, strCookiePath
);
584 strUrl
= RemoveQueryStringVariableFromUrl(strUrl
, FormsCookieName
); // Make sure there is no other ticket in the Query String.
585 if (!CookiesSupported
) {// Make sure the URL is relative, if we are using cookieless.
586 int pos
= strUrl
.IndexOf("://", StringComparison
.Ordinal
);
588 pos
= strUrl
.IndexOf('/', pos
+ 3);
590 strUrl
= strUrl
.Substring(pos
);
593 } else if (EnableCrossAppRedirects
) { // Cookieless scenario -- add it to the QueryString if allowed to
595 HttpCookie cookie
= GetAuthCookie(userName
, createPersistentCookie
, strCookiePath
);
596 strUrl
= RemoveQueryStringVariableFromUrl(strUrl
, cookie
.Name
); // Make sure there is no other ticket in the Query String.
597 if (strUrl
.IndexOf('?') > 0) {
598 strUrl
+= "&" + cookie
.Name
+ "=" + cookie
.Value
;
601 strUrl
+= "?" + cookie
.Name
+ "=" + cookie
.Value
;
606 throw new HttpException(SR
.GetString(SR
.Can_not_issue_cookie_or_redirect
));
609 context
.Response
.Redirect(strUrl
, false);
612 public static FormsAuthenticationTicket
RenewTicketIfOld(FormsAuthenticationTicket tOld
) {
616 DateTime utcNow
= DateTime
.UtcNow
;
617 TimeSpan ticketAge
= utcNow
- tOld
.IssueDateUtc
;
618 TimeSpan ticketRemainingLifetime
= tOld
.ExpirationUtc
- utcNow
;
620 if (ticketRemainingLifetime
> ticketAge
)
621 return tOld
; // no need to renew
623 // The original ticket may have had a custom-specified lifetime separate from
624 // the default timeout specified in config. We should honor that original
625 // lifetime when renewing the ticket.
626 TimeSpan originalTicketTotalLifetime
= tOld
.ExpirationUtc
- tOld
.IssueDateUtc
;
627 DateTime newExpirationUtc
= utcNow
+ originalTicketTotalLifetime
;
629 FormsAuthenticationTicket ticket
= FormsAuthenticationTicket
.FromUtc(
630 tOld
.Version
/* version */,
631 tOld
.Name
/* name */,
632 utcNow
/* issueDateUtc */,
633 newExpirationUtc
/* expirationUtc */,
634 tOld
.IsPersistent
/* isPersistent */,
635 tOld
.UserData
/* userData */,
636 tOld
.CookiePath
/* cookiePath */);
641 public static void EnableFormsAuthentication(NameValueCollection configurationData
) {
642 BuildManager
.ThrowIfPreAppStartNotRunning();
643 configurationData
= configurationData
?? new NameValueCollection();
644 AuthenticationConfig
.Mode
= AuthenticationMode
.Forms
;
646 // Last caller overwrites only the values that are present in the dictionary.
647 string defaultUrl
= configurationData
["defaultUrl"];
648 if (!String
.IsNullOrEmpty(defaultUrl
)) {
649 _DefaultUrl
= defaultUrl
;
651 string loginUrl
= configurationData
["loginUrl"];
652 if (!String
.IsNullOrEmpty(loginUrl
)) {
653 _LoginUrl
= loginUrl
;
657 public static bool IsEnabled
{
659 return AuthenticationConfig
.Mode
== AuthenticationMode
.Forms
;
663 public static String FormsCookieName { get { Initialize(); return _FormsName; }}
665 public static String FormsCookiePath { get { Initialize(); return _FormsCookiePath; }}
667 public static bool RequireSSL { get { Initialize(); return _RequireSSL; }}
669 public static TimeSpan Timeout { get { Initialize(); return new TimeSpan(0, _Timeout, 0); }
}
671 public static bool SlidingExpiration { get { Initialize(); return _SlidingExpiration; }}
673 public static HttpCookieMode CookieMode { get { Initialize(); return _CookieMode; }}
675 public static string CookieDomain { get { Initialize ();return _CookieDomain; }
}
677 public static bool EnableCrossAppRedirects { get { Initialize(); return _EnableCrossAppRedirects; }
}
679 public static TicketCompatibilityMode TicketCompatibilityMode { get { Initialize(); return _TicketCompatibilityMode; }
}
681 public static bool CookiesSupported
{
683 HttpContext context
= HttpContext
.Current
;
684 if (context
!= null) {
685 return !(CookielessHelperClass
.UseCookieless(context
, false, CookieMode
));
691 public static string LoginUrl
{
694 HttpContext context
= HttpContext
.Current
;
695 if (context
!= null) {
696 return AuthenticationConfig
.GetCompleteLoginUrl(context
, _LoginUrl
);
698 if (_LoginUrl
.Length
== 0 || (_LoginUrl
[0] != '/' && _LoginUrl
.IndexOf("//", StringComparison
.Ordinal
) < 0))
699 return ("/" + _LoginUrl
);
704 public static string DefaultUrl
{
707 HttpContext context
= HttpContext
.Current
;
708 if (context
!= null) {
709 return AuthenticationConfig
.GetCompleteLoginUrl(context
, _DefaultUrl
);
711 if (_DefaultUrl
.Length
== 0 || (_DefaultUrl
[0] != '/' && _DefaultUrl
.IndexOf("//", StringComparison
.Ordinal
) < 0))
712 return ("/" + _DefaultUrl
);
717 internal static string ReturnUrlVar
{
719 if (!String
.IsNullOrEmpty(AppSettings
.FormsAuthReturnUrlVar
)) {
720 return AppSettings
.FormsAuthReturnUrlVar
;
727 internal static string GetLoginPage(string extraQueryString
) {
728 return GetLoginPage(extraQueryString
, false);
730 internal static string GetLoginPage(string extraQueryString
, bool reuseReturnUrl
) {
731 HttpContext context
= HttpContext
.Current
;
732 string loginUrl
= FormsAuthentication
.LoginUrl
;
733 if (loginUrl
.IndexOf('?') >= 0)
734 loginUrl
= RemoveQueryStringVariableFromUrl(loginUrl
, ReturnUrlVar
);
735 int pos
= loginUrl
.IndexOf('?');
739 if (pos
< loginUrl
.Length
-1)
741 string returnUrl
= null;
742 if (reuseReturnUrl
) {
743 returnUrl
= HttpUtility
.UrlEncode( GetReturnUrl(false),
744 context
.Request
.QueryStringEncoding
);
746 if (returnUrl
== null)
747 returnUrl
= HttpUtility
.UrlEncode(context
.Request
.RawUrl
, context
.Request
.ContentEncoding
);
749 loginUrl
+= ReturnUrlVar
+ "=" + returnUrl
;
750 if (!String
.IsNullOrEmpty(extraQueryString
)) {
751 loginUrl
+= "&" + extraQueryString
;
757 public static void RedirectToLoginPage() {
758 RedirectToLoginPage(null);
762 public static void RedirectToLoginPage(string extraQueryString
) {
763 HttpContext context
= HttpContext
.Current
;
764 string loginUrl
= GetLoginPage(extraQueryString
);
765 context
.Response
.Redirect(loginUrl
, false);
768 /////////////////////////////////////////////////////////////////////////////
769 /////////////////////////////////////////////////////////////////////////////
770 /////////////////////////////////////////////////////////////////////////////
773 /////////////////////////////////////////////////////////////////////////////
775 private const String CONFIG_DEFAULT_COOKIE
= ".ASPXAUTH";
777 /////////////////////////////////////////////////////////////////////////////
779 private static bool _Initialized
;
780 private static String _FormsName
;
781 //private static FormsProtectionEnum _Protection;
782 private static FormsProtectionEnum _Protection
;
783 private static Int32 _Timeout
;
784 private static String _FormsCookiePath
;
785 private static bool _RequireSSL
;
786 private static bool _SlidingExpiration
;
787 private static string _LoginUrl
;
788 private static string _DefaultUrl
;
789 private static HttpCookieMode _CookieMode
;
790 private static string _CookieDomain
= null;
791 private static bool _EnableCrossAppRedirects
;
792 private static TicketCompatibilityMode _TicketCompatibilityMode
;
794 /////////////////////////////////////////////////////////////////////////////
795 private static byte[] MakeTicketIntoBinaryBlob(FormsAuthenticationTicket ticket
) {
796 // None of the modes (Framework20 / Framework40 / beyond) support null values for these fields;
797 // they always eventually just returned a null value.
798 if (ticket
.Name
== null || ticket
.UserData
== null || ticket
.CookiePath
== null) {
803 // Framework20 / Framework40 ticket generation modes are insecure. We should use a
804 // secure serialization mode by default.
805 if (!AppSettings
.UseLegacyFormsAuthenticationTicketCompatibility
) {
806 return FormsAuthenticationTicketSerializer
.Serialize(ticket
);
810 // If we have reached this point of execution, the developer has explicitly elected
811 // to continue using the insecure code path instead of the secure one. We removed
812 // the Framework40 serialization mode, so everybody using the legacy code path is
813 // forced to Framework20.
815 byte [] bData
= new byte[4096];
816 byte [] pBin
= new byte[4];
817 long [] pDates
= new long[2];
818 byte [] pNull
= { 0, 0, 0 }
;
820 // DevDiv Bugs 137864: 8 bytes may not be enough random bits as the length should be equal to the
821 // key size. In CompatMode > Framework20SP1, use the IVType.Random feature instead of these 8 bytes,
822 // but still include empty 8 bytes for compat with webengine.dll, where CookieAuthConstructTicket is.
823 // Note that even in CompatMode = Framework20SP2 we fill 8 bytes with random data if the ticket
824 // is not going to be encrypted.
826 bool willEncrypt
= (_Protection
== FormsProtectionEnum
.All
|| _Protection
== FormsProtectionEnum
.Encryption
);
827 bool legacyPadding
= !willEncrypt
|| (MachineKeySection
.CompatMode
== MachineKeyCompatibilityMode
.Framework20SP1
);
829 // Fill the first 8 bytes of the blob with random bits
830 byte[] bRandom
= new byte[8];
831 RNGCryptoServiceProvider randgen
= new RNGCryptoServiceProvider();
832 randgen
.GetBytes(bRandom
);
833 Buffer
.BlockCopy(bRandom
, 0, bData
, 0, 8);
836 // use blank 8 bytes for compatibility with CookieAuthConstructTicket (do nothing)
839 pBin
[0] = (byte) ticket
.Version
;
840 pBin
[1] = (byte) (ticket
.IsPersistent
? 1 : 0);
842 pDates
[0] = ticket
.IssueDate
.ToFileTime();
843 pDates
[1] = ticket
.Expiration
.ToFileTime();
845 int iRet
= UnsafeNativeMethods
.CookieAuthConstructTicket(
847 ticket
.Name
, ticket
.UserData
, ticket
.CookiePath
,
853 byte[] ciphertext
= new byte[iRet
];
854 Buffer
.BlockCopy(bData
, 0, ciphertext
, 0, iRet
);
858 /////////////////////////////////////////////////////////////////////////////
859 /////////////////////////////////////////////////////////////////////////////
861 internal static string RemoveQueryStringVariableFromUrl(string strUrl
, string QSVar
) {
862 int posQ
= strUrl
.IndexOf('?');
866 // Remove non-encoded QSVars
868 string question
= @"?";
870 string token
= amp
+ QSVar
+ "=";
871 RemoveQSVar(ref strUrl
, posQ
, token
, amp
, amp
.Length
);
873 token
= question
+ QSVar
+ "=";
874 RemoveQSVar(ref strUrl
, posQ
, token
, amp
, question
.Length
);
876 // Remove Url-enocoded strings
877 amp
= HttpUtility
.UrlEncode(@"&");
878 question
= HttpUtility
.UrlEncode(@"?");
880 token
= amp
+ HttpUtility
.UrlEncode(QSVar
+ "=");
881 RemoveQSVar(ref strUrl
, posQ
, token
, amp
, amp
.Length
);
883 token
= question
+ HttpUtility
.UrlEncode(QSVar
+ "=");
884 RemoveQSVar(ref strUrl
, posQ
, token
, amp
, question
.Length
);
888 /////////////////////////////////////////////////////////////////////////////
889 /////////////////////////////////////////////////////////////////////////////
890 static private void RemoveQSVar(ref string strUrl
, int posQ
, string token
, string sep
, int lenAtStartToLeave
)
892 for (int pos
= strUrl
.LastIndexOf(token
, StringComparison
.Ordinal
); pos
>= posQ
; pos
= strUrl
.LastIndexOf(token
, StringComparison
.Ordinal
))
894 int end
= strUrl
.IndexOf(sep
, pos
+ token
.Length
, StringComparison
.Ordinal
) + sep
.Length
;
895 if (end
< sep
.Length
|| end
>= strUrl
.Length
)
896 { // ending separator not found or nothing is at the end
897 strUrl
= strUrl
.Substring(0, pos
);
901 strUrl
= strUrl
.Substring(0, pos
+ lenAtStartToLeave
) + strUrl
.Substring(end
);
905 static private bool IsPathWithinAppRoot(HttpContext context
, string path
)
908 if (!Uri
.TryCreate(path
, UriKind
.Absolute
, out absUri
))
909 return HttpRuntime
.IsPathWithinAppRoot(path
);
911 if (!absUri
.IsLoopback
&& !string.Equals(context
.Request
.Url
.Host
, absUri
.Host
, StringComparison
.OrdinalIgnoreCase
))
912 return false; // different servers
914 return HttpRuntime
.IsPathWithinAppRoot(absUri
.AbsolutePath
);