Question

I'm running PHP, Apache, and Windows. I do not have a domain setup, so I would like my website's forms-based authentication to use the local user accounts database built in to Windows (I think it's called SAM).

I know that if Active Directory is setup, you can use the PHP LDAP module to connect and authenticate in your script, but without AD there is no LDAP. What is the equivalent for standalone machines?

Was it helpful?

Solution

I haven't found a simple solution either. There are examples using CreateObject and the WinNT ADSI provider. But eventually they all bump into User authentication issues with the Active Directory Service Interfaces WinNT provider. I'm not 100% sure but I guess the WSH/network connect approach has the same problem.
According to How to validate user credentials on Microsoft operating systems you should use LogonUser or SSPI.
It also says

LogonUser Win32 API does not require TCB privilege in Microsoft Windows Server 2003, however, for downlevel compatibility, this is still the best approach.

On Windows XP, it is no longer required that a process have the SE_TCB_NAME privilege in order to call LogonUser. Therefore, the simplest method to validate a user's credentials on Windows XP, is to call the LogonUser API.

Therefore, if I were certain Win9x/2000 support isn't needed, I would write an extension that exposes LogonUser to php.
You might also be interested in User Authentication from NT Accounts. It uses the w32api extension, and needs a support dll ...I'd rather write that small LogonUser-extension ;-)
If that's not feasible I'd probably look into the fastcgi module for IIS and how stable it is and let the IIS handle the authentication.

edit:
I've also tried to utilize System.Security.Principal.WindowsIdentity and php's com/.net extension. But the dotnet constructor doesn't seem to allow passing parameters to the objects constructor and my "experiment" to get the assembly (and with it CreateInstance()) from GetType() has failed with an "unknown zval" error.

OTHER TIPS

Good Question!

I've given this some thought... and I can't think of a good solution. What I can think of is a horrible horrible hack that just might work. After seeing that no one has posted an answer to this question for nearly a day, I figured a bad, but working answer would be ok.

The SAM file is off limits while the system is running. There are some DLL Injection tricks which you may be able to get working but in the end you'll just end up with password hashes and you'd have to hash the user provided passwords to match against them anyway.

What you really want is something that tries to authenticate the user against the SAM file. I think you can do this by doing something like the following.

  1. Create a File Share on the server and make it so that only accounts that you want to be able to log in as are granted access to it.
  2. In PHP use the system command to invoke a wsh script that: mounts the share using the username and password that the website user provides. records if it works, and then unmounts the drive if it does.
  3. Collect the result somehow. The result can be returned to php either on the stdout of the script, or hopefully using the return code for the script.

I know it's not pretty, but it should work.

I feel dirty :|

Edit: reason for invoking the external wsh script is that PHP doesn't allow you to use UNC paths (as far as I can remember).

Building a PHP extension requires a pretty large investment in terms of hard disk space. Since I already have the GCC (MinGW) environment installed, I have decided to take the performance hit of launching a process from the PHP script. The source code is below.

// Usage: logonuser.exe /user username /password password [/domain domain]
// Exit code is 0 on logon success and 1 on failure.

#include <windows.h>

int main(int argc, char *argv[]) {
    HANDLE r = 0;
    char *user = 0;
    char *password = 0;
    char *domain = 0;
    int i;

    for(i = 1; i < argc; i++) {
        if(!strcmp(argv[i], "/user")) {
            if(i + 1 < argc) {
                user = argv[i + 1];
                i++;
            }
        } else if(!strcmp(argv[i], "/domain")) {
            if(i + 1 < argc) {
                domain = argv[i + 1];
                i++;
            }
        } else if(!strcmp(argv[i], "/password")) {
            if(i + 1 < argc) {
                password = argv[i + 1];
                i++;
            }
        }
    }

    if(user && password) {
        LogonUser(user, domain, password, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &r);
    }
    return r ? 0 : 1;
}

Next is the PHP source demonstrating its use.

if($_SERVER['REQUEST_METHOD'] == 'POST') {
    if(isset($_REQUEST['user'], $_REQUEST['password'], $_REQUEST['domain'])) {
        $failure = 1;
        $user = $_REQUEST['user'];
        $password = $_REQUEST['password'];
        $domain = $_REQUEST['domain'];

        if($user && $password) {
            $cmd = "logonuser.exe /user " . escapeshellarg($user) . " /password " . escapeshellarg($password);
            if($domain) $cmd .= " /domain " . escapeshellarg($domain);
            system($cmd, $failure);
        }

        if($failure) {
            echo("Incorrect credentials.");
        } else {
            echo("Correct credentials!");
        }
    }
}
?>
<form action="<?php echo(htmlentities($_SERVER['PHP_SELF'])); ?>" method="post">
    Username: <input type="text" name="user" value="<?php echo(htmlentities($user)); ?>" /><br />
    Password: <input type="password" name="password" value="" /><br />
    Domain: <input type="text" name="domain" value="<?php echo(htmlentities($domain)); ?>" /><br />
    <input type="submit" value="logon" />
</form>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top