view release on metacpan or search on metacpan
AuthCookie.pm view on Meta::CPAN
# Instead of 'Bad Cookie', lets return something more useful.
# $ses_key_cookie has a unique value if ERROR, but undef if ! ERROR.
$r->subprocess_env('AuthCookieReason', $ses_key_cookie) if $ses_key_cookie =~ /ERROR/;
$r->subprocess_env('AuthCookieReason', 'ERROR! Your session has expired, or your login does not have the proper access level for this webpage.') if $ses_key_cookie !~ /ERROR/;
}
} else {
#$r->subprocess_env('AuthCookieReason', 'no_cookie');
# Instead of 'no_cookie, let's return something more useful.
$r->subprocess_env('AuthCookieReason', 'Please enter your user name and password.');
}
# They aren't authenticated, and they tried to get a protected
# document. Send them the authen form.
return $auth_type->login_form;
}
sub login_form {
my $r = Apache->request or die "no request";
AuthCookie.pm view on Meta::CPAN
=back
By using AuthCookie versus Apache's built-in AuthBasic you can design
your own authentication system. There are several benefits.
=over 4
=item 1.
The client doesn't *have* to pass the user credentials on every
subsequent access. If you're using passwords, this means that the
password can be sent on the first request only, and subsequent
requests don't need to send this (potentially sensitive) information.
This is known as "ticket-based" authentication.
=item 2.
When you determine that the client should stop using the
credentials/session key, the server can tell the client to delete the
cookie. Letting users "log out" is a notoriously impossible-to-solve
problem of AuthBasic.
AuthCookie.pm view on Meta::CPAN
(-----------------------) +---------------------------------+
( Request a protected ) | AuthCookie sets custom error |
( page, but user hasn't )---->| document and returns |
( authenticated (no ) | FORBIDDEN. Apache abandons |
( session key cookie) ) | current request and creates sub |
(-----------------------) | request for the error document. |<-+
| Error document is a script that | |
| generates a form where the user | |
return | enters authentication | |
^------------------->| credentials (login & password). | |
/ \ False +---------------------------------+ |
/ \ | |
/ \ | |
/ \ V |
/ \ +---------------------------------+ |
/ Pass \ | User's client submits this form | |
/ user's \ | to the LOGIN URL, which calls | |
| credentials |<------------| AuthCookie->login(). | |
\ to / +---------------------------------+ |
\authen_cred/ |
AuthCookie.pm view on Meta::CPAN
V
(---------------------) ^
( Request a protected ) |
( page, user has a )--------------+
( session key cookie )
(---------------------)
* The session key that the client gets can be anything you want. For
example, encrypted information about the user, a hash of the
username and password (similar in function to Digest
authentication), or the user name and password in plain text
(similar in function to HTTP Basic authentication).
The only requirement is that the authen_ses_key function that you
create must be able to determine if this session_key is valid and
map it back to the originally authenticated user ID.
=for html
</PRE>
=head1 METHODS
AuthCookie.pm view on Meta::CPAN
=over 4
=item 1.
The ACTION of the form must be /LOGIN (or whatever you defined in your
server configuration as handled by the ->login() method - see example
in the SYNOPSIS section).
=item 2.
The various user input fields (username, passwords, etc.) must be
named 'credential_0', 'credential_1', etc. on the form. These will
get passed to your authen_cred() method.
=item 3.
You must define a form field called 'destination' that tells
AuthCookie where to redirect the request after successfully logging
in. Typically this value is obtained from C<$r-E<gt>prev-E<gt>uri>.
See the login.pl script in t/eg/.
AuthCookie.pm view on Meta::CPAN
usually looks like C<$r-E<gt>auth_type-E<gt>logout($r);>.
Note that if you don't necessarily trust your users, you can't count
on cookie deletion for logging out. You'll have to expire some
server-side login information too. AuthCookie doesn't do this for
you, you have to handle it yourself.
=head1 ABOUT SESSION KEYS
Unlike the sample AuthCookieHandler, you have you verify the user's
login and password in C<authen_cred()>, then you do something
like:
my $date = localtime;
my $ses_key = MD5->hexhash(join(';', $date, $PID, $PAC));
save C<$ses_key> along with the user's login, and return C<$ses_key>.
Now C<authen_ses_key()> looks up the C<$ses_key> passed to it and
returns the saved login. I use Oracle to store the session key and
retrieve it later, see the ToDo section below for some other ideas.
AuthCookieDBIRadius.pm view on Meta::CPAN
return undef;
}
#<WhatEverDBI_User>
#The user to log into the database as. This is not required and
#defaults to undef.
$c{ DBI_user } = _dir_config_var( $r, 'DBI_User' ) || undef;
#<WhatEverDBI_Password>
#The password to use to access the database. This is not required
#and defaults to undef.
$c{ DBI_password } = _dir_config_var( $r, 'DBI_Password' ) || undef;
#<WhatEverDBI_UsersTable>
#The table that user names and passwords are stored in. This is not
#required and defaults to 'users'.
$c{ DBI_userstable } = _dir_config_var( $r, 'DBI_UsersTable' ) || 'users';
#<WhatEverDBI_UserField>
#The field in the above table that has the user name. This is not
#required and defaults to 'user'.
$c{ DBI_userfield } = _dir_config_var( $r, 'DBI_UserField' ) || 'user';
#<WhatEverDBI_PasswordField>
#The field in the above table that has the password. This is not
#required and defaults to 'password'.
$c{ DBI_passwordfield } = _dir_config_var( $r, 'DBI_PasswordField' ) || 'password';
#<WhatEverDBI_CryptType>
#What kind of hashing is used on the password field in the database. This can
#be 'none', 'crypt', or 'md5'. This is not required and defaults to 'none'.
$c{ DBI_crypttype } = _dir_config_var( $r, 'DBI_CryptType' ) || 'crypt';
#<WhatEverDBI_GroupsTable>
#The table that has the user / group information. This is not required and
#defaults to 'groups'.
$c{ DBI_groupstable } = _dir_config_var( $r, 'DBI_GroupsTable' ) || 'groups';
AuthCookieDBIRadius.pm view on Meta::CPAN
unless ( $c{ DBI_secretkeyfile } = _dir_config_var $r, 'DBI_SecretKeyFile' )
{
_log_not_set $r, 'DBI_SecretKeyFile';
return undef;
}
#<WhatEverDBI_EncryptionType>
#What kind of encryption to use to prevent the user from looking at the fields
#in the ticket we give them. This is almost completely useless, so don't
#switch it on unless you really know you need it. It does not provide any
#protection of the password in transport; use SSL for that. It can be 'none',
#'des', 'idea', 'blowfish', or 'blowfish_pp'.
#This is not required and defaults to 'none'.'
$c{ DBI_encryptiontype } = _dir_config_var( $r, 'DBI_EncryptionType' ) || 'none';
# If we used encryption we need to pull in Crypt::CBC.
if ( $c{ DBI_encryptiontype } ne 'none' )
{
require Crypt::CBC;
}
AuthCookieDBIRadius.pm view on Meta::CPAN
# Username goes in credential_0
my $user = $credentials[ 0 ];
unless ( $user =~ /^.+$/ )
{
$r->log_reason( "Apache::AuthCookieDBIRadius: no username supplied for auth realm $auth_name", $r->uri );
return 'ERROR! No Username Supplied';
#return 'bad';
}
# Password goes in credential_1
my $password = $credentials[ 1 ];
# create $temp for error messages.
my $temp = $password;
unless ( $password =~ /^.+$/ )
{
$r->log_reason( "Apache::AuthCookieDBIRadius: no password supplied for auth realm $auth_name", $r->uri );
return 'ERROR! No Password Supplied';
#return 'bad';
}
# get the configuration information.
my %c = _dbi_config_vars $r;
# Lock out after 5 failed consecutive attempts. Unlock when the next IP comes in.
my $attempts = 1;
my @split = ();
AuthCookieDBIRadius.pm view on Meta::CPAN
{
$r->log_reason( "Apache::AuthCookieDBIRadius: Security Error! Too many attempts to auth realm $auth_name", $r->uri );
return "ERROR! Security error. Too many attempts.";
}
}
# Store new value.
$result = $share->store("$ENV{REMOTE_ADDR}:$attempts");
# Look up user in database.
my $dbh = DBI->connect( $c{ DBI_DSN },
$c{ DBI_user }, $c{ DBI_password } );
unless ( defined $dbh )
{
$r->log_reason( "Apache::AuthCookieDBIRadius: couldn't connect to $c{ DBI_DSN } for auth realm $auth_name", $r->uri );
return 'ERROR! Internal Server Error (111). Please contact us immediately so we can fix this problem.';
#return 'bad';
}
my $cmd = "SELECT $c{DBI_passwordfield},activeuser,a,b,c,d,e,f,g FROM $c{DBI_userstable} WHERE $c{DBI_userfield} = @{[ $dbh->quote($user) ]}";
$result = $dbh->prepare($cmd);
$result->execute;
my @row = $result->fetchrow_array;
# debug line.
#$r->log_reason( "Apache::AuthCookieDBIRadius: results from database query: row = @row for user $user for auth realm $auth_name", $r->uri );
my $crypted_password = $row[0];
my $activeuser = $row[1];
my $a = $row[2];
my $b = $row[3];
my $c = $row[4];
my $d = $row[5];
my $e = $row[6];
my $f = $row[7];
my $g = $row[8];
#unless ( defined $crypted_password )
if ( !$crypted_password )
{
## Not in DBI database, let's try Radius.
#$r->log_reason( "Apache::AuthCookieDBIRadius: couldn't select password from $c{DBI_DSN}, $c{DBI_userstable}, $c{DBI_userfield} for user $user for auth realm $auth_name, lets try Radius", $r->uri );
#
## Create the radius connection.
#my $radius = Authen::Radius->new(
# Host => "$c{ DBI_Radius_host }:$c{ DBI_Radius_port }",
# Secret => $c{ DBI_Radius_secret },
# TimeOut => $c{ DBI_Radius_timeout });
#
## Error if we can't connect.
#if (!defined $radius)
#{
# $r->log_reason("Apache::AuthCookieDBIRadius: failed to connect to Radius host $c{ DBI_Radius_host }, Radius port $c{ DBI_Radius_port }", $r->uri );
# return 'ERROR! Internal Server Error (222). Please contact us immediately so we can fix this problem.';
# #return 'bad';
#}
## Do the actual check.
#if ($radius->check_pwd($user,$password))
#{
# # Passed.
# $r->log_reason("Apache::AuthCookieDBIRadius: User $user in Radius and password matches", $r->uri);
#
# # Must be an employee, give them everything.
# $activeuser = 'y';
# $a = 'y';
# $b = 'y';
# $c = 'y';
# $d = 'y';
# $e = 'y';
# $f = 'y';
# $g = 'y';
#}
#else
#{
# Radius failed, return to login page.
$r->log_reason("Apache::AuthCookieDBIRadius Radius authentication failed for user $user and password $password", $r->uri);
return 'ERROR! Authentication Failure.';
#return 'bad';
#}
}
else
{
# Return unless the passwords match.
if ( lc $c{ DBI_crypttype } eq 'none' )
{
unless ( $password eq $crypted_password )
{
$r->log_reason( "Apache::AuthCookieDBIRadius: plaintext passwords didn't match for user $user, password = $password, crypted_password = $crypted_password for auth realm $auth_name", $r->uri );
return 'ERROR! Password did not match.';
#return 'bad';
}
}
elsif ( lc $c{ DBI_crypttype } eq 'crypt' )
{
my $salt = substr $crypted_password, 0, 2;
unless ( crypt( $password, $salt ) eq $crypted_password )
{
$r->log_reason( "Apache::AuthCookieDBIRadius: crypted passwords didn't match for user $user, password supplied = $temp for auth realm $auth_name", $r->uri );
return 'ERROR! Password did not match.';
#return 'bad';
}
}
elsif ( lc $c{ DBI_crypttype } eq 'md5' )
{
unless ( md5_hex( $password ) eq $crypted_password )
{
$r->log_reason( "Apache::AuthCookieDBIRadius: MD5 passwords didn't match for user $user for auth realm $auth_name", $r->uri );
return 'ERROR! Password did not match.';
#return 'bad';
}
}
}
# Create the expire time for the ticket.
my $expire_time;
# expire time in a zillion years if it's forever.
if ( lc $c{ DBI_sessionlifetime } eq 'forever' ) {
AuthCookieDBIRadius.pm view on Meta::CPAN
my $auth_name = $r->auth_name;
# Get the configuration information.
my %c = _dbi_config_vars $r;
my $user = $r->connection->user;
# See if we have a row in the groups table for this user/group.
my $dbh = DBI->connect( $c{ DBI_DSN },
$c{ DBI_user }, $c{ DBI_password } );
unless ( defined $dbh ) {
$r->log_reason( "Apache::AuthCookieDBIRadius: couldn't connect to $c{ DBI_DSN } for auth realm $auth_name", $r->uri );
return undef;
}
# Now loop through all the groups to see if we're a member of any:
my $result = $dbh->prepare( <<"EOS" );
SELECT $c{ DBI_groupuserfield }
FROM $c{ DBI_groupstable }
WHERE $c{ DBI_groupfield } = ?
AuthCookieDBIRadius.pm view on Meta::CPAN
# key line must come first
PerlSetVar PortalDBI_SecretKeyFile /usr/local/apache/conf/site.key
PerlModule Apache::AuthCookieDBIRadius
PerlSetVar PortalPath /
PerlSetVar PortalLoginScript /login.pl
PerlSetVar AuthCookieDebug 1
PerlSetVar PortalDBI_DSN 'dbi:Pg:host=localhost port=5432 dbname=mydatabase'
PerlSetVar PortalDBI_User "database_user"
PerlSetVar PortalDBI_Password "database_password"
PerlSetVar PortalDBI_UsersTable "users"
PerlSetVar PortalDBI_UserField "userid"
PerlSetVar PortalDBI_PasswordField "password"
PerlSetVar PortalDBI_SessionLifeTime 00-24-00-00
<FilesMatch "\.pl">
AuthType Apache::AuthCookieDBIRadius
AuthName Portal
SetHandler perl-script
PerlHandler Apache::Registry
Options +ExecCGI
</FilesMatch>
AuthCookieDBIRadius.pm view on Meta::CPAN
PerlAuthenHandler Apache::AuthCookieDBIRadius->authenticate
PerlAuthzHandler Apache::AuthCookieDBIRadius->authorize
require valid-user
</Directory>
=head1 DESCRIPTION
This module is an authentication handler that uses the basic mechanism provided
by Apache::AuthCookie with a DBI database for ticket-based protection. It
is based on two tokens being provided, a username and password, which can
be any strings (there are no illegal characters for either). The username is
used to set the remote user as if Basic Authentication was used.
On an attempt to access a protected location without a valid cookie being
provided, the module prints an HTML login form (produced by a CGI or any
other handler; this can be a static file if you want to always send people
to the same entry page when they log in). This login form has fields for
username and password. On submitting it, the username and password are looked
up in the DBI database. The supplied password is checked against the password
in the database; the password in the database can be plaintext, or a crypt()
or md5_hex() checksum of the password. If this succeeds, the user is issued
a ticket. This ticket contains the username, an issue time, an expire time,
and an MD5 checksum of those and a secret key for the server. It can
optionally be encrypted before returning it to the client in the cookie;
encryption is only useful for preventing the client from seeing the expire
time. If you wish to protect passwords in transport, use an SSL-encrypted
connection. The ticket is given in a cookie that the browser stores.
After a login the user is redirected to the location they originally wished
to view (or to a fixed page if the login "script" was really a static file).
On this access and any subsequent attempt to access a protected document, the
browser returns the ticket to the server. The server unencrypts it if
encrypted tickets are enabled, then extracts the username, issue time, expire
time and checksum. A new checksum is calculated of the username, issue time,
expire time and the secret key again; if it agrees with the checksum that
AuthCookieDBIRadius.pm view on Meta::CPAN
Specifies the DSN for DBI for the database you wish to connect to retrieve
user information. This is required and has no default value.
=item C<WhatEverDBI_User>
The user to log into the database as. This is not required and
defaults to undef.
=item C<WhatEverDBI_Password>
The password to use to access the database. This is not required
and defaults to undef.
=item C<WhatEverDBI_UsersTable>
The table that user names and passwords are stored in. This is not
required and defaults to 'users'.
=item C<WhatEverDBI_UserField>
The field in the above table that has the user name. This is not
required and defaults to 'user'.
=item C<WhatEverDBI_PasswordField>
The field in the above table that has the password. This is not
required and defaults to 'password'.
=item C<WhatEverDBI_CryptType>
What kind of hashing is used on the password field in the database. This can
be 'none', 'crypt', or 'md5'. This is not required and defaults to 'none'.
=item C<WhatEverDBI_GroupsTable>
The table that has the user / group information. This is not required and
defaults to 'groups'.
=item C<WhatEverDBI_GroupField>
The field in the above table that has the group name. This is not required
AuthCookieDBIRadius.pm view on Meta::CPAN
readable by root. It is read at server startup time.
The key should be long and fairly random. If you want, you
can change it and restart the server, (maybe daily), which will invalidate
all prior-issued tickets.
=item C<WhatEverDBI_EncryptionType>
What kind of encryption to use to prevent the user from looking at the fields
in the ticket we give them. This is almost completely useless, so don't
switch it on unless you really know you need it. It does not provide any
protection of the password in transport; use SSL for that. It can be 'none',
'des', 'idea', 'blowfish', or 'blowfish_pp'.
This is not required and defaults to 'none'.
=item C<WhatEverDBI_SessionLifetime>
How long tickets are good for after being issued. Note that presently
Apache::AuthCookie does not set a client-side expire time, which means that
most clients will only keep the cookie until the user quits the browser.
However, if you wish to force people to log in again sooner than that, set
AuthCookieDBIRadius.pm view on Meta::CPAN
DD-hh-mm-ss -- Days, hours, minute and seconds to live.
This is not required and defaults to '00-24-00-00' or 24 hours.
=back
=head1 DATABASE SCHEMAS
For this module to work, the database tables must be laid out at least somewhat
according to the following rules: the user field must be a primary key
so there is only one row per user; the password field must be NOT NULL. If
you're using MD5 passwords the password field must be 32 characters long to
allow enough space for the output of md5_hex(). If you're using crypt()
passwords you need to allow 13 characters.
An minimal CREATE TABLE statement might look like:
CREATE TABLE users (
user VARCHAR(16) PRIMARY KEY,
password VARCHAR(32) NOT NULL
)
For the groups table, the access table is actually going to be a join table
between the users table and a table in which there is one row per group
if you have more per-group data to store; if all you care about is group
membership though, you only need this one table. The only constraints on
this table are that the user and group fields be NOT NULL.
A minimal CREATE TABLE statement might look like:
DESCRIPTION
Apache::AuthCookieDBIRadius is a module that subclasses Apache::AuthCookie
and is designed to be directly used for authentication in a mod_perl
server.
It allows you to authenticate against a DBI database -OR- your trusted NT domains
via a Radius server using a login webpage via AuthCookie.
It is a ticket-issuing system that looks up username/passwords in a DBI
database using generic SQL and issues MD5-checksummed tickets valid for
a configurable time period. Incoming requests with tickets are
checksummed and expire-time checked.
Upon failure, it then checks a Radius server for authentication.
(You do not need to run a Radius server to use this. Actually,
Radius authentication is commented out by default. Uncomment the
Radius lines in AuthCookieDBIRadius.pm if you intend to use this method
along with a DBI database. Most won't be using this method. You'll need to
get Radius authentication working first before using AuthCookieDBIRadius.
See Apache-AuthenRadius, Authen::Radius and http://www.funk.com/radius/.)
Included is a sample httpd.conf and login.pl for your review.
AUTHCOOKIE
Also included is a slightly customized AuthCookie.pm based on AuthCookie 3.0.
Replace with your existing AuthCookie.pm for added customized error messages:
# Please enter your username and password (default message).
# Incorrect Password.
# Incorrect Username (although some say this isn't a good idea, it can
be easily changed to Incorrect Login for the password and username).
# ERROR! Your session has expired, or your login does not have the proper
access level for this webpage.
# ERROR! Security error. Too many attempts (shared memory remembers how
many times the user has failed to login, locking them out after
X times).
# Internal Server Error (usually from an error in the configuration.
Error number will tell you exactly where you went wrong.)
generic_reg_auth_scheme.txt view on Meta::CPAN
AuthName AuthName
PerlAuthenHandler Apache::AuthCookieDBI->authenticate
PerlAuthzHandler Apache::AuthCookieDBI->authorize
Require [ valid-user, user username, group groupname ]
# you must set this.
PerlSetVar AuthNameDBI_DSN databasename
# all these are optional.
PerlSetVar AuthNameDBI_User username # default undef
PerlSetVar AuthNameDBI_Password password # default undef
PerlSetVar AuthNameDBI_UsersTable tablename # default 'users'
PerlSetVar AuthNameDBI_UserField fieldname # default 'user'
PerlSetVar AuthNameDBI_PasswordField fieldname # default 'password'
PerlSetVar AuthNameDBI_CryptType [ none, crypt, MD5 ] # default 'none'
PerlSetVar AuthNameDBI_GroupsTable tablename # default 'groups'
PerlSetVar AuthNameDBI_GroupField fieldname # default 'group'
PerlSetVar AuthNameDBI_GroupUserField fieldname # default 'user'
# dunno what this is.
DefaultTarget partial or full URL
You also need this to get people to log in (although I'm not exactly sure
why; I guess it's so that login() gets called, but why can't we check for
# key line must come first
PerlSetVar PortalDBI_SecretKeyFile /usr/local/apache/conf/site.key
PerlModule Apache::AuthCookieDBIRadius
PerlSetVar PortalPath /
PerlSetVar PortalLoginScript /login.pl
PerlSetVar AuthCookieDebug 1
PerlSetVar PortalDBI_DSN 'dbi:Pg:host=localhost port=5432 dbname=mydatabase'
PerlSetVar PortalDBI_User "database_user"
PerlSetVar PortalDBI_Password "database_password"
PerlSetVar PortalDBI_UsersTable "users"
PerlSetVar PortalDBI_UserField "userid"
PerlSetVar PortalDBI_PasswordField "password"
PerlSetVar PortalDBI_SessionLifeTime 00-24-00-00
<FilesMatch "\.pl">
AuthType Apache::AuthCookieDBIRadius
AuthName Portal
SetHandler perl-script
PerlHandler Apache::Registry
Options +ExecCGI
</FilesMatch>
<TR><TD colspan=2 align=center>
<!--login-->
<table>
<TR><TD align=right>
<B>Login:</B></TD>
<TD><INPUT TYPE="text" NAME="credential_0" SIZE=20 MAXLENGTH=50></TD>
</TR>
<TR>
<TD ALIGN=RIGHT><B>Password:</B></TD>
<TD><INPUT TYPE="password" NAME="credential_1" SIZE=20 MAXLENGTH=20></TD>
</TR>
</table>
<!--end login-->
</TD></TR>
<TR>
<TD COLSPAN=2 ALIGN=CENTER><INPUT TYPE="submit" VALUE="Continue"></TD>
</TR>
</TABLE>
</FORM>
<P>
<a href=>I need to request a new login</a>
<P>
<a href=>I forgot my password</a>
<P>
<a href=>I'm having problems with my existing login</a>
</body>
</html>
HERE
$r->no_cache(1);
my $x = length($form);
$r->content_type("text/html");
$r->header_out("Content-length","$x");
# $Id: schema.sql,v 1.1 2000/06/21 21:45:36 jacob Exp $
#
# Schema for creating the database tables for an authentication system.
CREATE TABLE users (
user CHAR(16) PRIMARY KEY,
password CHAR(24)
);
CREATE TABLE groups (
group CHAR(16),
user CHAR(16)
);
techspec.txt view on Meta::CPAN
$Id: techspec.txt,v 1.2 2000/04/05 19:02:49 jacob Exp $
Apache::AuthCookieDBI Technical Specification
* Description.
This module will allow cookie-based authentication backed by a DBI database,
using usernames and passwords for authentication.
* Authentication.
Authentication is based on a username and password. These are supplied in
plaintext by the user in a form submission through Apache::AuthCookie. These
are compared against values in a users table in a DBI database. The password
field in the database may be plaintext, or hashed with crypt() or md5_hex().
* Tickets.
When a user successfully authenticates, they are issued a cookie with a
session value. This value consists of a serialized version of
the userid, an issue time, an expiration date, and a two-round MD5 checksum
of the userid and times and a server secret key. This checksum
ensures that when the ticket is returned we can see that it has not been
tampered with since in order to generate the checksum you must have the secret