This chapter draws on knowledge gained from previous chapters of CGI Programming 101, including file I/O (chapter 6), data validation with regular expressions (chapter 14), encryption (chapter 10) and database interaction (chapter 18). You'll need some experience with these advanced topics to work through some of the examples in this chapter.


Copyright © 2000 by Jacqueline D. Hamilton.

User Authentication

One important feature for web sites is the ability to restrict access to part or all of the site. This is often used on subscription sites, such as online webzines or other members-only services. It's also used on administrative portions of web sites, as well as on secure sites for online banking and stock trading. Some online catalogs even require you to register with a username and password before you can order from them (a trend I don't recommend - if you want people to buy from you, you should make it as easy as possible, rather than force them to remember yet another username/password).

There are two kinds of user authentication. One is HTTP authentication; this is actually done by the web server itself. You create a .htaccess file (containing authentication instructions) in the web directory that you want to protect. Then whenever someone tries to access a web page in that directory, their browser presents a pop-up box that asks for their username and password:

The other kind of authentication is done by forms and CGIs, and often uses cookies to track user sessions. This is a much harder method to implement, as it requires you to add code to every page you want protected - and in fact, it will not protect basic HTML files or images at all, so you'd likely have to create CGIs to serve up your entire site (or do some other fancy server manipulation to make it work). You will need this method if you want to limit the amount of time a user stays online/idle, or to restrict logins so that a given user can't have more than one simultaneous login.

The examples in this chapter all deal with HTTP authentication. It is by far the easiest method to set up and maintain.

Designing Passworded Sites

Give some thought to the reasons for the password protection, and the level of security you need. If you're setting up a developmental site to share designs or documents with a handful of people, then a single username/password may be sufficient. For an intranet site accessible only to people from a certain domain, you may not even need a username/password - you can restrict access based on domain alone.

Keep in mind that unless you are using a secure server (where your protected pages are all being accessed via a https:// url), usernames and passwords are sent over the web "in the clear", and are not encrypted. It's possible for someone to run a "packet sniffer" (a program that intercepts internet traffic) - and if they do, they've got your username and password. If you're providing or asking for any kind of secure data (credit card or bank information, stocks, etc.), you need to use a secure server.

Also, if you have more than a hundred users accessing a hidden area, you should use a database (along with the appropriate mod_auth module compiled into the server) for lookups. The web server has to look up the username in the auth table for _every page_ that's being accessed, even after a user has logged in; if you use a flat password file for this, your server may get bogged down from excessive file I/O.

Basic HTTP Authentication

With HTTP authentication, you can only password-protect a directory and the files within; it's not possible to protect a single file with this method. Here's how to set up a passworded directory.

First, create a subdirectory in your web space. For this example we'll create one named "secure". Set the permissions on the directory so that it's world readable/executable. Here are the Unix shell commands:

Next you'll need to create a .htaccess file inside the secure directory. Make it a new file, and enter the following data. The items in bold are things you will want to change depending on the location of these files and directories on your server.

The AuthName is what the user will see when they're prompted for a password - "Enter Authorization for <AuthName>". If AuthName is more than one word, you'll need to enclose it in quotes, or you'll get an error instead of a password prompt when accessing the pages in that directory.

Now you'll set up the password file. You'll need to use the htpasswd program to do this. It is included with the Apache server, usually in the support subdirectory under the server root (try /usr/local/apache/bin, for Apache 1.3 and later). If you can't find it or your server doesn't have this program, you can download one - see the resources list at the end of the chapter.

Now for every user you want to add to the password file, enter the following. (the -c flag is only required the first time; it indicates that you want to create the .htpasswd file).

chmod both files (.htaccess and .htpasswd) to mode 644, so the webserver can read them. Now, when you access the secure directory via your browser, you should be prompted for a username and password.

* Working example: http://www.cgi101.com/class/password/secure/ - username is "webuser" and password is "foobar1".

You don't have to name the password file '.htpasswd'; it can be any file name. Just be sure to put the full path to the file name in the .htaccess file.

Creating a User Registration CGI

This example shows you how to create a form and CGI to allow users to register on your site, creating a username and password for themselves. This script writes directly to the .htpasswd file in the protected directory, and may be a security risk. If you are on a shared server (such as an ISP) and your CGIs don't run with your userid and permissions, then the only way this will work is if your .htaccess file is world-writable. This is dangerous, so you may want to consider using something like mod_auth_mysql instead (see below).

First, you'll need to create a registration form. At the very least, you'll need fields for the username and password. You may want to request additional information from the user, such as their full name and e-mail address. This extra info can be stored in a separate file or database. Here's an example user registration form:

Now you'll need a CGI to parse the form data, and do some validation before writing the password information to the .htaccess file. The new user's name and e-mail address are written to a separate data file called .userinfo.

* Source Code: http://www.cgi101.com/class/password/register.txt
* Working Example: http://www.cgi101.com/class/password/register.html

Remember you'll have to change the permissions of the .htpasswd and .userinfo files so that they're writable by the web server process.

A Better Method: Authentication via Database

Leaving your .htpasswd files writable by anyone is a bad idea, unless you're the only person with a user account on your web server's machine... and even then, there's some risk. There are many other ways you can authenticate users, however. The Apache server has a number of contributed modules available for this purpose; a search for "mod_auth" on the Apache Module Registry (http://modules.apache.org/) turns up dozens.

Since we've done a lot of work already with MySQL, these next examples will deal with user authentication using mod_auth_mysql. When compiled into Apache, this module allows you to store usernames and passwords in a MySQL database. You'll still need to create a .htaccess file to make the directory password-protected. Here's an example of a basic .htaccess file for mod_auth_mysql:

The format of the htaccess file is pretty self-explanatory. Auth_MYSQL_DB is the name of the database which contains the username/password info. Auth_MYSQL_Password_Table is the name of the table inside the database which contains the username/password info. The Auth_MYSQL_Username_Field and Auth_MYSQL_Password_Field directives specify the name of the columns in your password table that will be used for usernames and passwords, respectively. (You can call them whatever you like, just be sure to specify them in the htaccess file.)

There are more advanced ways to set up the file; you can, for example, maintain a database of subscribers and use a third column such as "Status" to determine whether the user can access the area:

The Auth_MYSQL_Group_Table and Auth_MYSQL_Group_Field directives specify what table and field to look at for the group information.

So, in this example, you could have different statuses such as "CURRENT" and "EXPIRED", and only allow users whose subscriptions are "CURRENT" to access the area. When their subscriptions expire, you just change their status to "EXPIRED" without deleting them from the database. This preserves their login information (and whatever other info you stored about them), in case they decide to renew later.

Let's try it. We'll create a MySQL table called "users":

mysql> create table users( username char(20) not null primary key, password char(40) not null, status enum('CURRENT','EXPIRED','SUSPEND') not null, name char(80) not null, email char(80) not null);

Now to automatically add users to your database, you'll use the same registration form as before, then modify the register.cgi to query and update the user database:

* Source Code: http://www.cgi101.com/class/password/register2.txt
* Working Example: http://www.cgi101.com/class/password/register2.html

Resetting Passwords

Whenever you set up password-protected areas, be prepared to provide support for users who forgot (or want to change) their usernames and passwords. There are two separate form/CGIs needed to do this. First you'll need a script to handle cases where someone has lost or forgotten their password. This script will need to be outside the password-protected area (since, if they don't have their password, they obviously can't login to change it). The script will reset the password and email it to their registered email address (provided you've saved it somewhere).

The second case is when someone knows their password, and wants to change it. This script can be saved in the password-protected area of your site.

Let's start with he 'forgot my password' example. In this example, we have a form that prompts for the visitor's username, along with their e-mail address (which we requested on the original registration form).

Now the CGI will look up the username and e-mail address in the user information file. We want to verify that the person requesting the change is really the person who registered the userid. If so, then we'll reset the password and mail it to the user's e-mail address.

* Source Code: http://www.cgi101.com/class/password/forgotpass.txt
* Working Example: http://www.cgi101.com/class/password/forgotpass.html

Change Password

This is the second support script needed for a passworded site - it handles cases where the user knows their password and just wants to change it to something else. It's good to provide a URL to this page whenever you reset (randomize) someone's password, as with the above script, because they'll want to reset it to something they can remember - rather than try to remember the randomized password.

Since this form will be in the passworded area, you don't NEED to ask them for their password again, but it's probably a good idea. This prevents someone from changing the password should the real user walk off and leave their browser unattended.

Now for the CGI. Notice we didn't ask for the username - that can be gotten from any script in the password-protected area by looking at $ENV{'REMOTE_USER'}.

Also, to verify the old password, we're using a slightly different method of encrypting to compare it to the one that's saved in the database. In earlier scripts, we've been using code like this:

to encrypt the password. The "salt" is a two-character string randomly chosen from the set [a-zA-Z0-9./] (which is stored in the @salt array). The salt string is used to randomize the encryption routine, thus making the encrypted string harder to crack. If you take the same string and encrypt it with a different pair of salt characters, you'll get a completely different encrypted string. But, if you encrypt it with the SAME pair of salt characters, you'll get the same encrypted string. Also, the frist two characters of the encrypted string ARE the salt pair that was used to encrypt it. This means you can take the first two characters of the stored encrypted password, use it as salt on the value the user typed in as their old password, and - if they typed the correct password - the resulting encrypted string will match what's stored in the database. Specifically:

If $oldpass is the same password that $storedpass was before encrypted, then $encpass will be equal to $storedpass.

Here's the complete password change script:

* Source Code: http://www.cgi101.com/class/password/passchg.txt
* Working Example: http://www.cgi101.com/class/password/secure2/passchg.html

Resources

NCSA Mosaic User Authentication Tutorial - http://hoohoo.ncsa.uiuc.edu/docs/tutorials/user.html

mod_auth_mysql

There is also a htpasswd Perl module available on CPAN: http://search.cpan.org/search?dist=Apache-Htpasswd


Copyright © 2000 by Jacqueline D. Hamilton.
CGI101 Home | CGI Class