Many months ago I made a PHP script that could read NTLM authentication information from your browser. What’s NTLM? Basically, if you’re using Microsoft Windows, your browser can automatically send your windows login information to a website (if you agree to it). This means that without needing to enter additional username or passwords, you can be authenticated at the website you’re visiting. This is quite convenient especially for company intranets. NTLM should work with all major browsers (Internet Explorer, Firefox and Opera).
The PHP code I wrote is simple and can be inserted into the top of any PHP script. The key output is $user $domain $workstation, which is the information advertised by the user. Be warned though, the script does NOT authenticate the user and merely assumes that the user is who they say they are. This is akin to a user entering only a username with no password required. I plan to add password/hash verification possibly in conjuction with samba in the future.
A limitation is that the PHP script relies on apache_request_headers() which is only available if you run PHP as a apache module. (Update 2010, newer code doesn’t have this issue)
[php]
<?php
// loune 25/3/2006, updated 22/08/2009
// For more information see:
// http://siphon9.net/loune/2007/10/simple-lightweight-ntlm-in-php/
//
// This script is obsolete, you should see
// http://siphon9.net/loune/2009/09/ntlm-authentication-in-php-now-with-ntlmv2-hash-checking/
//
// NTLM specs http://davenport.sourceforge.net/ntlm.html
$headers = apache_request_headers();
if (!isset($headers[‘Authorization’])){
header(‘HTTP/1.1 401 Unauthorized’);
header(‘WWW-Authenticate: NTLM’);
exit;
}
$auth = $headers[‘Authorization’];
if (substr($auth,0,5) == ‘NTLM ‘) {
$msg = base64_decode(substr($auth, 5));
if (substr($msg, 0, 8) != "NTLMSSP\x00")
die(‘error header not recognised’);
if ($msg[8] == "\x01") {
$msg2 = "NTLMSSP\x00\x02\x00\x00\x00".
"\x00\x00\x00\x00". // target name len/alloc
"\x00\x00\x00\x00". // target name offset
"\x01\x02\x81\x00". // flags
"\x00\x00\x00\x00\x00\x00\x00\x00". // challenge
"\x00\x00\x00\x00\x00\x00\x00\x00". // context
"\x00\x00\x00\x00\x00\x00\x00\x00"; // target info len/alloc/offset
header(‘HTTP/1.1 401 Unauthorized’);
header(‘WWW-Authenticate: NTLM ‘.trim(base64_encode($msg2)));
exit;
}
else if ($msg[8] == "\x03") {
function get_msg_str($msg, $start, $unicode = true) {
$len = (ord($msg[$start+1]) * 256) + ord($msg[$start]);
$off = (ord($msg[$start+5]) * 256) + ord($msg[$start+4]);
if ($unicode)
return str_replace("\0", ”, substr($msg, $off, $len));
else
return substr($msg, $off, $len);
}
$user = get_msg_str($msg, 36);
$domain = get_msg_str($msg, 28);
$workstation = get_msg_str($msg, 44);
print "You are $user from $domain/$workstation";
}
}
?>
[/php]
If you try the script in Firefox (on windows), you will notice that you get prompted for a username and password when encountering an NTLM challenge. This is because sending your windows credentials to any unscrupulous website poses a real security risk. To make it automatically use your windows credentials for sites you trust, you can add the website to a whitelist.
The whitelist is located at Firefox’s about:config (type that into the address bar), which allows the editing of all of the browser’s preferences. Find the preference entry network.automatic-ntlm-auth.trusted-uris, double click on it and type the hostname of the site (ie http://www.abc.com) that you want in your whitelist. Multiple entries are seperated by commas. After doing that, Firefox should send your windows creds automatically.
Update 20/09/2009. The above script is outdated, anyone wishing to use NTLM should see the new post: Part 2 – Now with hash checking
Pingback: simple plan » Simple lightweight NTLM in PHP
This works great, but how do I log the user off and so that the NTML dialog re-appears again?
is this working on IE6 and IE7? When looking tcp dump i see IE abandoning request just after sending x03 message, and before receiving 200 OK response.
In firefox it is working fine. any taughts?
Andrew – the point of NTLM is that you don’t need to enter the username/password. It reads it from the current user on windows. Otherwise, you can possibly send a 401 HTTP status code to clear the authenticated session.
Kruno – It works fine for IE6 for me. Have you checked that the site you’re trying it with is trusted or on local intranet zone? Also I’ve heard that the whole transaction needs to be on one connection so another thing to check is to see if persistent connections are enabled.
This is great and works fine – I can take the username and cross reference users in a MySQL database to determine what to display in my php form, but when I submit the form, it doesn’t appear to be passing the elements in the REQUEST headers. Is that a function of the NTLM piece? It works fine without it included.
Hi – any reason why this would work fine on my http server, but not a https one?
When I check your code against the Microsoft doco “NT LAN Manager (NTLM) Authentication Protocol Specification” http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf
it doesn’t seem to match. I’m having some trouble with subsequent pages POSTing data back to the server and am trying to debug it. Do you have a detailed breakdown of the NTLMSSP challenge (that you assigning to $msg2)?
Cool function, Did you ever figure out that hash check version?
@Gary – you’re right $msg2 is missing 3 bytes, it’s now fixed.
@Eric Z – See the new php ntlm library with hash checking:
http://siphon9.net/loune/2009/09/ntlm-authentication-in-php-now-with-ntlmv2-hash-checking/
I wanted to use this for our Intranet, not for authentication but to personalise the site for our users. So the hash check would sorta be over kill. This lightweight version works perfect… and here’s the but…
BUT, all forms on the intranet that are set to method=”post” stop working and the $_POST array is totally empty. This is not the case for form set as method=”get”.
As soon as I disable the ntlm authorisation all the forms are working again. Odd thing is that some of these forms are located in pages within a different directory even i.e. ntlm auth happens on index.php and subsequently a form located in /admin/docmanager.php stops working.
I am at wits end on this one and have scoured Google from end to end for a solution…. Any thoughts?
P.S. This even happens on a fresh install of LAMP.
This is probably due to the 3-way handshake (negotiate, challenge, response) the ntlm script performs. A post would be eaten up by the negotiate step. Ideally you should only ntlm authenticate the first time in a browser session and after that, you set a cookie and don’t ntlm authenticate until the next session ie:
$user = $_COOKIE[‘username’];
if (empty($user)) {
// the ntlm.php stuff
// straight after print “You are $user…, add:
setcookie(‘username’, $user);
}
This would work provided that the user has cookies enabled and that your first request in that session isn’t a post.
Nope, still not working.
Well…. it *sorta* works. Once the cookie has been set and you close and reopen the browser then the post forms work. I am guessing this is due to the authentication being skipped as $username is not empty.
Really, REALLY baffling! —- Ohhh look! A grey hair… YAY!
I’ve just quickly mocked up an example and it works fine. There was a mistake in my original snippet in that $username variable should really just be named $user as per my ntlm code. I’ve fixed that now.
My testing was in firefox 3.0 and IE8. Could it be some quirk or setting with your browser regarding cookies? I can post my example up if you want.
Yeah that’s be nice. I wasn’t using a cookie until suggested it (there wasn’t much of interest that I could put in the cookie before this authentication snippet) so I doubt it is a problem with a cookie.
Strange thing is it *does* work for Firefox, not a single problem. The problems occur mainly in IE7 & IE8 (though occasionally IE8 works but without any changes, deletions or anything stops working).
Now as much as I would LOVE to roll out FF3 within the company the directors would have an absolute heart attack as it can’t be simply managed from a central location…. Hey hang on, now there is an opportunity for a KICKASS addon and an server app to manage it, might boost adoption, but I digress…
Here’s my example code: http://siphon9.net/loune/f/ntlm_post_test.php.txt
David:
I had the same problem with it causing the post data to be empty, and I spent all day trying to figure out a solution. We solved the problem by simply adding that same code(posted by Loune) into every php file that is doing the “$variable = $_POST[‘name’].”
Thanks Man, very very helpful.
I understand this is an old post but this script is exactly what I was looking for as it is lightweight and I can call it very easily.
However I was getting the same problem as above, after calling the script and storing the username in $_SESSION any script that used $_POST didnt work as $_POST was empty. Does this mean I need to call the script EVERY time I want to use $_POST?
How can i get the source? it asks me for usename and pw?
@Hung, I’ve fixed the post to include the code now.
I notice that line 25 has a source reference to an image (looks inadvertent.) Whats the lenth of the string you are trying to compare?
Also I am using this code instead of the password hash check since I just want a simple piece of code to identify who is logged into the computer. However in IE8 it looks like its prompting me for a username and password, especially when not on a domain. Is there any setting changes I need to do in IE8? Thanks!
@Save Thanks for pointing out the error. WordPress was trying to make a smiley. It should now be fixed. Regarding the IE8 problem, I think in IE8, you need to add your PHP website as a trusted site. You can do this be going to Internet Options > Security tab, click on the Trusted sites icon, then the sites button. In the popup, add your website domain.
That worked. Thanks for the help.
Hi Loune
I’m trying this script on an intranet and keep getting the NT Challenge prompt when using a ‘protected’ directory and no user information when using an unprotected directory.
I am using the latest install of WAMP and have mod_auth_sspi installed working on a single directory – the protected directory.
Any assistance greatefully received!
@Chris, what do you mean by protected directory? And which browser are you using? This script is for finding out the username of the current logged on windows user. Is that what you want?
Hi Loune.
Many thanks for your response.
I am using your script in two places one of them is in a directory which has the mod_auth_sspi pointed at it (.\protected\whoami.php) and the other is in the root.
I am using IE6
When accessing the script via IE6 in the root http://localhost/whoami.php i get this error ( Undefined index: REMOTE_USER in C:\wamp\www\whoami.php on line 8
) and when I access the script http://localhost/protected/whoami.php I get my user name, domain name and machine name as expected.
How ever when I access the script http://10.99.99.99/whoami.php i still get this error ( Undefined index: REMOTE_USER in C:\wamp\www\whoami.php on line 8
) and when I access the script http://10.99.99.99/protected/whoami.php I get an NT Challenge prompt, which, if I fill in the user name part only (with say, the letter ‘h’) gives the user name as ‘h’ and the correct domain name etc.
What I wish to do is extract the windows user name, domain etc (with out the user having to fill in an NT Challenge prompt).
Is this some thing to do with the way the Apache web sever is set up as I have done this type of thing numerous times using windows IIS-5 with no NT Challange prompt.
Cheers.
Chris
@Chris unfortunately I’ve had no experience with mod_auth_sspi. The script I made above is for sites which just want to get NTLM authentication information with PHP. If you’re using mod_auth_sspi, then the above script is not neccessary. From what you’ve written, it sounds like a configuration issue with mod_auth_sspi. It’s probably best to post at the mod_auth_sspi mailing list and see if the devs there can help you.
This is a fantastic script, works like a treat but I’m experiencing the same issues as others. I’ve authenticated the user on intranet home page load and then stored what I need in a session. However as others have reported it stops forms from processing.
I have checked the post array and it’s empty when using this. Is there any answer of how to fix this?
Hi guys, after wasting half a day debugging my code,; the issue seems to be with ie. Ntlm authentication is required on every page that is posting data across. Which means that if one page has been authenticated using this script all subsequent pages need to run this script in order post any information across; which is why all posts on ie are returning empty and if you try the same on firefox would work fine.
Haven’t found a solution yet, but will not POST on ie unless all pages have this script;
Let me know if anyones found a soulution for this.
There are a few of you with the issue of needing to send this data via a form or other post methods. To do this with other applications I have always found it easiest to either use the PHP that generates the page to pass up the variables – $user $domain $workstation in this case – as either JavaScript variables when it writes the script or if all the scripts are includes then hiding it in a hidden form field in the html. If you need it for multiple forms/scripts you can also just enclose it in a echo “$user”; then to retrieve it just use a getElementById(‘#data_user’).innerHTML to return what is in the div element and pass it with the rest of the data in your AJAX (JavaScript w/o reload) call, POST (header), or GET (URL) request.
Thanks,
Great help, was looking for this.
Really appreciable.
Thanks again
Thanks,
Great Help.
Was looking for this for very long time.
Really appreciable.
Thanks again
Hi,
When I use this code for an intranet site, it works for most of the cases (auto retrieves the username), but for some user (possibly due to version of IE & OS) it asks for a username and pass.
Is there any way to check when this code asks for the username & pass. I tried this,
if(strlen(get_msg_str($msg, 36))==0)
but of no work.
Please help, my life is gonna hell.
Hi, I’m trying to make it work on PHP on IIS on an 2003 server. When I use IE9 on win7 it gives backup a status 401 (checked responseheaders in developertools and it gives a “www-authenticate NTML blablalba” response so it seems like IE doesn’t do it’s trick) and just stops. Site is in intranet zone.
IE: 9
OS: win 7 64bit
IIS: 6.0 on server 2003 (anonymous auth enabled)
domain: intranet..local
site: backupstatus.server.intranet..local
When I use firefox it prompts for credentials and when I fill in correct ones it displays username and password.
To all who has the problem with the empty $_POST variable in the Internet Explorer (IE), you need to disable the NTLMPreAuth (DisableNTLMPreAuth) in the registry on each clientcomputer.
—
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings]
“DisableNTLMPreAuth”=dword:00000001
—
See http://support.microsoft.com/kb/251404 or google ;-)
hey,
thank you so much for this script. i’m currently trying to understand exactly what it is doing, but a quick test looks very promising!
Pingback: php, retrieve the password of the current user : Unlimitedtricks Programming Blog, Tutorials, jQuery, Ajax, PHP, MySQL and Demos
Pingback: php, retrieve the password of the current user closed | Code and Programming
My two cents. Got it ported to Java.
I used this from an app inside the DMZ to identify (not authenticate) users from within the corporate network. Our app in the DMZ cannot see the Active Directory.
User has to login again in the app, but he is forced to use the same username as the windows one.
now the code:
package servlets;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
public class SsoTest extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
//System.out.println(“\n\n————————————- START REQUEST ——————————-“);
Enumeration names = req.getHeaderNames();
boolean authHeaderSet = false;
String authString = “”;
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
Enumeration values = req.getHeaders(name); // support multiple values
if (values != null) {
while (values.hasMoreElements()) {
String value = (String) values.nextElement();
if (name.toLowerCase().equals(“authorization”)) {
authHeaderSet = true;
authString = value;
}
}
}
}
if (!authHeaderSet) {
// Not authorized
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setHeader(“WWW-Authenticate”, “NTLM”);
} else {
byte[] msg = Base64.decodeBase64(authString.substring(5));
if (msg[8] == 0x01) {
// Send challenge
byte[] msg2 = { 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, // NTLMSSP
0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // target name len/alloc
0x00, 0x00, 0x00, 0x00, // target name offset
0x01, 0x02, (byte) 0x81, 0x00, // flags
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // challenge
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // context
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // target
// info
// len/alloc/offset
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setHeader(“WWW-Authenticate”, “NTLM ” + (Base64.encodeBase64String(msg2)).trim());
} else if (msg[8] == 0x03) {
// Challenge accepted
setVars(msg);
} else {
// Not authorized
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
private void setVars(byte[] msg) {
String user = getMsgString(msg, 36);
System.out.println(“USER:”+user);
String domain = getMsgString(msg, 28);
System.out.println(“domain:”+domain);
String workstation = getMsgString(msg, 44);
System.out.println(“workstation:”+workstation);
}
private String getMsgString(byte [] msg, int start) {
boolean unicode = true;
int len = ((int)msg[start+1] * 256 ) + (int)msg[start];
int off = ((int)msg[start+5] * 256 ) + (int)msg[start+4];
String value = new String (msg);
value = value.substring(off,off+len);
if (unicode) {
value = value.replace((char)0x00, (char)0x20);
value = value.replaceAll(” “, “”);
}
return value;
}
}
If you are encountering problems with Internet Explorer not working but Firefox ist working check the HTTPD “KeepAlive” Setting which has to be “On”. This is default but on our CentOS it was Off.
Pingback: Symfony2: automatically logging in users from their Windows session | BlogoSfera
this post by struppi really helped , as we enabled KeepAlive and it works fine – thanks
How we can retrieve the user group??
how we can get current user group??
Pingback: How do I use Microsoft AD and php single sign on web app? | Ask Programming & Technology
this code working for me in windows wamp server. but when i uploaded the code into Linux server it gives authentication pop up box. i want to get windows username only with out asking authentication in Linux..do you guys have any idea?
Pingback: Can a PHP intranet share Windows logins? | Yray Answers
Hey It helped!!!Thanks
The script works great except one problem: if I don’t configure Mozilla, a window is prompted asking for the user credentials. How can I avoid the window? If the browser is not configured, I want the user to be redirected to a page where he can authenticate manually, and not by using te pop-up window.
Can you help me? Thank you in advance.
Another problem: if I type in the prompted window the wrong credentials, it still authenticates the user. How can I fix this?