In the circumstance of an http dealings, the staple accession hallmark is a method acting contrive to leave a web browser, or other guest syllabus, to furnish credential – in the form of a user name and parole – when do a call for.
Before transmittal, the user name is tack with a costa rican colon and concatenated with the password. The leave draw is encoded with the base64 algorithm. For lesson, apt the user name ‘aladdin ‘ and password ‘open sesamum indicum ‘, the draw ‘aladdin : open sesamum indicum ‘ is base64 encoded, leave in ‘qwxhzgrpbjpvcgvuihnlc2ftzq = = ‘. The base64-encoded draw is transfer and decrypt by the recipient, leave in the colon-separated user name and password draw.
While encode the user name and parole with the base64 algorithmic rule typically throw them unreadable by the defenseless eye, they are as easily decrypt as they are encoded. Protection is not the aim of the encode step. Kinda, the aim of the encode is to encode non-http-compatible fiber that may be in the user name or parole into those that are http-compatible.
The basic get at hallmark was earlier determine by rfc 1945 ( hypertext transmit protocol – http/1. 0 ) although further data view security measures release may be get hold in rfc 2616 ( hypertext transmit protocol – http/1. 1 ) and rfc 2617 ( http hallmark : basic and tolerate get at hallmark ).
Apache REST based authentication
* Summary
* Authentication is hard
* Common authentication issues
* Optional authentication
* User should be able to log off
* Required authentication
* Automatic log out after a period of inactivity
* Personalised login page
* Forgot password page
* Caching issues
* Conclusion
* Appendix: my .htaccess file
* Appendix: my configuration
Summary
Distinguishing between requests from authenticated and non-authenticated users is possible by relying upon the standard feature of browsers that they include the Authentication HTTP header even for unprotected portions of the site.
Portions of the site that really require authentication will work correctly, because the Authentication header is included. Portions of the site that do not need authentication, can use the name of the authenticated user mentioned in the HTTP authentication header to do personalization.
This page shows how to achieve these effects with all the common browsers and almost pure Apache 2.2 (it probably works with the 1.3 series as well).
Authentication is hard
Authentication is one of the toilsome issue when modernize software. Because if you got even one bit wrong, your solvent is no long secure. And your reputation may go down with it. So why do web developers insist on modernize their own surety? why not use http authentication which is in all probability far more unattackable than most computer programmer will ever be able to uprise themselves?
Common authentication issues
When people talk about user authentication with a web browser, the following topics tend to come up:
1. Authentication should be optional: unauthenticated users should be able to use (part of) the site. Authentication gives access to additional features.
2. Login screen should be customizable.
3. User should be able to logout.
4. User should be logged out automatically after a certain time period.
It is broadly trust that biscuit are requisite to financial backing these features. Restafarians nevertheless like to change lite incandescent lamp without biscuit, so are we bind? we can result that question with a firm denial. As far as i know, jean-michel hiver was the showtime to show an choice technique that bank only on http hallmark and does n’t use cookies. As the code he face is more or less superannuated ( it does n’t work with the previous apache and mod_perl faculty for model ), is not consummate, and he does n’t intelligibly posit why his go up make, i have also fetch his code up-to-date. The existent resolution prefer by jean-michel ‘s accompany make fine nevertheless, you can see it in activity at mkdoc. I also improve upon his log off functionality. His resolution does not work with internet explorer and opera, but i detect some technique that give satisfactory results. .
The solution stage here use only vodoun, so ritual killing your crybaby and let ‘s go! Optional hallmark
Use case : if the user is documented, showing a personalized recognise message or show a personalized interpretation of the site. If the user is not documented, showing a login riddle or generic wine interpretation of the site.
The result to this trouble is to let the browser admit the assay-mark http heading, even when the site does n’t postulate authentication. Because the site does n’t need assay-mark, anon. Admission is calm down allowed. But if the user has documented himself, we can use this heading to individualise the site.
Let’s first have a look at the Authentication header. It looks something like this:
Authorization: Digest username=”myname”, realm=”My Site”,
nonce=”uJPKOnEOBAA=7c7d02a5ed5e214c7bb21f28bdc577f422ca0b47″,
uri=”/index.html”, algorithm=MD5,
response=”adefccac270ee8110639460a0fb90f4c”,
qop=auth, nc=00000004, cnonce=”c87ceaa8c4e9d15c”
It’s the username parameter in which we are interested. But how can we trick a browser to send us this header? Let’s first create the page that anyone who isn’t logged on will see. The body is something like this:
<body>
<p><a href=".login">Please login</a> to use this site.</p>
</body>
(By the way, a full demo of the code in this document is available under a subdirectory of this page, just follow this link to see the login page. Username is “myname”, password is “test”)
Before looking at how login works, you will need to make sure Apache is setup correctly. The most important setting is that a .htaccess file can override any security setting. Your httpd.conf should therefore include the following settings:
AllowOverride All
Options FollowSymLinks
in its VirtualHost or Directory directive.
The login link points to a URL which we will catch in .htaccess. That is easier than the approach chosen by Jean-Michel which requires some Perl hacking.
The .login entry in our .htaccess is this:
<Files .login>
AuthType Digest
AuthName "My Site"
AuthUserFile /var/www/html/rest/login/passwd
Require valid-user
</Files>
So there is no .login file, it’s just a URL that will trigger authentication because accessing this URL requires a user to be authenticated.
Authtype is set to digest. I opt to use digest hallmark because it work out some authoritative security system fear equate to the staple place setting used by jean-michel. It will not come as a surprisal that the with child package trafficker on this planet has turn some other, of package that command, due to the curve food market presence of that trafficker, everyone else in the cosmos to work some a bugs it bear when utilize digest authentication. Make utterly sure you have this directive in your form or .htaccess file:
BrowserMatch “MSIE” AuthDigestEnableQueryStringHack=On
Note that if you use SSL, basic authentication is as secure as the digest variant, because the password is now impervious to sniffing as well.
The AuthName directive is used to present the site name to the user for which he has to specify a user name and password. The password file itself is in the passwd file. You add users this file with the htdigest utility like this:
htdigest -c passwd “My Site” myname
It subsequently asks for a password for the user “myname”. The passwords you create are stored in a plain text file. To achieve scalability above more than a few dozen users, Apache also supports storing users and their passwords in a DBM file or in a relational database.
The statement Require valid-user is necessary in order for Apache to send the WWW-Authenticate header to the browser. It indicates that this file or directory is protected. As soon as any user tries to access this URL, he or she will need to provide a user name and password. In our case the user name is “myname” and the password is “test”.
At this point in time it is significant to point in time out that the login url must point in time to something that is in or downstairs every directory that neediness to net from optional authentication. For exemplar if the the login was a directory like /login the user would be documented, but the assay-mark head would never be send if the user went to /other/part. At least not with firefox 1. 5. 0. 1. You would imagine that bring urls to the authdigestdomain localise would do the magic trick, but that localise does n’t seem to be widely substantiate right-hand now.
Assuming that the Authentication header is present, is not a trick that just happens to work due to some browser quirks. RFC 2617 states:
The Authorization header may be included preemptively; doing so improves server efficiency and avoids extra round trips for authentication challenges.
This advise is probably followed by all decent browsers. It is at least by the browsers I’ve tested this approach with.
Careful readers might have seen that AuthDigestDomain was not present in the .login. As mentioned support for AuthDigestDomain seems to be lacking in browsers, but we can rely on particular feature when it is not present. As RFC 2617 states:
If this directive is omitted or its value is empty, the client should
assume that the protection space consists of all URIs on the
responding server.
Exactly the feature we’re looking for when basing personalisation on HTTP authentication!
Ok, that ‘s adequate approximately apache ‘s http authentication place setting for now. Back to the browser which just has toss off up a login duologue box if you follow the. Login url. If you allow for the castigate user name and watchword, apache will next try to send you the. Login file. But that would be kind of pointless. Later a successful login the user should be redirect to the home page and this should now indicate that the user has log in. We can do the redirect from inside the. Login segment in the. Htaccess by tote up these statement to it :
<Files .login>
…
RewriteEngine on
RewriteCond %{REMOTE_USER} !=""
RewriteRule ^.*$ /index.html [R]
</Files>
In the first line we turn on Apache’s rewrite engine. The next statement is a conditional: if the REMOTE_USER environment variable is set, execute the following rewriting rule. REMOTE_USER is set if a browser has sent an Authenticate header and if Apache has successfully authenticated the user. The rewrite rule simply directs the user to the site’s home page.
Although the user is now redirected to the home page, the default page (see above), index.html, is still served saying the the user needs to login. But when the user has been authenticated, we want to serve a different page. This can be solved either dynamically or statically. In your scripts you could emit different HTML when the REMOTE_USER environment variable is set. In our example we simply serve a different page, in this case one that let’s a user logout.
Serving a different page if a user has been authenticated sounds familiar. And indeed, just like with login’s .htaccess section we can accomplish this with another rewrite:
RewriteEngine on
RewriteCond %{REMOTE_USER} !=""
RewriteRule ^index.html$ authenticated.html
Again we turn the rewriting engine on and check if REMOTE_USER has been set. If so, we serve authenticated.html instead of index.html.
However, if you try this, you will notice that this does not work, even when you were just authenticated!. The reason is that the environment variable REMOTE_USER does not have a value. Apache sets this only when these three conditions are met:
1. The browser sends the Authenticate header.
2. Apache is asked to validate the user. For example the .htaccess file contains the Require valid-user directive.
3. Validation succeeds.
The browser sends the Authenticate header, but Apache doesn’t have to validate the user, so it does not set REMOTE_USER. So we’re stuck or what? One solution is to make sure REMOTE_USER is indeed set. That’s a solution that I explore in the alternative section. But there’s even an easier solution, one that doesn’t require any mod_perl skills at all! Just vodoo…
We know that the Authorization HTTP header is send, so why not use that? That leads to the following solution that nicely redirects a previously authenticated user to a personalised page:
RewriteEngine on
RewriteCond %{HTTP:Authorization} username=\”([^\"]+)\”
RewriteRule ^index.html$ authenticated.html [L]
If you try this, you will be properly redirected to the authenticated.html page. Where you can logout if you so wish. And that is the subject of the next section.
There are some other solutions as well that make the authenticated user available, see the alternative solutions page.
User should be able to log off
Use case : user has log on to our lotion from a world terminal. Before go away the world pole, the user deprivation to log off the school term so a subsequent user can not keep on his school term.
In parliamentary law to see the solution let ‘s beginning have a look at what is hap afterwards we have authenticated. Afterwards we have attested, the browser has store the supply user name and password. It necessitate to storage these prize so it can use them when the supply nonce economic value has become stale for example. This is no dissimilar from any other authentication scheme. The customer necessitate to storage some kind of ticket that can be used for a sure amount of money of time or automatically reincarnate if the customer stay on participating.
The aim of a log off is to make a browser block the furnish user name and password. If the browser does not block the user name and parole, the browser will, be able to re-authenticate. Bank upon http authentication is the most unattackable way of accomplish this. At that place are many more eyes view http authentication yield than the common roll-your-own biscuit establish authentication connive.
Unfortunately, no browser does offer an out-of-the-box logout feature for their HTTP authentication. This is a lamentable failure in their implementation and unfortunately not something the poor web developer can solve. For certain browsers, such as FireFox, there is an extension that adds logout. But most FireFox users will not have this extension installed.
It is therefore commonly thought, see for example HowToLogOff and HTTP Authentication and Forms, that it is not possible to allow a user to log off his HTTP authenticated session.
However this is not true. You can ‘trick’ a browser to forget the credentials. Unfortunately the solution is browser dependent. The approach that should work is forcing a user to re-authenticate by sending by sending a 401 response to a request. This should popup the login dialog box. If the user presses cancel here, the browser should discontinue sending the Authenticate header to the server. This technique works for Mozilla/FireFox and Opera (with a twist), but not for Internet Explorer.
We can force a re-authenticate by sending the browser to a URL called .logout for example. This resource will always return the 401 response, thereby forcing the user to press the Cancel button after which the credentials will be forgotten. And the “Authorization Required” page will be displayed. Jean-Michel observes: “However that’s very ugly.” But that can be fixed as well.
But let’s see how such a thing can be done with the usual .htaccess trickery. First a portion of the home page that is shown when a user has been authenticated:
<p>You're authenticated. But you may <a href=".logout">logout</a>.</p>
What happens when the browser tries to access .logout? We require authentication when something tries to access .logout, but we do two things differently:
1. We specify a user name that does not exist. So a user cannot authenticate and we respond to the browser’s request with 401 Authentication Required.
2. We specify an authentication realm that is either equal or different from our the authentication real used with .login. We need an equal realm for Mozilla/FireFox to forget the authentication credentials, but we need a different realm to trick Opera unfortunately.
3. We do not mention AuthDigestDomain so the change should apply to all URLs on our server.
Assuming our browser is FireFox, .logout can be implemented as below. This will not work for Internet Explorer, it will give a weird error message, it will not even popup the login dialog box. Opera will simply not forget the credentials if you press cancel in the login dialog box. But trusted FireFox works fine:
<Files .logout>
AuthType Digest
AuthName "My Site"
AuthUserFile /var/www/html/rest/login/passwd
Require user nonexistent
</Files>
But it is indeed not friendly at all. Popping up a dialog box is OK. It helps the user to understand that he is really logged off and can walk away from the public terminal now, because the next user will have to type in the credentials or press cancel. But what if our user just wants to log on again with either the same or different credentials? That should be supported nicely as well.
So a logout can be either of two things: logout, but also logging again. Logout and login are actually the same thing! Once this concept is grasped, the solution falls into place quite nicely:
1. The logout link is the same as the login link.
2. We add a special query string to it so login can deny access on the first attempt, forcing the browser to display the login dialog box.
The “first attempt” is the issue here. It sounds like we need to store state on the server. That’s always a big no-no as things start to get complicated. It’s much better if the client can store the state. But how to do that? If we let the browser ask for a URL of the form logout?first_attempt=true and based on the presence of first_attempt always return 401 Authorization required to the browser, the user will never be able to authenticate. The browser will simply popup a box asking for the proper user name and password and submit this again and again to our URL, i.e. logout?first_attempt=true. So how do we get around this and avoid storing state on the server?
And here Jean-Michel had his second brilliant idea: use a time stamp. If the time stamp is less than a few seconds ago we assume it’s the user’s first attempt, and he force him to logout. If the time stamp is past the current time, we assume he typed in his password and wants to login again.
We can solve this problem with almost pure voodoo (I’ve written the non .htaccess parts in Perl, so it keeps looking like voodoo :-) ). First the logout. When the user follows the .logout link we redirect him to login, but include the time stamp. Have this handler in your .htaccess file:
<Files .logout>
RewriteEngine on
RewriteRule ^.*$ /.login?logout=${timestamps:0} [R,L]
</Files>
The redirect to login now includes a parameter. It is important that this be an external redirect. The browser must get this exact time stamp, because that is where the state is stored!
The actual time stamp value is retrieved from a rewrite map. A rewrite map is something that accepts a key and returns a string. It can be a hashed file. But in our case it is a simple program that returns the current UNIX time (seconds since the Unix Epoch) + 7 seconds. That will give the browser 7 seconds to receive the redirect, send it to the server again, which will respond by showing the login dialog box to the user.
The time stamp returning program can be written in any language, here is the Perl variant:
#!/usr/bin/perl
$| = 1;
while (<STDIN>) {
print time() + 7, "\n";
}
The program simply reads a line from standard input and writes the time stamp to standard output. The input is not important, anything will do. The resulting redirect URL is of the format .login?logout=1141933263.
Installing rewrite maps that are programs takes some care. As they are started when Apache is started they need to be in your server config or VirtualHost directive. And make sure the RewriteEngine is turned on, else you will get the error message “map lookup FAILED” when you use the map.
RewriteEngine On
RewriteMap timestamps prg:/var/www/perl/timestamps.pl
The .login link needs to handle the case where the logout query is present, so we add these rewrite rules before any other rules:
RewriteEngine on
RewriteCond %{QUERY_STRING} ^logout=([0-9]+)$
RewriteRule ^.*$ /${optional-forced-logout:%1} [L]
What’s happening here? First we have a conditional on the presence of the logout parameter. Due to the presence of parentheses in the regular expression, the value of the time stamp is stored in the %1 variable. We pass the time stamp to another rewrite map. Again it is a simple program that accepts the time stamp on stdin and decides if the time stamp is new enough to force a login or not.
The output of the optional-forced-logout rewrite map is one of these two values:
1. .force_logout_offer_login
2. .dologin
Depending on the output of the rewrite map we redirect the user to one of these URLs. The rewrite map program can be written in any language as it is extremely simple, but the Perl version is this:
#!/usr/bin/perl
$| = 1;
while (<STDIN>) {
my $timestamp = $_;
if ($timestamp > time()) {
print ".force_logout_offer_login\n";
}
else {
print ".dologin\n";
}
}
It keeps on reading standard input and for every received time stamp it emits the proper redirection URL. You have to add this rewrite map to your VirtualHost or Directory directive as follows:
RewriteEngine On
RewriteMap optional-forced-logout prg:/var/www/perl/optional-forced-logout.pl
So what happens when the .optional-forced-logout is received? It is treated as an internal redirect, the browser isn’t aware of it, an important point. Actually, we need three redirects, depending on the browser. Remember that the authentication realm must be the same for Mozilla/FireFox, but different for Opera, and for Internet Explorer we must do something different altogether. So the rewrite engine routes us to one of these three URLs:
1. .force_logout_offer_login_mozilla, or
2. .force_logout_offer_login_opera, or
3. .force_logout_offer_login_ie.
Each does the actual logoff. The redirect voodoo looks like this:
<Files .force_logout_offer_login>
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (MSIE)
RewriteRule ^.*$ /rest/tada/.force_logout_offer_login_ie [L]
RewriteCond %{HTTP_USER_AGENT} (Opera)
RewriteRule ^.*$ /rest/tada/.force_logout_offer_login_opera [L]
RewriteRule ^.*$ /.force_logout_offer_login_mozilla [L]
</Files>
<Files .force_logout_offer_login_mozilla>
AuthType Digest
AuthName "My Site"
AuthUserFile /var/www/html/rest/login/passwd
Require user nonexistent
</Files><Files .force_logout_offer_login_opera>
AuthType Digest
AuthName "Not My Site"
AuthUserFile /var/www/html/rest/login/passwd
Require user nonexistent
</Files><Files .force_logout_offer_login_ie>
RewriteEngine on
RewriteRule ^.*$ /rest/tada/logged_out.html? [R]
</Files>
The force log off resource simply requires a user name that does not exist in the password file. So the server responds to the browser that authentication has failed. The browser shows the login dialog box. Let’s assume that the user types in his credentials again. If he presses OK, the browser again hits the .login?logout=123456789 URL. Let us further assume that 7 seconds have gone by. The optional-forced-logout rewrite map (see above) returns just .dologin. This is an internal redirect to:
<Files .dologin>
AuthType Digest
AuthName "My Site"
AuthUserFile /var/www/html/rest/login/passwd
AuthDigestDomain /
Require valid-user
# If user is authenticated, redirect to main page
RewriteEngine on
RewriteCond %{REMOTE_USER} !=”"
RewriteRule ^.*$ /index.html? [R]
By the way, .dologin is similar to the first version of .login we presented. The browser has sent us the credentials again and assuming the user has typed them in correctly, the user will be validated and redirected to our home page.
This approach works brilliantly with Mozilla/FireFox. If you find the dialog box that it displays less than brilliant, there’s even a solution for that. With Opera we have a minor issue: because we have changed the authentication realm, Opera will ask for a password for that realm. If the user presses cancel, everything is fine and Opera will have forgotten the password. However, if the user enters the password, he will not be authenticated because the realm is “Not My Site”, but the user is directed to a URL that requires a password for “My Site”. So a 401 is returned, and Opera will popup another login dialog box. User enters the correct password, this time for “My Site” Validation now proceeds correctly.
As you might have seen above, in case the browser is Internet Explorer, we redirect the browser straight to logged_out.html. The problem with Internet Explorer is that forcing re-authentication doesn’t work: if you try it for the same authentication realm, this browser will come up with a weird error message. If you try a different authentication realm, it will not forget the credentials. So that’s why we redirect to logged_out.html. The purpose of this page will be discussed below. This page contains some JavaScript trickerly that will force Internet Explorer to forget its credentials. Unfortunately this trick only works for version 6 SP1 or higher. When this page is loaded, we execute this JavaScript and now even Internet Explorer has logged off:
<script language="javascript" type="text/javascript">
var agt=navigator.userAgent.toLowerCase();
if (agt.indexOf("msie") != -1) {
document.execCommand("ClearAuthenticationCache");
}
</script>
Note that this log off solution works against three common browsers. Jean-Michel’s logout does not work for Internet Explorer and Opera. If you try this out on the live MKDoc site, it says you’re logged off, but you are not actually. I’ve not tested it myself, but others have told me that this solution also works on Safari 2.0.4 (419.3).
There are two remaining issues to be discussed. What happens when the user types in his user name and password within 7 seconds, i.e. within our time stamp period? In that case access will still be denied. The user will probably think he mistyped his password. It’s not nice, but not a very likely scenario either. If all users to your site are on a fast connection, you could lower the 7 second timeout value to further minimise occurrence of this case.
The second issue is that after the users logs out by pressing the cancel button, he gets an ugly “Authentication Required” page. How that is solved, is discussed below.
Required authentication
Pickup up a user’s name without real authentication raises security concerns. It is trivial to fake HTTP headers. When this approach is followed, care should be taken to protect sensitive URLs with the proper AuthXXXX directives. That should be done anyway, so this approach won’t make a site less secure. But for the unprotected data a designer should always keep in mind that although we have a user name, it might not be the actual user.
Automatic log out after a period of inactivity
Use case: if the user has not accessed the site, upon the next request the user will be asked to re-enter his or her credentials.
Banks seem to like this approach a lot. If you do not use the site for a while, they force you to log on again. I won’t be showing any code how to solve this issue, but I will just be sketching how it can be done.
This is a case where the server needs to keep state unfortunately. Client state isn’t reliable as it can be faked. Because the user has been authenticated (else he or she is by definition not logged in), we have a proper user name. For every access that a user does we update the last access time for that user in a database. Apache supports checking user credentials against a database, so there’s no need to duplicate user information.
When we receive an access from the user that is past a certain number of seconds compared to his last access, we can force re-authentication. There are two approaches:
1. The simplest is to send an external redirect to browser to the .logout URL. But that requires the user to find his way back, very annoying.
2. It would be much nicer to allow the user to proceed with the original request, after he has been re-authenticated again.
I haven’t actually developed any code for the second approach, but I believe this can be done if we realise that the user’s name and password hasn’t changed, but what has changed is the authentication realm. The authentication realm has expired and is no longer valid. The user needs to supply the username and password for the new realm. Here’s how this could be done:
1. Update the last access time of the user.
2. Force an internal redirect to something like the .force_logout_offer_login URL. It’s important that this URL has the new authentication realm. The new authentication realm is user dependent, so this needs to be stored in the database. Because the realm has changed, the server will send a 401 to the browser.
3. The browser will popup the login dialog box. The user must now authenticate or press cancel.
4. If the user authenticates again, the browser will retry the original request. Because the last access time has been updated, the request is considered valid. Because the resource is protected by authentication, authentication will be done and succeed if the user has supplied the correct credentials.
5. If the user presses cancel, he will be properly logged off in all browers, because even if the browser continues to remember the credentials, the authentication realm has expired so the user must login before being able to proceed to secured pages.
Apache’s mod_authn_dbd seems to be very suited to do this as it allows you to lookup the username and realm in a database.
Personalised login page
Use case: when the user logs on, do not force the browser to present the default login, but present a nicely formatted page where the user can enter his credentials.
With the help of AJAX pure, HTTP authentication will even support this! The steps are simple:
1. Put a user name and password input box on the form.
2. Put a “Login” button on the form. On click it should not post the data, but call a JavaScript function.
3. The JavaScript function asks the server if the user name and password are correct.
4. If so, the function access our original .login URL using the XmlHttpRequest object. This object allows you to pass the user name and password (Note: Opera currently does not support this). Because the user name and password are correct, the browser will not popup a dialog box. If they are incorrect, the browser will do this, even for an asynchronous call! (Note: Opera will not popup a dialog box, just return a 401 status code!)
5. Because the server responds that authentication has been successful, the browser will now continue to send the Authentication HTTP header to every URL to the directory of the current URL and all its subdirectories, as discussed before.
Problem solved. Or nearly. The big issue is item 3. If you send your user name and password as clear text over the internet, you defeat the entire purpose of using HTTP authentication. To solve this issue properly you need to implement an entire Challenge-Response scheme. This is nicely explained by Eric Lippert in a four part series Eric. This is the non-trivial part and is therefore left as an exercise to the reader. But let’s look at a few other parts of the code. First the onclick action of the button:
login(
document.getElementById('username').value,
document.getElementById('password').value)
On click the button calls a function called login(), and passes in the user name and password. The login() function is this:
function login (username, password) {
request.open("GET", ".login", true, username, password);
request.onreadystatechange = onLogin;
request.send(null);
}
The request object is an instance of XmlHttpRequest. This asynchronous call simply calls .login with the proper user name and password. If you want to check the user name and password before making this call, you have to implement step 3 with all the responsibilities to get it correct.
The callback function is onLogin. This checks if we have received a 200 response and in that case redirects us to the home page:
function onLogin () {
if (request.readyState == 4) {
if (request.status == 200) {
window.location="index.html";
}
}
}
Which should indicate that we have logged on, because in our .htaccess we redirect to authenticated.html in that case. Note that you should have FireBug disabled, else you still will get the standard login dialog box!
I have ambivalent feelings about custom login screens. Not only are they hard to get secure as discussed before, but how careful is the browser with storing this user name and password? Does it get stored in the cache? In unprotected areas of the computers memory? The browser doesn’t know this data is sensitive. I have the feeling that the browser will be more careful with the user name and password supplied by HTTP authentication than with two input fields on a page.
And lastly: it is probably very hard for a rogue application or third party to fake the default HTTP authentication login screen. This should be impossible really. That way a user can be sure he is using trusted authentication with proper security controls. Faking a custom login screen is much easier.
Forgot password page
Use case: if a user can no longer remember his password and gives up authentication by pressing the cancel button in the authentication dialog box, display a page where the user can request a new password.
This use case is just one of all the essential use cases a well-behaving HTTP-authentication application will have to cover. Because when a user presses the cancel button, the application knows the user cancelled the login, but not why. Didn’t the user remember his password anymore? Did the user want to logout? Or had the user more pressing issues right now, but wants to login at a later time?
So after pressing the cancel button we have to display a page that allows a user to do the following:
1. Allow the user to log on again, perhaps under a different identity.
2. Let the user request a new password.
In addition this page should confirm the user’s current status, i.e. that the user is logged off. Perhaps reiterating the point, after authentication only two things can happen:
1. The user supplies the correct user name and password. In that case the user will be given access to the authorised page.
2. The user gives up the authentication process by pressing cancel. In that case the error page is displayed.
The browser will never give up authentication until one of these two things have happened. When HTTP authentication fails, Apache serves the error document for the 401 status. This can be overridden in your .htaccess with the ErrorDocument directive:
ErrorDocument 401 /logged_out.html
The URL must start with a slash, else Apache thinks it is just an error message. The error document must contain the links to allow a user to login again or request a new password.
Caching issues
Because of all the URL rewriting and serving of different content when a user is authenticated, you must make sure caching doesn’t get in the way. For this example it is enough to disable sending any expiry information and disabling any intermediate caches. Put these lines in your .htaccess to make it all work:
ExpiresActive Off
Header append Cache-Control “no-cache”
ExpiresActive only needs to be set to Off when it has been set to On and when the Expires module is loaded of course. The “no-cache”
For a real site you probably want to be very cache friendly, but while testing you want to avoid tracking errors that are just due to you being send a non-updated page.
Conclusion
Cookie-less authentication is quite possible while retaining all the features that are usually associated with choosing cookie-based authentication in the first place.
I appreciate comments and questions about this solution. And because I don’t have access to Safari I like to get a note from those users if this approach works for their browser as well. And I appreciate it even more if you let me know if you think I have made a fatal flaw in my reasoning or code.
Appendix: my .htaccess file
The full .htaccess file I used in my examples:
# Turn off expires in case mod_expires has been loaded and it has been
# activated.
ExpiresActive Off
# And disable caching, always validate against server.
Header append Cache-Control “no-cache”
# Support the biggest software vendor on the planet which produces
# the biggest load of crap
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On
ErrorDocument 401 /logged_out.html
# Only when user tries to access .login, ask user for authentication
<Files .login>
# If login is actually a logout, force logout and offer login
# but only when the timestamp is within range.
RewriteEngine on
RewriteCond %{QUERY_STRING} ^logout=([0-9]+)$
RewriteRule ^.*$ /${optional-forced-logout:%1} [L]
AuthType Digest
AuthName "My Site"
AuthUserFile /var/www/passwords/passwd.digest
Require valid-user
# If user is already authenticated, redirect to main page
RewriteEngine on
RewriteCond %{REMOTE_USER} !=""
RewriteRule ^.*$ /index.html? [R]
</Files># We come here if a timestamp is present, but has expired
<Files .dologin>
AuthType Digest
AuthName "My Site"
AuthUserFile /var/www/passwords/passwd.digest
Require valid-user# If user is authenticated, redirect to main page
RewriteEngine on
RewriteCond %{REMOTE_USER} !=""
RewriteRule ^.*$ /index.html? [R]
</Files># Force a logout by forcing a re-authenticate which always fails
# We get send here by a .login when the timestamp has not expired.
<Files .force_logout_offer_login>
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (MSIE)
RewriteRule ^.*$ /.force_logout_offer_login_ie [L]
RewriteCond %{HTTP_USER_AGENT} (Opera)
RewriteRule ^.*$ /.force_logout_offer_login_opera [L]
RewriteRule ^.*$ /.force_logout_offer_login_mozilla [L]
</Files><Files .force_logout_offer_login_mozilla>
AuthType Digest
AuthName "My Site"
AuthUserFile /var/www/passwords/passwd.digest
Require user nonexistent
</Files><Files .force_logout_offer_login_opera>
AuthType Digest
AuthName "Not My Site"
AuthUserFile /var/www/passwords/passwd.digest
Require user nonexistent
</Files><Files .force_logout_offer_login_ie>
RewriteEngine on
RewriteRule ^.*$ /logged_out.html? [R]
</Files># Redirect user to .login but include a timestamp
# Must be an external redirect, else we come here again and again even
# if user supplies the correct password.
<Files .logout>
RewriteEngine on
RewriteRule ^.*$ /.login?logout=${timestamps:0|1} [R,L]
</Files># if user name known, show authenticated page
# We have several solutions each differing in complexity.
RewriteEngine on
#RewriteCond %{REMOTE_USER} !=""
#RewriteRule ^index.html$ authenticated.html [L]
RewriteCond %{HTTP:Authorization} username=\"([^\"]+)\"
RewriteRule ^index.html$ authenticated.html [L]
#RewriteRule ^index.html$ authenticated.html?user=%1 [L]
#RewriteRule ^index.html$ %1.html [L]
Appendix: my configuration
I’ve tested my sample code against the following browsers:
1. FireFox 1.5.0.1: full support.
2. Microsoft Internet Explorer 6.0.2800.1106 (version 6 SP1): full support (don’t forget to turn on the workarounds in Apache to work around IE’s Digest authentication bug).
3. Mozilla 1.7.11: full support.
4. Opera 8.52: custom logon does not work.
Apache 2.2 was built with:
./
configure
–prefix=/usr
–bindir=/usr/sbin
–sbindir=/usr/sbin
–localstatedir=/var/run/httpd
–enable-module=so
–sysconfdir=/etc/httpd/
–enable-rewrite
–enable-expires
–enable-headers
–enable-include
–enable-deflate
–enable-disk-cache
–enable-cache
–enable-auth-digest
–enable-authn-dbm
–enable-authz-dbm
–enable-ssl
–with-ssl=/usr/local/ssl
–enable-cgi –enable-headers
–enable-vhost-alias
–enable-log-forensic
I used Perl 5.6.1 for the RewriteMap examples I presented.
In alternative solutions I have used mod_perl 2.0.