IIdentity, Generic Principal, WebAPi and SignalR

Jul 25, 2012 at 3:22 PM
Edited Jul 25, 2012 at 4:57 PM

Hello,

well a year ago i started learning asp.net with your project and one of the things i learnt was the use of the Security.Principal class and IIdentity to store information about authenticated user in the cookie and then use in the MVC controllers (version 4 in my case). 

Well i been trying to use WebApi inside a project with this well know artifacts for authentication and so on with some minor changes and i found a WALL a can not climb.

The changes i´ve made is only in the class AuthorizedController, well really i create an AuthorizedApiController that basically is the same as AuthorizedController except that inherits from ApiController  vs Controller for mvc controller the rest is the same.

Other change i´ve made is that i use nuger Unity.webapi to make the IoC on the repositories and services because unity alone is very complicated to work on webapi.

ok, we reach the problem is thhis,, in the PrincipalExtensions class the code:

       public static MileageStatsIdentity MileageStatsIdentity(this IPrincipal principal)
        {
            return (MileageStatsIdentity) principal.Identity;
        }

the principal.Identity is Forms instead of MileAgestatsIdentity and a cast exception is thrown. But only when the call is a webapi, mvc calls works as expected. It's very strange because the Pincipal is there, the user is logged but the Identity is Forms instead of MileAgestatsIdentity 

I read that for webApi you should keep the Credentials also for the thread. So i tried in global.asax inside PostAuthenticateRequestHandler include it:

 this.Context.User = new GenericPrincipal(mileageStatsIdentity, null);
System.Threading.Thread.CurrentPrincipal =this.Context.User;

but this way i´ve got serialization problems of the MileageStatsIdentity for all calls.

Well any advice, clue, or link are welcome.

 

Thanks a lot in advance.

Developer
Jul 26, 2012 at 10:04 PM
Edited Jul 26, 2012 at 10:06 PM

Hi,

So far, I'm not aware if there are differences required when implementing authentication in a Web API application. In my opinion you could find more information about this in the Web API Official Site, also for better guidance regarding this specific topic you could check in the ASP.NET forums.

On the other hand, based on my understanding, it seems that the MileageStatsIdentity could be created based on the ticket contained in the FormsIdentity you are receiving in order to avoid the cast exception error. For example, I believe you could try modifying your PrincipalExtensions class to something like this:

 

public static class PrincipalExtensions
    {
        public static MileageStatsIdentity MileageStatsIdentity(this IPrincipal principal)
        {
            if(principal.Identity is MileageStatsIdentity)
            {
                return (MileageStatsIdentity)principal.Identity;
            }
            else if (principal.Identity is FormsIdentity)
            {
                FormsIdentity form = (FormsIdentity)principal.Identity;
                MileageStatsIdentity msIdentity = new MileageStatsIdentity(form.Ticket);
                return msIdentity;
            }

            return null;
        }
     }
  

 

Take into account that we haven't tested this and it could cause undesired behaviors in your scenario.

Also, consider that if your are implementing a custom AuthorizeAttribute in your scenario,  I have seen different users reporting authentication problems when implementing this in Web API applications, if this is your case, I believe you could check the following related thread.

I hope you find this useful,

Agustin Adami
http://blogs.southworks.net/aadami

Jul 26, 2012 at 11:39 PM

Hi,

I thought, It should derrive from FormAuthentication or

HttpContext current = HttpContext.Current;

if (current != null && current.User != null)

return current.User;

 

IPrincipal

principal = (IPrincipal)new GenericPrincipal((IIdentity)new GenericIdentity(userName, "Forms"), Enumerable.ToArray<string(AuthenticationBase

<T>.DefaultRoles));

Jul 26, 2012 at 11:41 PM

I did it for WPF

Hope it help

public abstract class AuthenticationBase<T> : IAuthentication<T> where T : IUser, new()
    {
        private static readonly IEnumerable<string> DefaultRoles = (IEnumerable<string>)new string[0];
        private static readonly IPrincipal DefaultPrincipal = (IPrincipal)new GenericPrincipal((IIdentity)new GenericIdentity(string.Empty), Enumerable.ToArray<string>(AuthenticationBase<T>.DefaultRoles));

        public T Login(string userName, string password, bool isPersistent)
        {
            if (!this.ValidateUser(userName, password))
                return default(T);
            IPrincipal principal = (IPrincipal)new GenericPrincipal((IIdentity)new GenericIdentity(userName, "Forms"), Enumerable.ToArray<string>(AuthenticationBase<T>.DefaultRoles));
            this.IssueAuthenticationToken(principal, isPersistent);
            return this.GetUserCore(principal);
        }

        public virtual bool ValidateUser(string userName, string password)
        {
            try
            {
                return Membership.ValidateUser(userName, password);
            }
            catch (SqlException ex)
            {
                throw new DomainException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_ProviderError, new object[2]
                {(object) "Membership",(object) ex.Message}), (Exception)ex);
            }
        }

        private T GetUserCore(IPrincipal principal)
        {
            T obj1 = default(T);
            T obj2 = !principal.Identity.IsAuthenticated ? this.GetAnonymousUser() : this.GetAuthenticatedUser(principal);
            if ((object)obj2 != null)
                return obj2;
            throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_GetUserCannotBeNull, new object[1]
            {(object) this.GetType()}));
        }

        private T GetUserImpl(IPrincipal principal)
        {
            T user = this.CreateUser();
            if ((object)user == null)
            {
                throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_CreateUserCannotBeNull, new object[1]
                {(object) this.GetType()}));
            }
            else
            {
                user.Name = principal.Identity.Name;
                user.Roles = AuthenticationBase<T>.GetRoles(user.Name);
                AuthenticationBase<T>.GetProfile(user);
                return user;
            }
        }

        protected virtual T CreateUser()
        {
            return new T();
        }

        private static IEnumerable<string> GetRoles(string userName)
        {
            if (!Roles.Enabled)
                return AuthenticationBase<T>.DefaultRoles;
            try
            {
                return (IEnumerable<string>)Roles.GetRolesForUser(userName);
            }
            catch (SqlException ex)
            {
                throw new DomainException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_ProviderError, new object[2]
                {(object) "Role",
                (object) ex.Message}), (Exception)ex);
            }
        }

        private static void GetProfile(T user)
        {
            if (string.IsNullOrEmpty(user.Name) || !ProfileManager.Enabled)
                return;
            ProfileBase profileBase = ProfileBase.Create(user.Name);
            foreach (PropertyInfo propertyInfo in user.GetType().GetProperties())
            {
                if (propertyInfo.CanWrite && AuthenticationBase<T>.IsInProfile(propertyInfo))
                {
                    if (propertyInfo.GetIndexParameters().Length <= 0)
                    {
                        try
                        {
                            propertyInfo.SetValue((object)user, profileBase.GetPropertyValue(AuthenticationBase<T>.GetProfileAlias(propertyInfo)), (object[])null);
                        }
                        catch (SqlException ex)
                        {
                            throw new DomainException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_ProviderError, new object[2]
                            {(object) "Profile",(object) ex.Message}), (Exception)ex);
                        }
                        catch (SettingsPropertyNotFoundException ex)
                        {
                            throw new InvalidDataContractException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_ProfilePropertyDoesNotExist, new object[1]
                            {(object) AuthenticationBase<T>.GetProfileAlias(propertyInfo)}), (Exception)ex);
                        }
                    }
                }
            }
        }

        private static bool IsInProfile(PropertyInfo propertyInfo)
        {
            bool flag = true;
            ProfileUsageAttribute profileUsage = AuthenticationBase<T>.GetProfileUsage(propertyInfo);
            if (profileUsage != null)
                flag = !profileUsage.IsExcluded;
            return flag;
        }

        private static string GetProfileAlias(PropertyInfo propertyInfo)
        {
            string str = propertyInfo.Name;
            ProfileUsageAttribute profileUsage = AuthenticationBase<T>.GetProfileUsage(propertyInfo);
            if (profileUsage != null && !string.IsNullOrEmpty(profileUsage.Alias))
                str = profileUsage.Alias;
            return str;
        }

        private static ProfileUsageAttribute GetProfileUsage(PropertyInfo propertyInfo)
        {
            return Enumerable.SingleOrDefault<object>((IEnumerable<object>)propertyInfo.GetCustomAttributes(typeof(ProfileUsageAttribute), false)) as ProfileUsageAttribute;
        }

        protected virtual T GetAuthenticatedUser(IPrincipal principal)
        {
            return this.GetUserImpl(principal);
        }

        protected virtual T GetAnonymousUser()
        {
            return this.GetUserImpl(AuthenticationBase<T>.DefaultPrincipal);
        }

        protected virtual void IssueAuthenticationToken(IPrincipal principal, bool isPersistent)
        {
            //FormsAuthentication.SetAuthCookie(principal.Identity.Name, isPersistent);
        }

        protected virtual void ClearAuthenticationToken()
        {
            //FormsAuthentication.SignOut();
        }

        private static IPrincipal GetPrincipal()
        {
            IPrincipal current = (IPrincipal)new GenericPrincipal((IIdentity)new GenericIdentity("tthaicarrot", "Forms"), Enumerable.ToArray<string>(AuthenticationBase<T>.DefaultRoles));
            if (current != null && current.Identity != null)
                return current;
            else
                return AuthenticationBase<T>.DefaultPrincipal;
        }

        #region IAuthentication members
        public T GetUser()
        {
            return this.GetUserCore(AuthenticationBase<T>.GetPrincipal());
        }

        public T Logout()
        {
            this.ClearAuthenticationToken();
            return this.GetUserCore(AuthenticationBase<T>.DefaultPrincipal);
        }

        public void UpdateUser(T user)
        {
            //if (this.ServiceContext.User == null || this.ServiceContext.User.Identity == null || !string.Equals(this.ServiceContext.User.Identity.Name, user.Name, StringComparison.Ordinal))
                //throw new UnauthorizedAccessException(Resources.ApplicationServices_UnauthorizedUpdate);
            this.UpdateUserCore(user);
        }

        private static bool IsReadOnly(PropertyInfo propertyInfo)
        {
            EditableAttribute editableAttribute = Enumerable.FirstOrDefault<EditableAttribute>(Enumerable.Cast<EditableAttribute>((IEnumerable)propertyInfo.GetCustomAttributes(typeof(EditableAttribute), false)));
            if (editableAttribute != null)
                return !editableAttribute.AllowEdit;
            else
                return false;
        }

        private static ProfileBase GetProfileBase(string userName)
        {
            HttpContext current = HttpContext.Current;
            if (current != null && current.Profile != null)
                return current.Profile;
            else
                return ProfileBase.Create(userName);
        }

        protected virtual void UpdateUserCore(T user)
        {
            AuthenticationBase<T>.UpdateProfile(user);
        }

        private static void UpdateProfile(T user)
        {
            if (string.IsNullOrEmpty(user.Name) || !ProfileManager.Enabled)
                return;
            ProfileBase profileBase = AuthenticationBase<T>.GetProfileBase(user.Name);
            foreach (PropertyInfo propertyInfo in user.GetType().GetProperties())
            {
                if (propertyInfo.CanRead && propertyInfo.CanWrite && (AuthenticationBase<T>.IsInProfile(propertyInfo) && !AuthenticationBase<T>.IsReadOnly(propertyInfo)))
                {
                    if (propertyInfo.GetIndexParameters().Length <= 0)
                    {
                        try
                        {
                            profileBase.SetPropertyValue(AuthenticationBase<T>.GetProfileAlias(propertyInfo), propertyInfo.GetValue((object)user, (object[])null));
                        }
                        catch (SettingsPropertyNotFoundException ex)
                        {
                            throw new InvalidDataContractException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_ProfilePropertyDoesNotExist, new object[1]
                            {(object) AuthenticationBase<T>.GetProfileAlias(propertyInfo)}), (Exception)ex);
                        }
                        catch (SettingsPropertyIsReadOnlyException ex)
                        {
                            throw new InvalidDataContractException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_ProfilePropertyReadOnly, new object[1]
                            {(object) AuthenticationBase<T>.GetProfileAlias(propertyInfo)}), (Exception)ex);
                        }
                        catch (SettingsPropertyWrongTypeException ex)
                        {
                            throw new InvalidDataContractException(string.Format((IFormatProvider)CultureInfo.InvariantCulture, Resources.ApplicationServices_ProfilePropertyTypeMismatch, new object[1]
                            {(object) AuthenticationBase<T>.GetProfileAlias(propertyInfo)}), (Exception)ex);
                        }
                    }
                }
            }
            bool flag = false;
            try
            {
                flag = ProfileManager.AutomaticSaveEnabled;
            }
            catch (HttpException ex)
            {
            }
            if (flag)
                return;
            profileBase.Save();
        }
        #endregion
    }

Jul 30, 2012 at 8:27 AM

Works very well, i will take this way. If someone knows or have time to discover why the Identity class is lost you are welcome to discuss here.

Again thanks for answering.

 

See you

Jul 30, 2012 at 9:14 AM

Hi jlsfernandez,

Have you take a look at http://msdn.microsoft.com/en-us/library/bb384339.aspx?

Aug 11, 2012 at 11:51 PM

Hello, i add in the title SignalR, :-)

first of all thanks i´ve been reading your msdn link (perhaps one of the reason i´ve take so long to answer you... so hard for me).

on the other hand i´ve been trying to use your sample code with hubs and persistent connections with SignalR. with different results.

 

Persistent connections can be managed "moreless" the same as Silk has been written but hubs is different, perhaps i´m miss something on the client side to keep on sybc the cookie.

I´m not able to access the cookie information, The _authorize class from silk extends from System.WQeb.Mvc.Controller that has Iprincipal User and so on, but Hub doesn't  have all this tools so i´m a little bit lost.

 

Couls you sugestme a link o sample project to learn from it?

 

Thanks