Authentication

From WeBWorK_wiki
Jump to navigation Jump to search

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 in global.conf and database.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.

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 in check_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.