Error when using PrincipalContext.ValidateCredentials to authenticate against a Local Machine?

11

I have a WCF service which contains a Login method that validates a username and password against the local machine credentials, and after a seemingly random period of time it will stop working for some users.

The actual login command looks like this:

public UserModel Login(string username, string password, string ipAddress)
{
    // Verify valid parameters
    if (username == null || password == null)
        return null;

    try
    {
        using (var pContext = new PrincipalContext(ContextType.Machine))
        {
            // Authenticate against local machine
            if (pContext.ValidateCredentials(username, password))
            {
                // Authenticate user against database
                using (var context = new MyEntities(Connections.GetConnectionString()))
                {
                    var user = (from u in context.Users
                                where u.LoginName.ToUpper() == username.ToUpper()
                                      && u.IsActive == true
                                      && (string.IsNullOrEmpty(u.AllowedIpAddresses)
                                        || u.AllowedIpAddresses.Contains(ipAddress))
                                select u).FirstOrDefault();

                    // If user failed to authenticate against database
                    if (user == null)
                        return null;

                    // Map entity object to return object and assign session token
                    var userModel = Mapper.Map<User, UserModel>(user);
                    userModel.Token = Guid.NewGuid();
                    userModel.LastActivity = DateTime.Now;

                    // Authenticated users are added to a list on the server
                    // and their login expires after 20 minutes of inactivity
                    authenticatedUsers.Add(userModel);
                    sessionTimer.Start();

                    // User successfully authenticated, so return UserModel to client
                    return userModel;
                }
            }
        }
    }
    catch(Exception ex)
    {
        // This is getting hit
        MessageLog.WriteMessage(string.Format("Exception occurred while validating user: {0}\n{1}", ex.Message, ex.StackTrace));
        return null;
    }

    // If authentication against local machine failed, return null
    return null;
}

This appears to work fine for a few days, then it will abruptly stop working for some users and throw this exception:

Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)

at System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target, String userName, String password)

at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)

at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)

at MyNamespace.LoginService.Login(String username, String password, String ipAddress) in C:\Users\me\Desktop\somefolder\LoginService.svc.cs:line 67

Line 67 is: if (pContext.ValidateCredentials(username, password))

I'm not sure if it matters or not, but the final line of the error message is the path of the VS solution on my development machine, not the path to the files on the production server.

When it fails, it only fails for some users, while others can continue to login just fine. The only thing I have found to temporarily fix the error is running iisreset. Stopping/starting the web site or recycling the app pool doesn't work.

I am unable to reproduce the error on demand. I've tried logging in with the same user from multiple sessions and IP addresses, logging in with different users from the same browser session at the same time, spamming the Login button to try and get it to run multiple times, etc but everything appears to work fine.

I can see from our logging that users have been successfully able to login in the past:

3/21/2013
o   9:03a   I logged in
o   1:54p   UserB logged in
o   1:55p   UserA logged in
o   2:38p   UserB logged in
o   3:48p   UserB logged in
o   5:18p   UserA logged in
o   6:11p   UserB logged in

3/22/2013
o   12:42p  UserA logged in
o   5:22p   UserB logged in
o   8:04p   UserB logged in

3/25/2013 (today)
o   8:47a   I logged in
o   12:38p  UserB tries logging in and fails. Repeated ~15 times over next 45 min
o   1:58p   I login successfully
o   2:08p   I try to login with UserB's login info and fail with the same error

The reason we authenticate against the local machine is because users have an account created locally for FTP access, and we didn't want to build our own custom login system or make our users remember two sets of credentials.

The code should only authenticate the user's credentials, and does not do do anything else with the user's credentials. There is no other code that uses System.DirectoryServices, no file IO going on, and no access to anything locally on the file system other than the files required to run the web application.

What can cause that error to appear, seemingly at random, after a few days? And how can I fix it?

The server is Windows Server 2003, which runs IIS 6.0, and it is setup to use .Net Framework 4.0

c#
wcf
authentication
active-directory
principalcontext
asked on Stack Overflow Mar 25, 2013 by Rachel • edited Jun 20, 2020 by Community

1 Answer

5

The closest I can find online towards explaining this problem is this forum post, where the user experiencing the same error and got a replay stating:

The WinNT provider does not do well in a server environment. I am actually suprised you don't see this with a much smaller load. I have been able to get this with only 2 or 3 users.

and this SO comment stating

The BEST way to correctly authenticate someone is to use LogonUserAPI as @stephbu write. All other methods described in this post will NOT WORK 100%

where "all other methods" includes the top voted answer of using PrincipalContext.ValidateCredentials

Its sounding like PrincipalContext.ValidateCredentials isn't completely 100% reliable on Windows Server 2003 and IIS6.0, so I rewrote my authentication code to use the LogonUser WinAPI method instead.

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
    string lpszUsername,
    string lpszDomain,
    string lpszPassword,
    int dwLogonType,
    int dwLogonProvider,
    out IntPtr phToken
    );

IntPtr hToken;
if (LogonUser(username, "", password, 
    LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, out hToken))
{
    ...
}
answered on Stack Overflow Mar 26, 2013 by Rachel • edited May 23, 2017 by Community

User contributions licensed under CC BY-SA 3.0