There are a large number of plugins available, which makes it
easy to do almost anything you wish. I was able to quickly find plugins for
session management, database access, authentication, etc. However, the sheer volume of options
quickly became confusing. I had trouble figuring out how to best put all the
seperate parts together into one big (working) piece.

Of course, there are examples out there ( see the CGI::Application wiki)
- but I couldn't find anything exactly
like what I wanted to do. Furthermore, I wanted to use SSL for secure authentication, and couldn't
find any info on that documented anywhere. So, after much time and effort, I'm presenting this small,
working example in order to help anyone else that might come along and find themselves
in the same situation.

I don't consider myself an expert, and I don't claim that this is the only way to do it,
nor the best way to do it. But it does work, and I think one could use this as a starting
point upon which to build. After reading all the necessary module documentation on CPAN,
you should be able to modify this as necessary to suit your own needs.

The finished example demonstrates a simple CGI login page. Authentication is
performed against a MySQL database. The session ID is stored as a cookie
on the client side (browser). There are public pages anyone can see,
and private pages one must login to see. The login form uses SSL to
provide greater security.

I'm going to assume you already have apache installed and configured
properly. If you need help with this part, try looking here:
apache docs.
I'm also going to assume you have MySql installed and configured properly.
If you need help with that, try looking here: mysql docs.
I'm also going to assume that you've installed all the necessary perl
modules on your system. If you need help with that, try looking here:
Modules: How to Create, Install, and Use .

If you've made it this far - congratulations! Now let's get started.

Database Setup

The database used here contains two tables. The first
table will be used to store user names and passwords.
The second table will be used to store session data.
Having sessions allows data to persist over time.
For example, if you develop a shopping cart application,
and someone places an item in the cart, the session will
'remember' the cart's contents. For this tutorial, we'll only
be remembering if someone is logged in or not.

CGI-BIN Scripts

I'm going to assume that your cgi-bin directory is
located at this path:

/var/www/cgi-bin

If for some reason yours is in a different location, that's okay, you'll
just have to change the path appropriately in the instructions below.

First, I'm going to create a directory for my application, called WebApp.
Inside this directory, I'll create a script called simple.pl.
Just to make sure everything is working, put the following code
inside simple.pl:

You should see the message "hello world". If you do not,
then don't read any further until you get that working.
Check your apache config file and be sure to 'chmod 755'
the WebApp directory and the simple.pl script.

Inside the WebApp directory, create a 'templates' subdirectory,
and a 'libs' subdirectory. Next create a 'MyLib' directory inside of 'libs'.

Line-by-Line Discussion of Code Listings

The code listings to follow have each line numbered for easy
reference. If you'd like to download these files and try
running them yourself, use the following perl one-liner to
remove the numbers from the start of each line:

perl -p -i.orig -e 's/^=\s+\d+\s+= ?//' simple.pl

In this particular case, 'simple.pl' will be edited in place (line numbers removed),
and the original file backed up as 'simple.pl.orig'.

The file 'simple.pl' (Listing 1) is the application script.
Line 3 tells perl where to look for my custom module files. Line 4
loads the module that actually defines the run modes (pages)
for my application, and line 12 starts it running. Lines 7-10
set some instance-specific run parameters. In this case, we're
defining 'simple.ini' to be our configuration file (Listing 2).
It's basically just name = value pairs. Using these will help us avoid hard-coding
anything in our scripts that may need to be changed. For now,
all that's in here is info needed for establishing a connection
to the database.

Normally, you would create a web app by inheriting from class CGI::Application.
In this case, class MyLib::Simple actually inherits
from MyLib::Login. MyLib::Login inherits from CGI::Application.
This lets us put all of the login/logout related functions into
a seperate module. Plus, if we later develop a second web app (MyLib::Simple2, for
example) it can easily reuse all of the same login code. Imagine having to maintain
10 different web apps. Let's say you want to switch databases from MySql to Postgres.
This approach allows you to modify one single Login.pm module, rather than
10 seperate app-specific modules. See? Code reuse is good!

Since the first thing Simple.pm does is load Login.pm (line 22), let's start
with Login.pm (Listing 4).
We'll come back to Listing 3 later.

Line 97 loads CGI::Application. Lines 99-104 load all of the plugin modules
we want to use. Here's a quick overview of what each one does:

'AutoRunmode' will allow us to use shorter URLs to refer to our web pages.
For example, instead of this:

http://localhost/cgi-bin/WebApp/simple.pl?rm=index

we can instead use this:

http://localhost/cgi-bin/WebApp/simple.pl/index

In other words, it will allow us to extract the desired run mode from
the PATH_INFO environment variable. It also lets us define run mode methods
using attributes, like this:

sub mypage : Runmode {

Otherwise, we'd have to use the runmodes() method to register each page we want to define.
This would normally be done in the setup method, but since we're using AutoRunmode, our
setup method (lines 107-114) is pretty short.

The Session plugin is a wrapper around CGI::Session. This will help us maintain
state from one page view to the next (in other words, it helps us provide
persistent data).

The Authentication module provides methods for logging in and out. Let's say we
have 50 people browsing our site at the same time, and each one has placed items
in their shopping cart. Authentication allows us to identify individual users.
Otherwise, there would be no way to determine which cart belongs to which user.

The ConfigAuto module helps us read parameters from our config file.

Line 105 loads the MD5 module, which we'll use later to encrypt passwords.

Lines 107-114 define our setup method. The most important thing here is line 111,
which tells CGI::Application to parse the PATH_INFO environment variable.
The AutoRunmode plugin needs this to work properly.

Line 116 begins our cgiapp_init method. As the name implies, this is where we
do most of our 'initialization'. Line 119 uses the cfg method from the ConfigAuto
plugin to read our config file and store all our name-value pairs into a hash
called %CFG.

Line 121 tells CGI::Application where to look for template files (HTML::Template
will need to know this later on).

Lines 124-128 initialize our database connection. The dbh_config method is defined
by the DBH plugin. Rather than hard-code the necessary parameters, we're giving
it the data we read from our config file. Note that this lets us avoid hard coding
our mysql password inside our script. If we need to run our web app on several
different servers, we might have a different database on each. In this case,
we'd have a different config file for each server, but the perl code would be the same
for all of them.

Lines 130-142 initialize the session configuration. There are lots of different
ways to do this. You could, for example, choose to save session data to a file
instead of to a database. Or you could choose to use Data::Dumper to serialize
your data instead of Storable. Or, you could use Postgres instead of MySql.
But in this case, I've decided to use MySql, so I specify the 'mysql' driver.
Storable is a good serializer because it's fast and uses compression, but
the result is not humanly readable like Data::Dumper. If you run mysql and
type 'select * from sessions' you'll see lots of strange characters!
So Data::Dumper might be better if you're trying to debug problems with
your database, but the two formats are incompatible, so it's not easy to
switch back and forth between the two (if you want to swap methods, I'd
recommend dropping the 'sessions' table and recreating it each time).

In Line 133, self->query will return the current CGI object, and $self->dbh
will return the current database handle (the one we just configured in lines 124-128).

Line 136 defines the default expiration time for our session. This means
that once someone logs in, they will automatically be logged out after 1 hour
of inactivity.

Each session created will be assigned a unique 32 character id code.
This id will be written into a browser cookie. Lines 137-141 show
how to configure cookie-related parameters. I've commented these out,
because I prefer to use the defaults.

Lines 145-160 configure parameters for the Authentication plugin.
In line 146, we specify the DBI driver because we're using a database.
Line 147 gives the Authentication plugin a copy of our database handle.
Line 148 gives it the name of the table to use for finding usernames and
passwords. Lines 150 and 151 define which columns to use within that table.
Line 151 also specifies that passwords will be encrypted using MD5.
Line 155 says that we'll save our login state inside a session.
If a user is not logged in, but tries to access a protected page,
the Authentication plugin will automatically redirect the user to the
login page. Once the user enters a valid username and passsword, they
get redirected back to the protected page they originally requested.
If a session expires, they get automatically logged out. For this to
work properly, the Authentication plugin has to know which methods
to call to perform the login/logout functions. These are defined
in lines 156-157.

For added security, I wanted to use SSL to prevent a password entered
on the login page from being transmitted over the internet as plain text.
So I make sure the login page is accessed using https, rather than http
(more on this later).
But https is generally slower than http, so once logged in, I wanted
to switch back to regular http. There's no easy way to do this!
My work-around is to introduce a post-login run mode (line 158).
This is a run mode that gets called after a user successfully logs in.
What does this special run mode do? It basically figures out which
run mode (page) the user really wanted to see, then redirects the browser
to that page using http (not https).

Line 159 specifies a subroutine
to use to generate a login form. Note that the Authentication plugin
comes with a default form that you can use. I'm including this one
just to demonstrate how to go about creating one of your own, in case
you really want to. The default one actually looks much better than
mine, so you might wish to comment out lines 157-159 in order to see it!
The best solution is to write a custom login form of your own, that looks
the way you want. The one I present here is intended only to demonstrate
the basic functionality.

Lines 163-165 define which runmodes require a successful login.
The Login.pm module doesn't define any content - all of the actual
web pages are in Simple.pm. So I define the 'mustlogin' page
here as a kind of place-holder. It's a dummy page that forces you
to login, but immediately redirects you back to the default start page
(usually the index page). The mustlogin runmode is defined in lines 174-178.

The teardown method (lines 169-172) is used to close the database connection.

The 'okay' method (lines 180-192) exists only to switch from https back to http.
It assumes that the target run mode is stored in a cgi parameter named 'destination',
but if for some reason this is not the case, it will default back to the index page.

The login method (lines 194-213) basically just displays the login form (line 211).
But first, it checks to make sure you're not already logged in (lines 198-204),
and second, it makes sure you're connecting with https. If you try to
access the login page with http, it will automatically redirect you using https.

The login form is generated by my_login_form (lines 215-238). Actually,
most of the form is pregenerated in the form of a template
(see Listing 7).
Line 217 loads this template, lines 234-236 insert values into the
template parameters, and line 237 generates the final HTML. The 'destination'
parameter is important, because it contains the URL of the page to go to
once the user has successfully logged in. This gets tricky, because there
are two ways to log in. On the index page, there's a link which says
"click here to log in". If you click that link, and log in successfully,
you'll be taken back to the index page. If you try to access a protected page
before logging in, you'll automatically get redirected to the login page, but it
will use the 'destination' parameter to remember where you were trying to go,
and take you there once you do login.

So, my_login_form first tries to get a value for 'destination' from the CGI
query object (in case it was passed as a hidden variable). If that fails,
it tries looking at the PATH_INFO environment variable (in case it's being
passed as part of the URL). If all else fails, it defaults to the index page.
In the event the login attempt fails, you get redirected back to the login
form, where it asks you to try again.

Adding SSL into this process gets tricky. The login method will ensure
that the login page has a URL like this:

https://localhost/cgi-bin/WebApp/simple.pl/login

But for the username/password data to be encrypted, it's important that the
page that this form gets submitted to also have an https in the URL.
So if you look at line 341 (inside the login_form.html template) you'll
see the "action" part of the form tag is set to point to the 'mustlogin'
method. But once you DO successfully login, the Authentication plugin
is going to run the post-login run mode 'okay', which redirects us back
to the indended destination, minus the https URL.

Note that depending on which browser you're using, and what security
settings you have enabled, you may see popups asking you to accept
a security certificate, or that you're entering/leaving a secure area, etc.
These are all normal and can be safely ignored.

Lines 240-247 define the logout method. Note that logging out actually
deletes the current session (see line 244).

Lines 249-257 define the default error run mode: if any run mode fails
to eval, CGI::Application will redirect you to this run mode. It gives
you a nice way to trap errors in a sane way.

Lines 259-268 define the AUTOLOAD method. This will get called if you
try to access a non-existant run mode. Having this in place gives
a user a nice error message if they accidentally type in a URL wrong.

Now we're ready to return to Listing 3.
Since MyLib::Simple inherits
from MyLib::Login, it has access to all the methods we've just discussed,
in addition to all the methods defined by CGI::Application.

Lines 24-35 define cgiapp_init. Line 27 calls the cgiapp_init method
we saw before, in Login.pm. The "SUPER::" tag means "call this method
from my parent class". We need to do this because we need the database
connection, session config, etc. like before - but we also want to
do some additional, application specific initialization. For example,
Simple.pm defines two private run modes (pages that require a user
to login in order to view). We specify those in lines 30-33. But without
line 27, the cgiapp_init in Simple.pm would over-ride the one in Login.pm,
and be run INSTEAD of that one, not IN ADDITION to that one, like we want.

So just remember, every web app we create can inherit from Login.pm to
get the same login functionality, and each will have its own cgiapp_init
to do "local initialization", but each will need to call SUPER::cgiapp_init
to do Login.pm's "global initialization" stuff (database connect, session config,
etc.). The alternative is to copy & paste the code each time, but then if you
ever want to make a change, you'll have to change it in each copy. This
way you can simply change it in one place and globally effect every application
that depends on it (for example, if you decide you'd rather use SHA1 instead
of MD5 - just change Login.pm).

Lines 37-46 define our index page. Notice that it has the attribute "StartRunmode"
instead of "Runmode". That means it will be the page loaded if no other page
is specified. So these three URLs are all equivalent:

The first URL explicitly sets the runmode to 'index', the second URL
sets the run mode to 'index' via the PATH_INFO environment variable,
and the third URL defaults to 'index' as the start run mode because no
other mode was specified.

So what does the index run mode do? It loads the index.html template
(Listing 5, line 39),
populates a few template parameters (lines 41-43) - note that you can
define several values inside a single param statement - then returns
the final HTML for rendering (line 45).

Note that we call $self->authen->username. If someone is logged in, this
will return their username. Otherwise, it will return null. We can test
the USER value inside the template to display different messages accordingly
(lines 292-297). So a user that is NOT logged in will see a "click here
to login" message, and a user that IS logged in will see a "click here
to log out" message.

Finally, the index page displays links to other pages: two public, and
two private. All of these pages use the same default.html template
(Listing 6), but the
parameters like NAME and MESSAGE vary for each one.

If you'd like to test the "error run mode", uncomment line 66 then
try clicking the link for "public2". The "die" statement will
generate an error message, and the error run mode will trap it.

To test the "autorunmode", try asking for a nonexistant page, like this:

http://localhost/cgi-bin/WebApp/simple.pl/bogus

You should be able to visit pages public and punlic2 without logging in.
If you click on the link for private or private2, you'll get redirected to
the login page (URL will switch to https), once you submit your username
and password you'll get redirected to the private page (the URL will
go back to http). If you click on "login" first, you'll be taken back
to the index page. The, if you click on a link to a private page, it will
take you there directly. If you do nothing for 1 hour, then try to
again access a private page, it will ask you to login again (because
the session will have expired).

Note that the templates contain a < META > tag with an expiration date
several years old. This is a hack to trick the browser into NOT caching
the page. Otherwise, you might still be able to see a private page after
logging out, because the browser will pull it from the local page cache.
If you force the page to reload, you should be prompted to login again.

Note that if this were a real application, the Login.pm module would
include a "register" run mode to add new users, a "reset" method
to let users change their password, a "forgot" method in case someone
can't recall their password, etc.

I'll leave that as an exercise for the reader. The tricky part is
logging in and out and getting the SSL to work, and hopefully I've
been able to help with that.

This looks very detailed and i'm actualy anxious to try it out myself. A few points though. I don't like your line numbers. I have an editor that does line numbers and perlmonks itself I have configured for line numbers, your number don't match those and add a layer of confusion. I can't download everything and work from your line numbers and run the scripts, since the line numbers break the script so that part bugs me. Also while mentioning the line numbers it would be nice if they were bold so i could see a line i was curious about and then jump up to your explanation. I don't know if I'm alone, but I like to read the code and only read the text when I am confused.

Second I think any beginner is going to see that huge list of modules and the length of this and be quite afraid. Maybe eliminate some of the unneeded modules? AutoRun and Config are definitely nice, but if you went without your code doesn't get much worse while the prerequisite list will drop down.

Finaly, for the mysql username addition, you probably want to mention they can use the MD5 function in mysql to get that hash. You should be able to just change the example to

My intent, for those who actually want to run the script, was for you to download the files, then run the provided perl one-liner to strip the numbers out. For example, you could download Simple.pm, then run this:

perl -p -i.orig -e 's/^=\s+\d+\s+= //' Simple.pm

And you'll have a working copy of Simple.pm (no line numbers) and a backup called Simple.pm.orig (line numbers included) that you can use to follow along with the tutorial.

I'm guessing your editor numbers each file starting at line 1. This makes perfect sense, but if I numbered the listings that way it would get confusing because of the overlap (i.e., 7 listings => 7 line number 1's, etc.).
I concatenated all the files together, then ran a script to prepend the numbers, so that each line would have a unique identifier.

But I'm open to suggestions - if there's a better way to do it, I'm willing to give it a try. I'll wait a few days and see what other people suggest/recommend.

For the line numbers you could do Simple.pm:10-15 or something like that. I for one would find that much less confusing. Plus once i've downloaded and stripped the line numbers to actualy use it, your directions are now useless because they no longer match the line numbers.

Please don't. That is really inconvenient for your readers/users. PerlMonks supports adding line numbers automatically to code blocks. You can turn it on for yourself while you're writing your post; turn it off again later if you don't want it. As it stands, I'm not at all inclined to download your code.

I reckon we are the only monastery ever to have a dungeon stuffed with 16,000 zombies.

Second I think any beginner is going to see that huge list of modules and the length of this and be quite afraid.

I liked the contents and wouldn't like to see any of it dropped. What about splitting it into several linked tutorials. Each tutorial could add another feature to the application (a bit like the Catalyst tutorial).

Please post your tutorial as a Meditation first - or edit your node to contain it. This allows for comments and editing time. Once you feel it is ready, then post it as a Tutorial. Writeups placed on scratchpads are usually transient; replies to this node, for example, will make no sense without context a month or a year from now.

I thank you for taking such effort to present this tutorial++. But I must I admit that I only skimmed over the code listings so I can't comment on the logic or syntax. This is not a code snippet which I can just simply get it and run it. Since this is a complete application, I'd like to rather see the actual application online to see how it works. So we know what to expect before trying. Especially you claim that:

it does work

I don't think using a large number of modules would be a problem because tutorials has levels of audience. We can say that this tutorial is aimed for intermediate users, not merely beginners.

I'd like also to point about the documentation you refer to. It's nice to tell that those docs are also available locally, because one isn't always connected to the Internet. I guess many intermediate users already know about this, but I'd considered it value added if you mention that users can access Apache docs via http://localhost/manual and MySQL docs, provided that it's installed properly by whatever Linux distro users use or installed manually, via1:

And about the line numbers.... You should know that codes in PerlMonks are meant for downloading so it can be run right away without any modification (additional step, even if you provide a nice snippet to remove them) on the text unrelated to the code logic or internal data. But, I also support the usage of line numbers in the tutorial code as guidance or reference of the explanation. In this case, it's good to provide other means to obtain the actual codes.

Each listing should start with their own number 1. And it's better for me as audience to see the explanations separated for each listing. So when you say, for example, "Simple.pm:10-15" as suggeted by eric256, I know for sure what's the context that particular saying in. In this case of this clear separation of the explanations, it's useful to make cross references by explicitly mentioning the listing name, or merely the listing number. Putting backlinks would also improve user experience so we can go back and forth between the listing and the text. For example:

About the number of modules used... I probably didn't give enough background. When I first began working with CGI::Appliction, creating a login form was one of the first things I wanted to do. CGI::Application assumes you're already familiar with CGI and HTML::Template, so if you're not, there's a fairly steep learning curve (but less so than, say, Catalyst! IMO). Browsing the list of plugins on CPAN (to avoid "reinventing the wheel") I found CGI::Application::Plugin::Authentication. At first, I thought this would be all I needed. But then I realized that it required CGI::Application::Plugin::Storable (unless you want to maintain state with cookies). And it also requires CGI::Application::Plugin::DBH, if you want your passwords stored in a database, instead of a text file. Well, the DBH plugin is really just a wrapper around DBI, so you have to read the documentation for both, but that's still not enough, because at that level it's independent of which database you actually plan to use. Once you decide on, say, mysql, then you have to look up the mysql specific driver modules to learn how to configure things properly, etc. So, what happened to me (and I suspect everyone else that goes down this path) is that you start looking at "just one module" and before you know it you've downloaded about 20 things from CPAN and have two dozen browser windows open to all the various documentation files and while you know all these things are supposed to work together (and each part may, in fact, have good docs and good examples), there is no example of everything put together in final form. So that's what I wanted to do. Too many tutorials are made up of code snippets, and the reader is left having to "connect the dots". The other problem is too much documentation. I am barely scratching the surface of what modules like DBI and Storable are capable of. But for what I want to do, I don't need to know 99% of what's documented - so it's a shame to spend all day reading it trying to find the one detail that you DO need to know.

As for the line numbers, I'm going to point the finger at Randal Schwartz. I love Randal! And his articles way-back-when in Web Techniques magazine really helped me learn how to program in perl. And he ALWAYS showed complete code listings with each line numbered, for easy reference. So I did my stuff the same way. I agree, it would be nice to upload a working demo online somewhere, maybe with a downloadable tar ball of all the code, to save people from having to download each piece and then strip off the line numbers (oh the horror!). But I don't currently have an ISP that allows CGI scripts, so that's not possible.

Including backlinks is an excellent idea. I actually had thought of that myself, but was too lazy to do it. I'll try to add that in, later.

One more thing - regarding my claim that it works. I'm running perl5.8, apache2, and red hat linux (RHEL4). So if anyone out there is trying to develop on win32, er, your milage may vary.

It just so happens I'm just starting a new web
application as a first time CG::Application user! I have downloaded and run this. An excellent and much needed starting point CGI::Application and friends.

I didn't have my desktop Apache (1.3) configured for https, so I commented out the https redirects in Login.pm lines 187-189 and 207-209. I'm also new to Apache 1.3 SSL setup and will get back to it later.

It my be useful to add a bit more on on the required https set-up. Also get the user to test it early, say try https access to the first 'hello world' script.