web analytics

Understanding ASP.NET Authentication

Options
@2017-03-23 14:13:29

Web Farm Scenarios

In a Web farm, you cannot guarantee which server will handle successive requests. If a user is authenticated on one server and the next request goes to another server, the authentication ticket will fail the validation and require the user to re-authenticate.

The validationKey and decryptionKey attributes in the machineKey element are used for hashing and encryption of the forms authentication ticket. The default value for these attributes is AutoGenerate.IsolateApps. The keys are auto-generated for each application, and they are different on each server. Therefore, authentication tickets that are encrypted on one computer cannot be decrypted and verified on another computer in a Web farm, or in another application on the same Web server.

To address this issue, the validationKey and decryptionKey values must be identical on all computers in the Web farm.

Machine Key Explained

The default settings for the <pages> and <machineKey> elements are defined in the machine-level web.config.comments file. The relevant default settings are shown here for reference.

<pages enableViewStateMac="true" viewStateEncryptionMode="Auto" ... />

<machineKey validationKey="AutoGenerate,IsolateApps"  
            decryptionKey="AutoGenerate,IsolateApps" 
            validation="SHA1" decryption="Auto" />

When you configure ViewState, the <pages> element is used in conjunction with the <machineKey> element.

The <machineKey> attributes are as follows:

  • validationKey. This specifies the key that the HMAC algorithm uses to make ViewState tamper proof. The ViewState MAC is checked at the server when either the enableViewStateMAC attribute of the <pages> element or the EnableViewStateMac attribute of the @Page directive is set to true.
    <pages enableViewStateMAC="true" ... /> 
    or
    <%@Page EnableViewStateMac="true" ... %>
    

    Forms authentication also uses this key for signing the authentication ticket. Role manager and anonymous identification if enabled also uses this key for signing their cookies. If you use anonymous identification in cookieless mode, the data on the URL is also signed with this value,

  • decryptionKey. This specifies the key used to encrypt and decrypt data. Forms authentication, role manager and anonymous identification features use this key to encrypt and decrypt the authentication ticket, roles cookie and anonymous identification cookie. ASP.NET uses the key to encrypt and decrypt ViewState, but only if the validation attribute is set to AES or 3DES.
  • decryption. This specifies the symmetric encryption algorithm used to encrypt and decrypt forms authentication tickets.
  • validation. This specifies the hashing algorithm used to generate HMACs to make ViewState and forms authentication tickets tamper proof. This attribute is also used to specify the encryption algorithm used for ViewState encryption. This attribute supports the following options:
    • SHA1–SHA1 is used to tamper proof ViewState and, if configured, the forms authentication ticket. When SHA1 is selected for the validation attribute, the algorithm used is HMACSHA1.
    • MD5–MD5 is used to tamper proof ViewState and, if configured, the forms authentication ticket.
    • AES–AES is used to encrypt ViewState with the key specified in the decryptionKey attribute.
    • 3DES–3DES is used to encrypt ViewState with the key specified in the decryptionKey attribute. This is the only way to encrypt ViewState in ASP.NET 1.1. Both the forms authentication ticket and the ViewState are tamper-proofed using SHA-1 and the key specified in the validationKey attribute. Because the validation attribute is overloaded in ASP.NET 1.1, ASP.NET 2.0 introduces a new decryption attribute.

 

In general, you should choose SHA1 over MD5 for tamper-proofing because this produces a larger hash than MD5 and is considered cryptographically stronger.

Forms authentication defaults to SHA1 for tamper proofing (if <forms protection="validation" or "All"). When <forms protection="All"> or <forms protection = "Encryption">, then forms authentication hashes the forms authentication ticket by using either MD5 or HMACSHA1 (HMACSHA1 is used even if validation is set to AES or 3DES). Forms authentication then encrypts the ticket using the algorithm specified in the decryption attribute.

@2018-01-12 11:21:40

When a client browser makes a Web request, this initiates a thread in IIS, and objects relating to the request, such as the token contained in the IIdentity object, which is contained in the IPrincipal object, are attached to the thread. Programmatically, the IIdentity and IPrincipal objects are accessed through the HttpContext.User property, and both the objects and property are set by authentication modules that are part of the .NET pipeline, as shown in the following figure.

 

@2019-11-19 11:55:06

This example shows all of the attribute settings that are available for an instance of ActiveDirectoryMembershipProvider.

<configuration> 
  <connectionStrings> 
    <add name="ADService" connectionString="LDAP://ldapServer/" /> 
  </connectionStrings> 
  <system.web> 
    <membership 
      defaultProvider="AspNetActiveDirectoryMembershipProvider"> 
      <providers> 
        <add name="AspNetActiveDirectoryMembershipProvider" 
          type="System.Web.Security.ActiveDirectoryMembershipProvider, 
          System.Web, Version=1.0.3600, Culture=neutral, 
          PublicKeyToken=b03f5f7f11d50a3a" 
          connectionStringName="ADService" 
          connectionUsername="UserWithAppropriateRights" 
          connectionPassword="PasswordForUser" 
          connectionProtection="Secure" 
          enablePasswordReset="true" 
          enableSearchMethods="true" 
          requiresQuestionAndAnswer="true" 
          applicationName="/" 
          description="Default AD connection" 
          requiresUniqueEmail="false" 
          clientSearchTimeout="30" 
          serverSearchTimeout="30" 
          timeoutUnit="Minutes" 
          attributeMapPasswordQuestion="department" 
          attributeMapPasswordAnswer="division" 
          attributeMapFailedPasswordAnswerCount="singleIntAttribute" 
         attributeMapFailedPasswordAnswerTime="singleLargeIntAttribute" 
         attributeMapFailedPasswordAnswerLockoutTime="singleLargeIntAttribute" 
          attributeMapEmail = "mail" 
          attributeMapUsername = "userPrincipalName" 
          maxInvalidPasswordAttempts = "5" 
          passwordAttemptWindow = "10" 
          passwordAnswerAttemptLockoutDuration = "30" 
          minRequiredPasswordLength="7" 
          minRequiredNonalphanumericCharacters="1" 
          passwordStrengthRegularExpression=" 
          @\"(?=.{6,})(?=(.*\d){1,})(?=(.*\W){1,})" /> 
        /> 
      </providers> 
    </membership> 
  </system.web> 
</configuration> 

clientSearchTimeout and serverSearchTimeout default to minutes. To change the units, set the timeoutUnit attribute value to one of "Days", "Hours", "Minutes", "Seconds", or "Milliseconds". If the attribute is not specified, the default is "Minutes".

@2021-05-08 15:46:29

The RolePrincipal object implements the IPrincipal interface and represents the current security context for the HTTP request.

When role management is enabled (see Roles), the RoleManagerModule assigns a RolePrincipal object to the User property of the CurrentHttpContext.

The RolePrincipal class exposes the security identity for the current HTTP request and additionally performs checks for role membership. If CacheRolesInCookie is true, then the RolePrincipal object manages the cached list of roles and looks up role membership for the current user in the cached list first, then the role Provider. If CacheRolesInCookie is false, the RolePrincipal object always looks up role membership using the role provider.

The RolePrincipal object encrypts and decrypts role information cached in the cookie identified by the CookieName based on the CookieProtectionValue.

 

//------------------------------------------------------------------------------
// <copyright file="RolePrincipal.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

/*
* RolePrincipal
*
* Copyright (c) 2002 Microsoft Corporation
*/

namespace System.Web.Security {
    using System.Collections.Specialized;
    using System.Configuration;
    using System.Configuration.Provider;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Security;
    using System.Security.Claims;
    using System.Security.Permissions;
    using System.Security.Principal;
    using System.Text;
    using System.Web;
    using System.Web.Hosting;
    using System.Web.Security.Cryptography;
    using System.Web.Util;

    [Serializable]
    public class RolePrincipal : ClaimsPrincipal, ISerializable   
    {
        [NonSerialized]
        static Type s_type;

        public RolePrincipal(IIdentity identity, string encryptedTicket)
        {
            if (identity == null)
                throw new ArgumentNullException( "identity" );

            if (encryptedTicket == null)
                throw new ArgumentNullException( "encryptedTicket" );
            _Identity = identity;
            _ProviderName = Roles.Provider.Name;
            if (identity.IsAuthenticated)
                InitFromEncryptedTicket(encryptedTicket);
            else
                Init();
        }

        public RolePrincipal(IIdentity identity)
        {
            if (identity == null)
                throw new ArgumentNullException( "identity" );
            _Identity = identity;
            Init();
        }

        public RolePrincipal(string providerName, IIdentity identity )
        {
            if (identity == null)
                throw new ArgumentNullException( "identity" );

            if( providerName == null)
                throw new ArgumentException( SR.GetString(SR.Role_provider_name_invalid) , "providerName" );

            _ProviderName = providerName;
            if (Roles.Providers[providerName] == null)
                throw new ArgumentException(SR.GetString(SR.Role_provider_name_invalid), "providerName");

            _Identity = identity;
            Init();
        }

        public RolePrincipal(string providerName, IIdentity identity, string encryptedTicket )
        {
            if (identity == null)
                throw new ArgumentNullException( "identity" );

            if (encryptedTicket == null)
                throw new ArgumentNullException( "encryptedTicket" );

            if( providerName == null)
                throw new ArgumentException( SR.GetString(SR.Role_provider_name_invalid) , "providerName" );

            _ProviderName = providerName;
            if (Roles.Providers[_ProviderName] == null)
                throw new ArgumentException(SR.GetString(SR.Role_provider_name_invalid), "providerName");
            _Identity = identity;
            if (identity.IsAuthenticated)
                InitFromEncryptedTicket(encryptedTicket);
            else
                Init();
        }

        private void InitFromEncryptedTicket( string encryptedTicket )
        {
            if (HostingEnvironment.IsHosted && EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc) && HttpContext.Current != null)
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_ROLE_BEGIN, HttpContext.Current.WorkerRequest);

            if (string.IsNullOrEmpty(encryptedTicket))
                goto Exit;

            byte[] bTicket = CookieProtectionHelper.Decode(Roles.CookieProtectionValue, encryptedTicket, Purpose.RolePrincipal_Ticket);
            if (bTicket == null)
                goto Exit;

            RolePrincipal   rp = null;
            MemoryStream    ms = null;
            try{
                ms = new System.IO.MemoryStream(bTicket);
                rp = (new BinaryFormatter()).Deserialize(ms) as RolePrincipal;
            } catch {
            } finally {
                ms.Close();
            }
            if (rp == null)
                goto Exit;
            if (!StringUtil.EqualsIgnoreCase(rp._Username, _Identity.Name))
                goto Exit;
            if (!StringUtil.EqualsIgnoreCase(rp._ProviderName, _ProviderName))
                goto Exit;
            if (DateTime.UtcNow > rp._ExpireDate)
                goto Exit;

            _Version = rp._Version;
            _ExpireDate = rp._ExpireDate;
            _IssueDate = rp._IssueDate;
            _IsRoleListCached = rp._IsRoleListCached;
            _CachedListChanged = false;
            _Username = rp._Username;
            _Roles = rp._Roles;


 

            // will it be the case that _Identity.Name != _Username?

            RenewIfOld();

            if (HostingEnvironment.IsHosted && EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc) && HttpContext.Current != null)
                EtwTrace.Trace( EtwTraceType.ETW_TYPE_ROLE_END, HttpContext.Current.WorkerRequest, "RolePrincipal", _Identity.Name);

            return;
        Exit:
            Init();
            _CachedListChanged = true;
            if (HostingEnvironment.IsHosted && EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc) && HttpContext.Current != null)
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_ROLE_END, HttpContext.Current.WorkerRequest, "RolePrincipal", _Identity.Name);
            return;
        }

        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////

        private void Init() {
            _Version = 1;
            _IssueDate = DateTime.UtcNow;
            _ExpireDate = DateTime.UtcNow.AddMinutes(Roles.CookieTimeout);
            //_CookiePath = Roles.CookiePath;
            _IsRoleListCached = false;
            _CachedListChanged = false;
            if (_ProviderName == null)
                _ProviderName = Roles.Provider.Name;
            if (_Roles == null)
                _Roles = new HybridDictionary(true);
            if (_Identity != null)
                _Username = _Identity.Name;

            AddIdentityAttachingRoles(_Identity);
        }

        /// <summary>
        /// helper method to bind roles with an identity.
        /// </summary>
        [SecuritySafeCritical]
        void AddIdentityAttachingRoles(IIdentity identity)
        {
            ClaimsIdentity claimsIdentity = null;

            if (identity is ClaimsIdentity)
            {
                claimsIdentity = (identity as ClaimsIdentity).Clone();
            }
            else
            {
                claimsIdentity = new ClaimsIdentity(identity);
            }

            AttachRoleClaims(claimsIdentity);
            base.AddIdentity(claimsIdentity);
        }

        [SecuritySafeCritical]
        void AttachRoleClaims(ClaimsIdentity claimsIdentity)
        {
            RoleClaimProvider claimProvider = new RoleClaimProvider(this, claimsIdentity);

            if (s_type == null)
            {
                s_type = typeof(DynamicRoleClaimProvider);
            }

            s_type.InvokeMember("AddDynamicRoleClaims", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { claimsIdentity, claimProvider.Claims }, CultureInfo.InvariantCulture);
        }

 

        ////////////////////////////////////////////////////////////
        // Public properties

        public int       Version           { get { return _Version;}}
        public DateTime  ExpireDate        { get { return _ExpireDate.ToLocalTime();}}
        public DateTime  IssueDate         { get { return _IssueDate.ToLocalTime();}}
        // DevDiv Bugs: 9446
        // Expired should check against DateTime.UtcNow instead of DateTime.Now because
        // _ExpireData is a Utc DateTime.
        public bool      Expired           { get { return _ExpireDate < DateTime.UtcNow;}}
        public String    CookiePath        { get { return Roles.CookiePath;}} //
        public override  IIdentity Identity          { get { return _Identity; }}
        public bool      IsRoleListCached  { get { return _IsRoleListCached; }}
        public bool      CachedListChanged { get { return _CachedListChanged; }}
        public string    ProviderName      { get { return _ProviderName; } }

        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        // Public functions

        [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public string ToEncryptedTicket()
        {
            if (!Roles.Enabled)
                return null;
            if (_Identity != null && !_Identity.IsAuthenticated)
                return null;
            if (_Identity == null && string.IsNullOrEmpty(_Username))
                return null;
            if (_Roles.Count > Roles.MaxCachedResults)
                return null;

            MemoryStream ms = new System.IO.MemoryStream();
            byte[] buf = null;
            IIdentity id = _Identity;
            try {
                _Identity = null;
                BinaryFormatter bf = new BinaryFormatter();
                bool originalSerializingForCookieValue = _serializingForCookie;
                try {
                    // DevDiv 481327: ClaimsPrincipal is an expensive type to serialize and deserialize. If the developer is using
                    // role management, then he is going to be querying regular ASP.NET membership roles rather than claims, so
                    // we can cut back on the number of bytes sent across the wire by ignoring any claims in the underlying
                    // identity. Otherwise we risk sending a cookie too large for the browser to handle.
                    _serializingForCookie = true;
                    bf.Serialize(ms, this);
                }
                finally {
                    _serializingForCookie = originalSerializingForCookieValue;
                }
                buf = ms.ToArray();
            } finally {
                ms.Close();
                _Identity = id;
            }

            return CookieProtectionHelper.Encode(Roles.CookieProtectionValue, buf, Purpose.RolePrincipal_Ticket);
        }

        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        private void RenewIfOld() {
            if (!Roles.CookieSlidingExpiration)
                return;
            DateTime dtN = DateTime.UtcNow;
            TimeSpan t1  = dtN - _IssueDate;
            TimeSpan t2  = _ExpireDate - dtN;

            if (t2 > t1)
                return;
            _ExpireDate = dtN + (_ExpireDate - _IssueDate);
            _IssueDate = dtN;
            _CachedListChanged = true;
        }

        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        public string[] GetRoles()
        {
            if (_Identity == null)
                throw new ProviderException(SR.GetString(SR.Role_Principal_not_fully_constructed));

            if (!_Identity.IsAuthenticated)
                return new string[0];
            string[] roles;

            if (!_IsRoleListCached || !_GetRolesCalled) {
                _Roles.Clear();
                roles = Roles.Providers[_ProviderName].GetRolesForUser(Identity.Name);
                foreach (string role in roles)
                    if (_Roles[role] == null)
                        _Roles.Add(role, String.Empty);
                _IsRoleListCached = true;
                _CachedListChanged = true;
                _GetRolesCalled = true;
                return roles;
            } else {
                roles = new string[_Roles.Count];
                int index = 0;
                foreach (string role in _Roles.Keys)
                    roles[index++] = role;
                return roles;
            }
        }

        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////

        public override bool IsInRole(string role)
        {
            if (_Identity == null)
                throw new ProviderException(SR.GetString(SR.Role_Principal_not_fully_constructed));

            if (!_Identity.IsAuthenticated || role == null)
                return false;
            role = role.Trim();
            if (!IsRoleListCached) {
                _Roles.Clear();
                string[] roles = Roles.Providers[_ProviderName].GetRolesForUser(Identity.Name);
                foreach(string roleTemp in roles)
                    if (_Roles[roleTemp] == null)
                        _Roles.Add(roleTemp, String.Empty);

                _IsRoleListCached = true;
                _CachedListChanged = true;
            }
            if (_Roles[role] != null)
                return true;

            //
            return base.IsInRole(role);
        }

        public void SetDirty()
        {
            _IsRoleListCached = false;
            _CachedListChanged = true;
        }

 

        protected RolePrincipal(SerializationInfo info, StreamingContext context)
            :base(info, context)
        {
            _Version = info.GetInt32("_Version");
            _ExpireDate = info.GetDateTime("_ExpireDate");
            _IssueDate = info.GetDateTime("_IssueDate");
            try {
                _Identity = info.GetValue("_Identity", typeof(IIdentity)) as IIdentity;
            } catch { } // Ignore Exceptions
            _ProviderName = info.GetString("_ProviderName");
            _Username = info.GetString("_Username");
            _IsRoleListCached = info.GetBoolean("_IsRoleListCached");
            _Roles = new HybridDictionary(true);
            string allRoles = info.GetString("_AllRoles");
            if (allRoles != null) {
                foreach(string role in allRoles.Split(new char[] {','}))
                    if (_Roles[role] == null)
                        _Roles.Add(role, String.Empty);
            }

            // attach ourselves to the first valid claimsIdentity. 
            bool found = false;
            foreach (var claimsIdentity in base.Identities)
            {
                if (claimsIdentity != null)
                {
                    AttachRoleClaims(claimsIdentity);
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                AddIdentityAttachingRoles(new ClaimsIdentity(_Identity));
            }
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            GetObjectData(info, context);
        }

        protected override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (!_serializingForCookie) {
                base.GetObjectData(info, context);
            }

            info.AddValue("_Version", _Version);

            info.AddValue("_ExpireDate", _ExpireDate);
            info.AddValue("_IssueDate", _IssueDate);
            try {
                info.AddValue("_Identity", _Identity);
            } catch { } // Ignore Exceptions
            info.AddValue("_ProviderName", _ProviderName);
            info.AddValue("_Username", _Identity == null ? _Username : _Identity.Name);
            info.AddValue("_IsRoleListCached", _IsRoleListCached);
            if (_Roles.Count > 0) {
                StringBuilder sb = new StringBuilder(_Roles.Count * 10);
                foreach(object role in _Roles.Keys)
                    sb.Append(((string)role) + ",");
                string allRoles = sb.ToString();
                info.AddValue("_AllRoles", allRoles.Substring(0, allRoles.Length - 1));
            } else {
                info.AddValue("_AllRoles", String.Empty);
            }

        }

        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        private int         _Version;
        private DateTime    _ExpireDate;
        private DateTime    _IssueDate;
        private IIdentity   _Identity;
        private string      _ProviderName;
        private string      _Username;
        private bool        _IsRoleListCached;
        private bool        _CachedListChanged;

        [ThreadStatic]
        private static bool _serializingForCookie;

        [NonSerialized]
        private HybridDictionary _Roles = null;
        [NonSerialized]
        private bool _GetRolesCalled;
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////
     }
}

Comments

You must Sign In to comment on this topic.


© 2024 Digcode.com