Authentication
Contents
Authentication
By default, WeBWorK manages how users (students and instructors) authenticate to be allowed access to resources. There are a number of modules that allow WeBWorK to defer to another authentication source, e.g., so that it can use a central login service on a campus. The different implementations of such external authentication resources are indicated below. The remainder of this document describes generally how authentication is managed in WeBWorK, to inform how the existing modules work and provide some background information about how to set up a new one.
Existing Authen Modules
There are currently several WeBWorK authentication modules to allow it to use an external authentication system:
- Authen::Cosign The Authen::Cosign module is designed to allow authentication with a system that manages authentication by way of web-server level configuration. That is, in this case you must be able to tell your apache server that given directories (that is, the course directories) are protected by your authentication system, and then the web server takes care of the redirect that is required for the user to log in. Then if the user ever gets to WeBWorK we know that s/he has authenticated and have only to check that the user is in the correct course roster.
- Authen::LDAP This is "similar" to Cosign authentication, but in this case we have to do more work to figure out if the user is valid. This is illustrated in the explanation below.
- Authen::Moodle A module to allow authentication through Moodle. THIS AUTHENTICATION MODULE IS DEPRECATED. Mention here is retained as a code example only.
- This module assumes that a copy of moodle (version 1.6 I believe) uses the same mysql installation as WeBWorK. Authentication is accomplished by redirecting the "user" table for WeBWorK to the "user" table of moodle. One must also configure WeBWorK to use the
sql_moodle
schema by setting the appropriate flags inglobal.conf
anddatabase.conf
. This authentication method is very fragile and requires specific versions of moodle and WeBWorK. It is no longer supported. The current interoperability of moodle and WeBWorK described at Moodle_Integration does not use this module. Instead the moodle and webwork applications communicate via a webservice using SOAP or XMLRPC protocol.
- This module assumes that a copy of moodle (version 1.6 I believe) uses the same mysql installation as WeBWorK. Authentication is accomplished by redirecting the "user" table for WeBWorK to the "user" table of moodle. One must also configure WeBWorK to use the
WeBWorK's Authentication Process
WeBWorK controls authentication to the homework system in the Authen.pm module. To verify that a user is authorized to access the WeBWorK system, the module checks, in order
$self->get_credentials; $self->check_user; $self->verify_normal_user;
or, if the user is a practice (guest) user, the last is replaced with
$self->verify_practice_user;
These methods, respectively, get the user's login name and authentication credentials, then check that the user is a valid WeBWorK user, and finally, checks that the authentication credentials match those for the user.
The verify_normal_user
routine is also configured to easily allow separate site authentication: in pseudo-code, it checks authentication with
if ( $self->valid_webwork_session() ) { return 1; } else { if ( $self->checkPassword( $user_id, $password ) ) { $self->create_session(); return 1; } elsif ( $self->can( site_checkPassword ) && $self->site_checkPassword( $user_id, $password ) ) { $self->create_session(); return 1; } else { return 0; } }
Accordingly, if we want to develop a new authentication module, we need to replace one or more of these methods in a subclass of the Authen module. This is considered below in the examples.
Examples
Authen::Cosign
Cosign (Collaborative single-sign on) is an open source project originally designed to provide the University of Michigan with a secure single sign-on web authentication system. It is part of the National Science Foundation Middleware Initiative (NMI) EDIT software release.
Cosign runs as an apache module (as does WeBWorK), so that authentication through Cosign is based on the location that is being accessed. This means that if we configure the apache server to recognize a WeBWorK course as being Cosign protected the web server will automatically redirect the user to the Cosign login if the user isn't authenticated with Cosign, and won't allow the user to access the resource if the user isn't authenticated. This makes using Cosign pretty easy: we tell our webserver to protect a WeBWorK course with Cosign, and then if the user actually gets to the course we know that they've already successfully authenticated against the central login server.
Thus, in our Authen::Cosign.pm module, we have only two small changes:
- In
$self->get_credentials
we have to get the user's login information from Cosign, not from a WeBWorK form; - In
$self->site_checkPassword
we just return 1 (true), because we know that if the user checked correctly incheck_user
and we've entered the WeBWorK system, then the user has both successfully authenticated against the central server and is a valid WeBWorK user.
We don't have to change the checkUser
method, but we do have to be sure that the classlist in the WeBWorK class is kept up-to-date.
get_credentials
The simplest get_credentials
method for Cosign authentication gets the user's login name from the environment (Cosign assures that this is set), and then forces verify_normal_user
to fall through to the site_checkPassword
routine:
sub get_credentials { my ($self) = @_; my $r = $self->{r}; my $ce = $r->ce; my $db = $r->db; # # Cosign makes sure that the environment variable REMOTE_USER # is set for us. if ( defined( $ENV{REMOTE_USER} ) ) { $self->{user_id} = $ENV{REMOTE_USER}; $self->{r}->param("user", $ENV{REMOTE_USER}); } else { return 0; } # we set the external auth parameter so that Login.pm knows # what error to return if there's a check_user failure. $self->{external_auth} = 1; # # set other parameters to force a password check that will # call site_checkPassword $self->{session_key} = undef; $self->{password} = 'youWouldNeverPickThisPassword'; $self->{credential_source} = params; # return 1; }
site_checkPassword
Because of the availability of the site_checkPassword
function in verify_normal_user
, we just have to provide such a routine:
sub site_checkPassword { my ( $self, $userID, $clearTextPassword ) = @_; # if we got here, we know we've already successfully authenticated against # Cosign! return 1; }
The actual code in Authen::Cosign.pm is slightly more complicated, but not much; see the module in the CVS for comparison.
Authen::LDAP
LDAP is a protocol to allow networked access to directory information (e.g., usernames and passwords). Therefore, to use it to authenticate WeBWorK users, we have to replace the checkPassword
or site_checkPassword
routines to query the LDAP server to find if the user is valid.
The simplest LDAP authentication system would therefore not have to change either of the get_credentials
or check_user
methods, and could leave checkPassword
as well as long as the LDAP password didn't match the password in WeBWorK. This implementation would have
sub site_checkPassword { my ( $self, $userID, $clearTextPassword ) = @_; if ( $self->ldap_authen_uid( $userID, $clearTextPassword ) ) { return 1; } else { return 0; } }
and then the routine ldap_authen_uid
would check against the LDAP server. Alternately, we might want to first authenticate against LDAP, and if that fails, authenticate against the standard WeBWorK passwords. This requires changing checkPassword
, as suggested by the following example.
sub checkPassword { my ( $self, $userID, $clearTextPassword ) = @_; if ( $self->ldap_authen_uid( $userID, $clearTextPassword ) ) { return 1; } else { return $self->SUPER::checkPassword($userID, $clearTextPassword); } }
Note that in this case we can omit the site_checkPassword
method. If we didn't want to authenticate against the WeBWorK database, the call to $self->SUPER::checkPassword
could be replaced with a return 0
to force failure in that case.
In either case, the ldap_authen_uid
routine must check the user with the LDAP server. For the actual (somewhat more complicated) implementation of checkPassword
, and the code for ldap_authen_uid
, see the module in the CVS.
Authen::MercedCAS
This section is underdevelopment.
This authentication system determines if a user is correctly authenticated by querying a central authentication server (CAS). The server responds to a HTTP request with a "ticket" that gives the login and authentication data. Thus, to use this, we have to change get_credentials
to redirect to the CAS on initial entry, and to check the input ticket if we enter with a ticket. Once the user has been authenticated, we fall back to WeBWorK's session management rather than querying the CAS every time.
Thus, we have for get_credentials
sub get_credentials { my ($self) = @_; my $r = $self->{r}; my $ce = $r->ce; my $db = $r->db; # if we come in with a user_id, we're in the loop where we # use WeBWorK's authentication, and so just continue # with the superclass get_credentials method. if ( $ce->{cosignoff} || ( $r->param("user") && ! $r->param("force_passwd_authen") ) ) { return $self->SUPER::get_credentials( @_ ); } else { my $ticket = $r->param('ticket') || 0; if ( $ticket ) { my ($error, $user_id) = $self->check_cas($ticket); if ( $error ) { $self->{error} = $error; return 0; } else { $self->{'user_id'} = $user_id; $self->{r}->param("user", $user_id); # set external auth parameter so that Login.pm # knows not to rely on internal logins if # there's a check_user failure. $self->{external_auth} = 1; $self->{session_key} = undef; $self->{password} = "youWouldNeverPickThisPassword"; $self->{login_type} = "normal"; $self->{credential_source} = "params"; return 1; } } else { # if there's no ticket, redirect to get one # my $this_script = "http://" . $ENV{'SERVER_NAME'} . $ENV{'REQUEST_URI'}; my $go_to = "$CAS_SERVER?renew=true&service=$this_script"; $self->{redirect} = $go_to; return 0; } } }
The method check_cas
sends a query to the CAS server to verify that the ticket is valid.
Then, because we know that to get the user's login name in get_credentials
we had to successfully authenticate against the CAS, we can add a site_checkPassword
routine to pass through the authentication.
sub site_checkPassword { my ( $self, $userID, $clearTextPassword ) = @_; # if we got here, we know we've already successfully authenticated against # the CAS return 1; }
If we wanted to be more careful that the password we assigned in get_credentials
doesn't accidentally match any password in the WeBWorK database, or if we wanted to fall back to WeBWorK password authentication as in the LDAP module above, we would change checkPassword
as well.
The method check_cas
is in the Authen module, available here soon.