Problems with sessions, different users have same session

Hi,
i have huge problems with my sessions, seems that the users logged in will have the same session.

  1. If i log in with UserA a session is created and the user can call different services and everything works fine.
  2. But when UserB loggs in on another computer that session is also been overwritten for UserA.
  3. This means that when UserA calls a service next time he will get the last logged in users info.

Can you maybe see if something is wrong in my code or maybe guide me of what is wrong?

Would be very appreciated!

Class AspnetMembershipAuthSession

public class AspnetMembershipAuthSession : AuthUserSession
{
    private readonly IRoleProviderWrapper _roleProvider;
    private readonly IUserAuthRepository _userRep;

    public AspnetMembershipAuthSession(IRoleProviderWrapper roleProvider, IUserAuthRepository userRep)
    {
        _roleProvider = roleProvider;
        _userRep = userRep;
    }

    [DataMember]
    public Guid MembershipId { get; set; }

    [DataMember]
    //RegisteredUserId
    public Guid CustomerId { get; set; }

    [DataMember]
    public Guid CompanyUserId { get; set; }

    [DataMember]
    public Guid CompanyId { get; set; }

    public bool IsAdmin
    {
        get { return HasRole(Role.ApplicationAdmin, _userRep); }
    }

    public bool IsSuperAdmin
    {
        get { return HasRole(Role.SuperAdmin, _userRep); }
    }

    public bool IsAnyAdmin
    {
        get { return IsAdmin || IsSuperAdmin; }
    }

    public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens,
        Dictionary<string, string> authInfo)
    {
        base.OnAuthenticated(authService, session, tokens, authInfo);

        var authRepository = authService.TryResolve<IAuthRepository>();
        var userAuth = authRepository.GetUserAuth(session, tokens);
        var guids = userAuth.RefIdStr.Split(new[] {':'}, StringSplitOptions.RemoveEmptyEntries);

        MembershipId = Guid.Parse(guids[0]);
        CustomerId = Guid.Parse(guids[1]);

        if (guids.Length > 3)
        {
            CompanyUserId = Guid.Parse(guids[2]);
            CompanyId = Guid.Parse(guids[3]);
        }

        // we want to save after we set the custom fields
        authService.SaveSession(this, SessionFeature.DefaultSessionExpiry);
    }

    public override void OnLogout(IServiceBase authService)
    {
        var id = authService.GetSessionId();
        authService.RemoveSession();
        base.OnLogout(authService);
        var id2 = authService.GetSessionId();
        
    }


    public override bool HasRole(string role, IAuthRepository authRepo)
    {
        //return Roles != null && Roles.Any(r => r.ToLowerInvariant() == role.ToLowerInvariant());
        return _roleProvider.HasRole(UserName, role);
    }
}

Class AspNetMembershipAuthRepository

public class AspNetMembershipAuthRepository : IUserAuthRepository
{

    private IDbConnectionFactory _dbFactory { get; set; }
    //private readonly IDbConnection _db;
    private readonly IMembershipProviderWrapper _membershipProvider;
    private readonly IRoleProviderWrapper _roleProvider;

    // See https://github.com/ServiceStack/ServiceStack/blob/b6b89cb23a96e002d39894f70f6acdba9312ef51/src/ServiceStack.Authentication.RavenDb/RavenDbUserAuthRepository.cs
    public AspNetMembershipAuthRepository(IMembershipProviderWrapper membershipProvider,
        IRoleProviderWrapper roleProvider,
        IDbConnectionFactory dbFactory)
    {
        _membershipProvider = membershipProvider;
        _roleProvider = roleProvider;
        _dbFactory = dbFactory;
    }

    public IUserAuth CreateUserAuth(IUserAuth newUser, string password)
    {
        throw new NotImplementedException();
    }

    public IUserAuth UpdateUserAuth(IUserAuth existingUser, IUserAuth newUser)
    {
        throw new NotImplementedException();
    }

    public void DeleteUserAuth(string userAuthId)
    {
        throw new NotImplementedException();
    }

    public IUserAuth GetUserAuth(string userAuthId)
    {
        throw new NotImplementedException();
    }

    public IUserAuth UpdateUserAuth(IUserAuth existingUser, IUserAuth newUser, string password)
    {
        throw new NotImplementedException();
    }

    public IUserAuthDetails CreateOrMergeAuthSession(IAuthSession authSession, IAuthTokens tokens)
    {
        throw new NotImplementedException();
    }

    public IUserAuth GetUserAuth(IAuthSession authSession, IAuthTokens tokens)
    {
        return GetUserAuthByUserName(authSession.UserAuthName);
    }

    public IUserAuth GetUserAuthByUserName(string userNameOrEmail)
    {
        var member = _membershipProvider.GetUser(userNameOrEmail, true);

        // Roles
        var roles = _roleProvider.GetRolesForUser(member.User.UserName).ToList();

        var isAdminUser = roles.Contains(Role.ApplicationAdmin) ||
                          roles.Contains(Role.SuperAdmin);

        return GetUserInfo(isAdminUser, member, roles);
    }

    public List<IUserAuthDetails> GetUserAuthDetails(string userAuthId)
    {
        return new List<IUserAuthDetails>();
    }

    public void LoadUserAuth(IAuthSession session, IAuthTokens tokens)
    {
        throw new NotImplementedException();
    }

    public void SaveUserAuth(IUserAuth userAuth)
    {
        throw new NotImplementedException();
    }

    public void SaveUserAuth(IAuthSession authSession)
    {
        throw new NotImplementedException();
    }

    public bool TryAuthenticate(Dictionary<string, string> digestHeaders, string privateKey, int nonceTimeOut,
        string sequence, out IUserAuth userAuth)
    {
        throw new NotImplementedException();
    }

    public bool TryAuthenticate(string userName, string password, out IUserAuth userAuth)
    {
        if (_membershipProvider.ValidateUser(userName, password))
        {
            userAuth = GetUserAuthByUserName(userName);

            if (userAuth != null)
            {
                // TODO: Can we set the membership cookie here maybe?
                //FormsAuthentication.SetAuthCookie(userName, false);
                return true;
            }
        }

        userAuth = null;
        return false;
    }

    private IUserAuth GetUserInfo(bool isAdminUser, IMembership membership, List<string> roles)
    {
        using (var Db = _dbFactory.Open())
        {
            var membershipId = (Guid)membership.ProviderUserKey;


            var registeredUser = Db.Single<Customer>(u => u.MembershipId == membershipId);


            var userAuth = new UserAuth();
            userAuth.Roles = roles;

            if (registeredUser != null)
            {
                userAuth.UserName = membership.User.UserName.ToLowerInvariant();
                userAuth.FirstName = registeredUser.Firstname;
                userAuth.LastName = registeredUser.Lastname;
                userAuth.Email = registeredUser.Email;
                userAuth.CreatedDate = membership.CreateDate;
                userAuth.PhoneNumber = registeredUser.Phone;
                userAuth.RefIdStr = string.Format("{0}:{1}",
                    membershipId,
                    registeredUser.Id);
            };


            // For admin users, get the applicationuser aswell
            if (isAdminUser)
            {
                var applicationUser = Db.Single<CompanyUser>(u => u.MembershipId == membershipId);

                if (applicationUser != null)
                {
                    userAuth.UserName = userAuth.UserName ?? applicationUser.Email;
                    userAuth.FirstName = userAuth.FirstName ?? applicationUser.Firstname;
                    userAuth.LastName = userAuth.LastName ?? applicationUser.Lastname;
                    userAuth.Email = userAuth.Email ?? applicationUser.Email;
                    userAuth.PhoneNumber = userAuth.PhoneNumber ?? applicationUser.Phone;

                    userAuth.RefIdStr = string.Format("{0}:{1}:{2}:{3}",
                        membershipId,
                        registeredUser == null ? Guid.Empty : registeredUser.Id,
                        applicationUser.Id,
                        applicationUser.CompanyId);
                }

                return userAuth;
            }

            return userAuth;
        }
    }
}

Class MembershipProviderWrapper

public class MembershipProviderWrapper : IMembershipProviderWrapper
{
    public IMembership GetUser(string userNameOrEmail, bool userIsOnline)
    {
        var user = Membership.Provider.GetUser(userNameOrEmail, userIsOnline);

        return new MembershipWrapper
        {
            User = new MembershipUserWrapper
            {
                UserId = Guid.Parse(user.ProviderUserKey.ToString()),
                UserName = user.UserName,
                LastActivityDate = user.LastActivityDate
            },
            CreateDate = user.CreationDate,
            ProviderUserKey = user.ProviderUserKey
        };
    }

    public bool ValidateUser(string userName, string password)
    {
		return Membership.Provider.ValidateUser(userName, password);
    }

    public bool ValidatePasswordComplexity(string password)
    {
        return Regex.IsMatch(password, "(?!^[0-9]*$)(?!^[a-zA-Z]*$)^([a-zA-Z0-9]{6,15})$");
    }

    public bool ChangePassword(string userName, string oldPassword, string newPassword)
    {
        var user = Membership.Provider.GetUser(userName, true);
        return user.ChangePassword(oldPassword, newPassword);
    }


    public Guid CreateUser(string userName, string password)
    {
        var user = Membership.CreateUser(userName, password).ProviderUserKey;
        return Guid.Parse(user.ToString());
    }
}

 public class MyCredentialsAuthProvider : CredentialsAuthProvider
{

    public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
    {
        var authResponse = (AuthenticateResponse) base.Authenticate(authService, session, request);
        var user = ((BokaMera.API.ServiceInterface.Security.AspNetMembership.AspnetMembershipAuthSession)session);
        user.Language = Thread.CurrentThread.CurrentUICulture.ToString();
        //return your own class, but take neccessary data from AuthResponse
        //return new
        //{
        //    SessionId = authResponse.SessionId,
        //    UserName = authResponse.UserName,
        //    CompanyUserId = user.CompanyUserId,
        //    RegisteredUserId = user.RegisteredUserId,
        //    FirstName = user.FirstName,
        //    LastName = user.LastName,
        //    Language = user.Language,
        //    ReferrerUrl = authResponse.ReferrerUrl,
        //    SessionExpires = DateTime.Now
        //};

        return authResponse;
    
    }
    
}

No-one’s going to be able to debug these code fragments so you’re going to be in the best position to debug and diagnose what the issue is.

What stands out is that the AuthUserSession should be treated as a Data Transfer Object (DTO) and should never have a non-parameterless constructor that you’re currently using to inject dependencies. The purpose of the AuthUserSession is to be serialized and deserialized from the Cache, it should never have any non-serializable/non-DTO properties like IRoleProviderWrapper or IUserAuthRepository. This would be the first thing I’d look at resolving. Any dependencies you need can be resolved with HostContext.Resolve<T>.

Don’t save the Session in OnAuthenticated(), it doesn’t make sense to persist yourself from within yourself and the Session is already automatically persisted.

Unless you know what you’re doing don’t try to override the existing Logout behavior, the implementation shouldn’t be in the UserSession and removing the Session Ids is likely to interfere with the existing Logout implementation.

public override void OnLogout(IServiceBase authService)
{
    var id = authService.GetSessionId();
    authService.RemoveSession();
    base.OnLogout(authService);
    var id2 = authService.GetSessionId();    
}

I’ll reiterate that the UserSession is a data model and shouldn’t contain any behavior like this. If you want to override the existing Logout implementation you can override AuthProvider.Logout() in your Custom Auth Provider. Your Custom AuthProvider should also be the 1st AuthProvider registered in your AuthFeature plugin.

But I’d recommend leaving the existing Logout implementation as-is unless you really need to change it, if you want to provide a custom logout impl, you’re better off leaving the existing implementation alone and create a new Service (e.g. /my-logout`) containing your custom Logout functionality and call that instead.

Remove AspNetMembershipAuthRepository

Your partial implementation of AspNetMembershipAuthRepository isn’t useful, if you’re not going to provide a complete implementation you’re better off not having one. Your Custom AuthProvider is what uses the registered AuthRepository to persist/retrieve User Info in the database. Since you’re not using your AuthRepository as your Users source, you shouldn’t have one - as it doesn’t do what AuthRepository’s should.

Look at AspNetWindowsAuthProvider

This impl is all over the place. I’d suggest having a look at the existing AspNetWindowsAuthProvider which is likely closer to what you’re after as it appears you’re trying to use a custom ASP.NET Membership Provider.

The AspNetWindowsAuthProvider implements IAuthWithRequest so it re-authenticates on each Request (using Windows Auth) that doesn’t use an AuthRepository or persist anything to the Session since it doesn’t need an authenticated User Session in between requests (since it Authenticates on each request) and since it doesn’t store any User Sessions, doesn’t need to logout since nothing gets stored in the Cache and there’s no Authenticated UserSession to remove.

If this is closer to what you need, I’d recommend starting from scratch and only copy over the custom impl that you need, instead of trying to rework the broken impl you’ve got now.

1 Like

Thx Mytz, very good support. I will try to fix the issues as you suggested.

Regards Kristian

Hi Mythz,
just wanted to let you know the problems is solved.
It was dueto your gudiance we solved the problem and also corrected alot of things as you suggested.
Many thanks!

1 Like