NAME

SimpleCAS - Basic CAS authentication for php


DESCRIPTION

SimpleCAS is a php class defining a library of methods for doing basic CAS authentication, hopefully in an easy to use manner. The inner workings are based on phpCAS(Comparison to phpCAS), but it is hoped to make it easier to do basic CAS authentication.

CAS Overview

The Central Authentication Service, hereafter simply refered to as CAS, is a framework originally developed at Yale University to provide a single sign-on solution for multiple web applications. It is currently maintained by the JA-Sig (http:www.ja-sig.org) consortium.

CAS is heavily influenced by Kerberos in its design, and involves the use of a trusted CAS server/web application. Users of web applications present their credentials (e.g. username and password) to the CAS server, which then sends back a service ticket which gets presented to the client web application. The client application then communicates directly with the CAS server to validate the the service ticket, and if successful knows that it has an authenticated session with a particular user.

On the first visit to a client application, the client application redirects the user to the CAS server, where the user will typically log in, and upon successful login, the CAS server will redirect the user back to the client application with this service ticket. The client then sends an HTTPS query back to the CAS server with the ticket, and the CAS server will validate the ticket (assuming it is valid) and provide the username associated with it.

This initial contact then generated two redirections and a request and reply from client to CAS server. While not horrible as a one-shot authentication sequence, it is a bit time consuming if required for every request to your application, so CAS clients typically make use of web sessions to cache authentication information, so that on subsequent visits to your application in that session the application recognizes that it authenticated you recently and typically does not repeat the authentication process.

CAS service tickets are single use affairs. CAS will also typically install a ticket-granting ticket (TGT) cookie on the users machine, and normally will be able to authenticate the user based on the value of this cookie. This enables some single sign-on functionality for web applications authenticating off the same CAS server; if the user uses your CAS enabled application first, when they present username and password to CAS server, it installs this TGT cookie on their machine. When they then visit my application the first time, and I redirect them to the CAS server, the CAS server can silently log them in using this TGT cookie, and the user gets redirected back to my application possibly without even realizing they were redirected in the first place.

CAS has two non-standard modes of authentication related to this TGT cookie, in addition to what was described above. First of all, sometimes you do not wish to let the user log in on the basic of a cookie, which may have been left on someone else's machine by mistake. In this situation, CAS supports what it calls renew mode, which will ignore the TGT cookie and require the user to enter their username and password. SimpleCAS refers to this as forced mode.

At the other end of the spectrum, perhaps your page is open to the public, but has enhanced capabilities for logged in users. In this case, when the user first visits your application, you might not want to force them to log in if they are not logged in with CAS, but would like to if they have a TGT cookie. CAS supports this with its gateway mode, which will redirect back to the client application with a service ticket if it can authenticate the user based on the TGT cookie (so does not involve any user interaction in general), or redirect back without a service ticket if it cannot. SimpleCAS refers to this as optional mode.

CAS also has some support for proxy tickets, whereby the CAS server can validate the user of your application to another application, allowing your application to act on the user's behalf in that other application, but SimpleCAS does not support any of this. phpCAS has some support for that if you need it.

Requirements and Installation

SimpleCAS is written for PHP4 (tested on version 4.4.4 to be exact), though it probably should run on anything at 4.3.3 or above (4.3.3 is required for session_regenerate_id with cookie support). I have included a PHP5 constructor, but have done no testing on PHP5. My understanding is that the XML parsing library on PHP5 is different than on PHP4, so you may something like domxml-php4-php5.php available with phpCAS.

SimpleCAS requires php to be built with DOM and cURL support.

To install SimpleCAS, simply copy the SimpleCAS.inc file somewhere in your include path, and remember the location so you can include/require it.

I strongly recommend that you create a simple subclass of SimpleCAS setting your site's preferred defaults, particularly for things like the CAS server settings. Or multiple subclasses if have a development and production server.

Basic Usage

The first step to using the package is to include or require the file SimpleCAS.inc in whatever directory you installed it to. SimpleCAS is completely OO in design, and so the next step is to instantiate an object of that class with the appropriate options. SimpleCAS has a lot of options in order to make it reasonably flexible, but most of them are optional and have reasonable defaults. The primary required options are those that tell it what CAS server to use.

Because SimpleCAS will do HTTP redirects to the CAS server if required, it is important that the object constructor (and authenticate methods, if calling separately) are invoked before any output HTML is written. Since normally you want to authenticate the user before starting to output anything anyway, this is usually not a big constraint.

For many applications, the above steps are all you need. By default, SimpleCAS starts PHP session handling and will invoke the authenticate method in the constructor, using the supplied options, and the authenticate method will take care of checking if the user already logged in during this session, redirecting to the CAS server if needed, etc. Unless the authenticationOptional option is set (causing SimpleCAS to use CAS gateway mode), if the script proceeds past the authenticate method (and hence the constructor if called from there), the user has been authenticated and his or her username can be obtained from the username method. In authenticationOptional mode, the username method will return an empty string if the user is not authenticated.

Thus, many applications can get CAS authentication with something as simple as:

        <?php
                require_once('/libs/SimpleCAS.inc'); #Or wherever you put it
                $opts = array( 'casVersion' => SCAS_CAS_2_0,
                        'casServer' => 'cas.server.com',
                        'casPort' => 443,
                        'casPath' => 'cas',
                        'casCAPath' => '/usr/local/ssl/certs');
                $cas = new SimpleCAS($opts);
                $user = $cas->username();
        ?>
        <p>Hello, <?= $user ?>

Indeed, since all of the options given above are related to identifying the CAS server, and typically this will be common to most if not all scripts at a site, it is recommended that you make a subclass of SimpleCAS for your site similar to:

        <?php
            require_once('/libs/SimpleCAS.inc'); #or wherever you put it
        
            class SimpleCAS_Local extends SimpleCAS
            {   function _defaultSettings()
                #Set defaults to our CAS server
                {       if ( ! IsSet($this->casVersion) )
                                $this->casVersion = SCAS_CAS_2_0;
                        if ( ! IsSet($this->casServer) )
                                $this->casServer = 'cas.server.com';
                        if ( ! IsSet($this->casPort) )
                                $this->casPort = 443;
                        if ( ! IsSet($this->casPath) )
                                $this->casPath = 'cas';
                        if ( ! IsSet($this->casCAPath) )
                                $this->casCAPath = '/usr/local/ssl/certs';
                        #Add any other default settings you wish to add/override
                        #Get default defaults
                        SimpleCAS::_defaultSettings();
                }
            }
        ?>

You can also add any other default options you wish to override. The full list of options is given later (SimpleCAS Constructor Options). The first example can then be simplified to something like:

        <?php
                require_once('/libs/SimpleCAS_Local.inc');
                $cas = new SimpleCAS_Local();
                $user = $cas->username();
        ?>
        <p>Hello, <?= $user ?>

Can't get much simpler, can it?

Of course, sometimes you may wish to make the authentication explicit. Just pass the option doNotAutoAuthenticate with a true value to the new, and it will not automatically authenticate. You can call the method authenticate or the more specific methods authenticate_normal, authenticate_forced, or authenticate_optional to make the authentication call explicit. The last three methods will authenticate in the standard way, or with CAS renew or gateway flags set. In all cases, the user's session is checked first for authentication information, the presence of a service ticket is checked, and if not the user is redirected to the CAS server, which might authenticate the user based on his TGT cookie (unless using authenticate_forced/renew mode) or ask for an username and password (unless using authenticate_optional)/ gateway mode). Note that authenticate is just a wrapper method, calling one of the other three depending on the options set. If authenticate_normal or authenticate_force return, the user is authenticated.

Logging out is also easy to script. The logoutSession method deletes the authentication information in the user's session, and the logoutCAS method causes CAS to delete the user's TGT cookie. The wrapper method logout will invoke logoutSession and optionally logoutCAS.

Conceptually, logging out is a bit more complicated, and often will not behave as you might expect. It is usually recommended that an application should NOT invoke logoutCAS, as this would defeat the single sign-on feature of CAS. However, exceptions to this recommendation exist, particularly if the user might be on a public kiosk type machine. Note that logoutCAS does not destroy authentication information in the session, so the user will still be authenticated to the application if logoutCAS is called alone, at least until authentication information in session expires or is deleted.

On the other hand, logoutSession alone will not remove the TGT cookie, but will destroy the authentication information in the session. But again, the behavior is typically unexpected, because when the user next tries accessing your application, they will be silently redirected to CAS, which will likely silently authenticate them via their TGT cookie, and the user will once again see the application they just logged out of, with authentication credentials, and without re-entering their username and password.

So unless your application has a natural end of session point (e.g. the application is for submitting purchase requests and the user just finished submission of one), it may be more confusing to include a logout link than omitting it. If you have a natural end point, you may wish to silently logout the user's session (if they have, e.g., another purchase request to make, they will get silently logged in again).


API Details

SimpleCAS is entirely OO, defining the class SimpleCAS. Since it is PHP4 compatible, the methods, etc. are not really public or protected, but for API purposes methods beginning with an underscore are considered protected and may be subject to change in terms of the interface without warning in subsequent versions. Methods starting with a letter are considered part of the public API and the calling conventions will not change in this major version except in a backwards compatible manner.

Most of the behavior of SimpleCAS can be controlled via options given to the constructor, but some situations may call for subclassing and overriding specific methods. As such, we intend to discuss both the public and protected methods below, though protected methods will be discussed more briefly and one should refer to the code for more detail.

Class Constructor and initialization methods

As an OO library, one of the first requirements is to construct an instance of the class. This is actually done in the protected _constructObject method. The standard PHP4 and PHP5 constructors ( SimpleCAS and __construct, respectively) are simply wrappers around this.

The standard and the protected constructors all take a single argument, and array of options. Options should be given as key-value pairs, with the key of the array being the name of the option, and the value the value to use.

SimpleCAS Constructor Options

Option names are case insensitive, although the data members they effect are case sensitive, using the case shown below.

CAS Server Specification Options

The following options specify which CAS server to use and related parameters. It is recommended that you subclass SimpleCAS and default all of these for your CAS server to simplify use of the library. The defaults listed are for the main SimpleCAS class library.

casServer
This should be set to the hostname or IP address of the CAS server to use. No default. Required.

casPort
This should be set to the port number the CAS server is listening on. Defaults to 443 (standard https port).

casPath
This should be set to the path portion of the URL for the CAS server. Defaults to the empty string.

casVersion
This specifies the version of the CAS protocol to use. The constants SCAS_CAS_1_0 and SCAS_CAS_2_0 exist to specify CAS protocol versions 1.0 and 2.0, which are the only supported protocols for this client at this time. Default is SCAS_CAS_2_0.

casCAInfo
The path to a file containing the certificate for the CA signing the CAS server's certificate, for use when casVerifyPeer is set. This corresponds to the CURLOPT_CAINFO option in the cURL library. Typically only one of casCAInfo or casCAPath would be used.

casCAPath
The path to a directory containing certificates for the CA signing the CAS server's certificate, for use when casVerifyPeer is set. This corresponds to the CURLOPT_CAPATH option in the cURL library. Typically only one of casCAInfo or casCAPath would be used.

casVerifyPeer
At one point in the CAS validation process (CAS Overview), the SimpleCAS library needs to send an HTTPS request to the CAS server to validate a service ticket and read its response. If this option is true, we require the certificate used by the server for the response be signed by a CA which we can verify (using either default CA certificates for cURL or ones pointed to by casCAInfo or casCAPath options). Failure to verify certificate will prevent successful authentication. Corresponds to the CURLOPT_SSL_VERIFYPEER option in the cURL library. Default is TRUE. Setting this to FALSE may also require downgrading the value for casVerifyHost. (See also Security Concerns and (Comparison to phpCAS)

casVerifyHost
When examining the certificate from the CAS server, the certificate should have the name of the service it is for (the common name field in the certificate). This option, which corresponds to the CURLOPT_SSL_VERIFYHOST option in the cURL library, determines how stringently it should be checked. A value of 0 means no checking is done, a value of 1 simply checks that the certificate has a common name field, and a value of 2 verifies that it matches the server's name. Default is 2. This may need to be lowered if the option casVerifyPeer is false. (See also Security Concerns and (Comparison to phpCAS)

Options controlling constructor behavior

Applications using SimpleCAS require sessions to be enabled, and presumably will want to authenticate the user. Since the constructor for the SimpleCAS object typically needs to be invoked early in the script, and since many applications will want to start the session handling and do simple authentication early on as well, SimpleCAS rolls this functionality into the constructor by default. But you can turn it off.

autoStartSession
If this option is set, the constructor will invoke the protected method _session_init after processing the options. The version of this in the main class just invokes the session_start function. Default is TRUE. If you disable this, you need to start session handling before using any SimpleCAS authentication methods.

doNotAutoAuthenticate
If this option is set, the constructor will invoke the method authenticate after processing options (and starting session handling if requested). Default is TRUE. If you disable this, you will probably want to invoke one of the authentication methods manually.

Options controlling authenticate and logout methods

The following options control the behavior of the authenticate and logout methods

forcePassword
If true, authentication will be done using CAS renew mode. The authenticate method will pass the buck to authenticate_forced, which will require a session with recent authentication information obtained from a renew mode service ticket, or a renew mode service ticket, or will redirect to CAS in renew mode. Default is FALSE.

authenticationOptional
If true, authentication will be done using CAS gateway mode. The authenticate method will pass the buck to authenticate_optional, which will check for a session with valid authentication information, or process a service ticket if provided, or redirect to CAS in gateway mode. Default is FALSE. This option is ignored if forcePassword is set.

authOptDeltaTime
When authenticationOptional is true, or invoking authenticate_optional (i.e. when using CAS gateway mode), if you do not get authenticated, you get redirected back to the page without authentication credentials. Without some hack, this can result in an infinite redirection loop. Even without worrying about the infinite loop, every visit would try to redirect to the CAS server, slowing things down, despite the fact that it is unlikely that you logged in during the 0.1 seconds from your last visit. The authOptDeltaTime option sets a minimum period (in seconds) between revisiting the CAS server in authenticationOptional mode. It's value must be greater than zero, and is a compromise between how much delay is added if the user does not log in, and how long it takes the application to detect if they do log in. Default is 300 seconds (5 minutes).

sessionVarNameOptTstamp
This option defines the name of the session variable (key in the $_SESSION hash) wherein we store the timestamp of our last unsuccessful visit to the CAS server in authenticationOptional mode for comparison to the authOptDeltaTime option above. Default is __authinfo_optTstamp. Should you require use of the default name for some other library, you can change what SimpleCAS uses. In theory, it can also conflict with other applications on the same server using the same session_name and SimpleCAS, but this conflict is probably not problematic.

casLogoutOnLogout
If this is true, calling the logout method will invoke both the logoutSession and the logoutCAS methods (if false, only logoutSession is called). This causes a redirect to the CAS server which will expunge the user's TGT cookie. In most situations, this is not advisable and may be considered rude behavior on the part of your application. Default is FALSE.

Options controlling session based authentication information

SimpleCAS uses the PHP session control to cache authentication information and thereby speed up response times (since otherwise every request would involve two redirects to the CAS server plus a service ticket validation request and response). However, the use of sessions does increase the security risk (see Security Concerns), as your identity basically becomes your session id.

SimpleCAS tries to reduce the threat of session ID hijacking by optionally regenerating session IDs every time you authenticate, and limiting the lifetime of the authentication information in the session (this allows the authentication information to expire well before PHP expires the session as a whole). By combine the expiration of authentication information with session ID regeneration and possibly tacit reauthentication via CAS and the TGT cookie, you can limit the valid lifetime of a session ID thereby reducing chances of session id hijacking.

SimpleCAS also supports requiring that session authentication information is only accepted if coming from the same IP address that logged in (or at least presented the service ticket to your application); this can cause problems with some ISPs (I believe AOL in particular) that do proxying. Furthermore, setting the expirations to happen too frequently can place a load on the application web server, the CAS server, and have significant impact on the response time of your application. So you may need to tweak these settings to get the best compromise between security and usability for your application.

sessionName
This sets the name of the session (i.e. the name of the cookie storing the session id), assuming you are using &autoStartSession;. Will also affect session name if using &authChangeSessionIDs;.

sessionVarName
This defines the name of the session variable (key in the $_SESSION hash) used to store all the authentication information related to the user's authentication status. The default is __authinfo, but you can change it if it conflicts with another library or application using the same session_name.

authInfoExpiry
This sets the maximum time (in seconds) that the authentication information in the session will be considered valid. It must be at least 1 second. After that time, the authentication information will not be considered valid, and all the authentication methods will redirect to the CAS server unless a valid service ticket was presented. Only the authentication information in the session will be invalidated, any other session information should still be present. The default is 1800 seconds, or 30 minutes. Expiration is from when the authentication information was first created, which is when a service ticket presented to the application is validated. Note that while you can set this value very high, it will not prevent PHP from deleting old session data, which would delete authentication information as well. This simply allows you to expire authentication information more rapidly then less sensitive data.

authInfoExpiryLastUse
This is similar to the authInfoExpiry option, but the clock is reset every time the user visits a page (or more precisely, a page in your application making a call to one of the authentication methods). Both this and authInfoExpiry operate independently. The default is 1800 seconds, or 30 minutes (effectively nullifying this unless authInfoExpiry was increased). You can reduce this to 180 (3 minutes), for example, to allow authentication information to remain valid for up to 30 minutes (authInfoExpiry) but only the user is active (accesses one of your pages at least every 3 minutes).

authInfoSameIP
If this is set, the authentication information stored in the session is only valid as long as the user is accessing your application from the same IP address from which the authentication information was created (i.e. the IP address which presented the service ticket which was validated to create the authentication information). The default is true. This can make session hijacking much more difficult, but can cause problems if the user is going through a proxy (e.g. AOL users). Note that only the authentication information is invalidated, so if the user's machine uses DHCP and gets new IP addresses every day, this typically is not a problem, as you generally want the user to log in again at least daily anyway.

autoChangeSessionIDs
If this is set, every time a hash of authentication information is created in the session (i.e. every time SimpleCAS validates a service ticket that was presented to it), we regenerate the session id and delete the old session. This should not affect any data stored in the session, just the session id. It will involve another call to the _session_init method, and you may need to override that in a subclass if you are doing non-standard things when creating your session. It should greatly increase the difficulty of hijacking a session, and if combined with authInfoExpiry or similar, and silent CAS authentication from a TGT cookie, can be used to ensure session ids get changed periodically without the user noticing.

forceExpiry
When forcePassword is set or authenticate_forced is used (i.e. when using renew mode in CAS), SimpleCAS notes in the authentication information that the information was based on a recent direct entering of the password, and that flag allows subsequent calls to authenticate_forced to accept the session cached credentials. This option, which should be a time duration in seconds, allows this flag to expire before the rest of the authentication information. It defaults to 1800 seconds or 30 minutes, which effectively disables it unless authInfoExpiry was raised. Like authInfoExpiry, the expiration time is measured from the creation of the authentication information (when a service ticket was presented and validated).

forceExpiryLastUse
This is similar to forceExpiry except that the expiration time is measured from the last time the user visited a page in your application that called the authenticate_forced method (either directly or through authenticate method with forcePassword option set). It defaults to 1800 seconds or 30 minutes, which effectively disables it unless authInfoExpiry, authInfoExpiryLastUse, and forceExpiry all have been raised.

The constructor calls the methods _initObject, _defaultSettings, and _checkRequiredArgs in order to do the option processing. You can override these in a subclass to create new options or change default values. It is recommended that you create your own local subclass of SimpleCAS which overrides the _defaultSettings method to default to your CAS server, as well as any other settings you wish to change.

When SimpleCAS starts a session (in the constructor if autoStartSession is set, or when regenerating the session id if authChangeSessionIDs is set), it invokes the method _session_init. You can override if you need to do anything special. _session_init handles setting the session name to &sessionName;, and any overridden version must take over that responsibility as well.

When SimpleCAS uses cURL to ask the CAS server about the validity of a service ticket, it will invoke the protected method _curl_init($url) with the name of the URL (the CAS server with some options) we are visiting. You can override this if you wish to add or alter curl options. Note that the standard version of this handles the casCAInfo, casCAPath, casVerifyPeer, and casVerifyHost options for SimpleCAS, and takes care to set CURLOPT_RETURNTRANSFER and CURLOPT_HEADER to 1 as is expected by the calling method. If you do not call the parent SimpleCAS version at the end of your version, you can break things.

Authentication and Logout Methods

The method authenticate is the basic authentication method, and it is invoked by the constructor unless the option doNotAutoAuthenticate is set. It is actually nothing but a simple wrapper delegating to one of authenticate_normal, authenticate_forced, or authenticate_optional depending on the settings of options forcePassword and authenticationOptional. The return value is the username of the authenticated user, or an empty string if there is no authenticated user (which can only occur if authenticationOptional is set). If both forcePassword and authenticationOptional are set, the authenticationOptional setting is ignored and authenticate_forced is invoked.

All of the authentication methods (authenticate, authenticate_normal, authenticate_forced, and authenticate_optional) need to be called before any HTML is written out, as they all may need to issue an HTTP redirect header to go to the CAS server.

For an actual login process (not authenticating based on session information), the user will access your application page twice (an initial time, which will get redirected to the CAS server, and when the CAS server redirects back to your application with a service ticket after authenticating the user). You must take care to use the same authentication mode options for each visit; the behavior of the SimpleCAS library is not defined if you mix authentication mode options between the initial visit and the redirected visit with the service ticket. For example, calling authenticate_optional on the initial and then calling authenticate_forced when presented the ticket will always fail; doing authenticate_normal with authenticate_forced processing the ticket might fail or succeed. (It is acceptable to use different authentication modes depending on what the user is requesting to do, but you should ensure that the same mode is used for the initial call to the authentication method and on the visit after the redirect. It is also important to realize that the redirection from the CAS server will not have any POST variables, and only the ticket GET variable, so any state information used to determine what the user was requesting needs to be put in a session or other persistant storage.)

In all cases, the authentication method first checks for authentication information in the user's session, using the is_authinfo_valid method. This method takes a single boolean parameter, $forced, which if set to true means it should validate for forced or renew mode. The parameter $forced is set true when invoked from authentication_forced (or authenticate when forcePassword is set). is_authinfo_valid returns true is the authentication information in the session exists and is valid.

If is_authinfo_valid returns true, the invoking authentication method will obtain the username from the authentication information (using the username method) and return it. If is_authinfo_valid returns false, the authentication method will then check for a valid service ticket, using the CAS renew mode in authenticate_forced (either called directly or from authenticate due to forcePassword being set).

If a valid service ticket was presented, the authentication method will generate authentication information in the session. If autoChangeSessionIDs is set, this will involve the generation of a new, random session id to thwart session hijacking. The username is then obtained and returned.

If no valid service ticket was presented, the methods authenticate_forced and authenticate_normal, and therefore authenticate if authenticationOptional is not set, will redirect the user to the CAS server's login page, in CAS renew mode for authenticate_forced/ forcePassword mode. Thus, in these cases the methods will not return unless the user is authenticated.

For authenticate_optional (called directly or from authenticate due to authenticationOptional), the situation is a bit trickier. If we blindly redirect at this point, an unauthenticated user would get redirected to CAS, then redirected back to our application without a ticket, then redirected to CAS, and back to us again, ad infinitum (or until the browser gets fed up) with the user seeing nothing. To prevent this infinite loop, and to speed up future requests, we check for a session variable (named by the sessionVarNameOptTstamp option) with a timestamp for our last redirection to the CAS server, and if it exists and is within authOptDeltaTime seconds of the current time, we just return an empty string to signify the user is not logged in. Note that this means this method can return without the user being authenticated, and also means that authenticate and the constructor may do so depending on SimpleCAS option settings. Increasing authOptDeltaTime will improve performance slightly, but also mean it can take longer to recognize that the user has logged into an application using CAS (remember, they can authenticate to your application without them typing their username and password after your application redirects them to the CAS server).

The is_authinfo_valid method uses the following criteria to establish the validity of the authentication information stored in the session. First, the session variable holding the information must exist, and have a valid structure and content. If the option authInfoSameIP is set, SimpleCAS requires that the IP address of the current request matches the IP address for which the authentication information was created (the IP address for the request which gave us the service ticket which we validated to authenticate the user). This enhances security, but can cause problems if user is going through a proxy (i.e. AOL users).

is_authinfo_valid then requires that the authentication information be sufficiently recent, based on authInfoExpiry which measures from creation of the information and on authInfoExpiryLastUse which measures from the last time the user accessed the application (more precisely, the last time an authentication method was called on that session).

If any of the above tests fail, any authentication information that exists is wiped out and is_authinfo_valid returns false. If all of the above tests were passed, and the $forced parameter is false, is_authinfo_valid will return true (actually, the username of the authenticated user).

If the $forced parameter is set (as would be the case if authenticate_forced or authenticate with the forcePassword option were called), additional tests are run. First, the existance of a flag marking that the authentication information was created by a call to authenticate_forced (directly or indirectly via authenticate). If so, another round a freshness tests are run. First, the age of the authentication information (from creation time) must be within forceExpiry seconds of the current time. Second, the user must have accessed a forcePassword mode page (more precisely, a page which invoked the authenticate_forced method, directly or indirectly) within forceExpiryLastUse seconds. If all of the above tests were passed, the is_authinfo_valid method returns true (the username of the authenticated user>). Otherwise, it deletes the forced flag and returns false. Note that calling is_valid_authinfo with $forced set to true will not clear out all the authentication information if only the additional tests from the $forced setting fail, but will otherwise.

The username method can also be used to determine if an user is authenticated, and will return the username if authenticated and FALSE otherwise.

As discussed earlier (see CAS Overview), logging out with CAS is somewhat odd. You generally do not want to have CAS delete the TGT cookie, as that can interfere with other CAS authenticated applications the user might be running, and defeats the single sign-on feature of CAS. And logging out of your application (i.e. deleting the authentication information stored in the session) can behave unexpectedly as the user may get silently logged back in due to the TGT cookie. However, depending on your security situation and whether users are typically logged in from private or public machines, various levels of logging out may be advisable.

SimpleCAS provides several logout methods. unsetAuthInfoForced is the least of them, and it basically just relinquishes any additional privileges from having used authenticate_forced or authenticate with the forcePassword option; in actuality it just removes that $forced flag from the session, effectively similar to manually expiring via forceExpiry.

logoutSession deletes all authentication information from the session, which means that the next time the user views your application page, he will be redirected to the CAS server for authentication (but if he has a valid TGT cookie, he will be redirected back with a service ticket without having to type in his username and password unless using forcePassword mode). This will not delete any other information in the session.

You can also simply delete the entire session if you wish, using the standard PHP function session_destroy(), which would behave similarly to logoutSession except all session information is lost.

There also is a method logoutCAS, which will redirect the user to the CAS server to delete their TGT cookie. Note that this method does not clear the authentication information out of the session, you need to call logoutSession before logoutCAS to do that (as logoutCAS will not return).

As a convenience, the method logout is also provided. It will always do a logoutSession. This will be followed by a session_destroy if the option destroySessionOnLogout is true. This will then be followed by a logoutCAS if casLogoutOnLogout is true.

Expired session data is not considered an error and is not logged (except at a debug level). IP mismatches in sessions are also only logged at a debug level for now (assuming authInfoSameIP is true), but that may change. Basically, errors that are likely to occur normally are only logged at a debug level. Errors that might be interpretted as attacks get logged at warning or higher levels.

Miscellaneous Methods

The username method returns the username of the currently authenticated user, or the null string if no one is authenticated.

The method myURL returns an absolute URL to the current page. It is basically $_SERVER['SERVER_NAME'] followed by $_SERVER['PHP_SELF'], and prefixed by either http:// or https:// depending on whether connected over SSL. The whole URL is passed through htmlentities first to avoid possible XSS attacks through the PATH_INFO portion of the URL; this may invalidate the URL in these cases, but should at least prevent XSS attacks. See http://blog.phpdoc.info/archives/13-XSS-Woes.html for details on this attack.

The package also includes some error logging routines. These were primarily written to report problems with the code, or with the inputs given the code. The standard versions just write to STDERR, which should get logged on the server somewhere. All of the routines go through the protected method _log_message, which in turn filters messages through the protected method _log_filter, which you can override in a subclass if different actions are needed.

The logging methods are public, just in case you wish to use in your application. They are error($msg), $warning($msg) and debug($msg, $level). The debug method is slightly more complicated, and only logs the message $msg if the specified $level is less than or equal to the data method $debugLevel in the SimpleCAS object invoking it. You can change the value of that method at anytime to vary the level of debugging. The code has some constants defined for debugging levels used in the library itself.

Some errors are too serious to continue, and the public method fatal_error($msg) can be used to deal with this. As with other error methods, this is used internally by the library, and you can override it in a subclass if desired (though you should ensure that it does not return). The standard fatal_error logs the $msg to STDERR (or wherever _log_message goes to), then prints a generic error HTML page to the user. The error page is actually printed by the protected method _fatal_error_html($msg), which you can override if you prefer a different error message. The $msg parameter is actually the message ( after being passed through htmlentities) that gets logged to the error logs, the standard version of _fatal_error_html simply ignores it (so as not to provide potentially dangerous information to hackers), but it is provided in case a subclass' version of this method wants to make use of it.


Comparison to phpCAS

This library is indebted to the ESUP-portail consortium for their phpCAS library, from which a number of lower level functions were borrowed. Having finished the acknowledgements, we move on to the more pressing issue: if phpCAS was already around, why create another CAS library for PHP. This section will try to provide a balanced comparison of the two.

The main motivation for creating SimpleCAS was to try to provide a simpler interface to CAS authentication. SimpleCAS strives to be higher level and more programmer friendly library than phpCAS if all you need is basic authentication. SimpleCAS tries to allow the programmer to focus on what is unique about his application, and relegate all the CAS authentication stuff to a black box as much as possible. Thus in SimpleCAS, the programmer just needs to set some options (and since we try to provide reasonable defaults for most options, hopefully really the only options that absolutely need setting are the ones identifying the CAS server, and these can be set once with a local subclass of SimpleCAS), construct the SimpleCAS object, and maybe call an authentication or logout method or two.

Both SimpleCAS and phpCAS support basic authentication (by which I mean anything not involving proxy tickets) in both the standard CAS (with neither gateway or renew flags, or authenticate_normal mode in SimpleCAS parlance) and gateway ( or authenticationOptional) mode. SimpleCAS also supports CAS renew (or forcePassword ) mode, which I do not see support for in phpCAS.

phpCAS wins hands down in support for proxy tickets and the like as SimpleCAS has no support for that whatsoever.

I believe phpCAS supports PHP5. SimpleCAS has only been tested on PHP 4.4.4, although it is expected to work on anything PHP4 4.3.2 or later. It might work on PHP5, especially if you install the domxml-php4-php5.php file from phpCAS, but no guarantees.

SimpleCAS tries to provide a lot of security features (see Security Concerns) and make them easy to use. Security is always a compromise with usability, and while the default options try to provide a moderately secure environment, SimpleCAS tries to make it easy to tighten or lessen security as needed for your environment. In particular, it supports validation of the CAS server's certificate, IP restrictions and expiration of authentication information from sessions, and automatic regeneration of session ids. I am unaware of any of that in phpCAS.

SimpleCAS tries to be easily customizable for your environment. Many features are customizable via the options list passed to the constructor. It uses a completely OO interface, so that if the options list cannot be used to tweak things to your liking, you can subclass and override a method to do so (this assumes you take care not to break anything, but the comments in the code should alert you to any special requirements). Most of the places where I could conceive of someone possibly wanting to modify things have been factored out into fairly simple methods ( e.g. _fatal_error_html) to make this sort of overriding simpler. Indeed, the main reason that SimpleCAS includes code based on phpCAS rather than just be a wrapper around it was because I wanted to change the ticket validation to verify the CAS server's certificate, and there seemed to be no way to do this without redefining the whole ticket validation routine. If you have an option you think should be added (particularly if you have the code to do so), feel free to contact me about adding it to the main source tree.

phpCAS mentions that it has internationalization support. I am unsure how this comparison applies to SimpleCAS, as the only user visible output from it is the fatal error message page, and possibly a page that gets printed when we redirect to the CAS server if your browser is particularly stupid and does not redirect automatically. Comments in the code, documentation, and error messages are all only in English (although presumably the documentation could get translated). However, the _fatal_error_html$msg) and _httpRedirect($newurl) could be overridden in a subclass to provide for some internationalization.

So if you need to deal with proxy tickets, you should definitely choose phpCAS over SimpleCAS. Similarly if you are using PHP5 and want something that was actually tested on PHP5. Otherwise, I hope you will find SimpleCAS worth looking at.

Security Concerns

SimpleCAS, as a CAS client, is tasked with handling authentication. As such, any limitations in how it does that raises security concerns. The basic CAS security model (see CAS Overview) is based on the mutually trusted server concept; the user trusts that he can safely provide his authentication information to the CAS server, and your application trusts that an user is properly authenticated when the CAS server says so. This when the CAS client (e.g. your application) is presented a service ticket by the user, makes an HTTP request to the CAS server with the ticket included, and receives a positive response from the CAS server. SimpleCAS will store the fact that the user authenticated inside of a session, so that on future visits to the application the fact that the user has a valid session (or more precisely, the browser, typically via a cookie, provided a session ID for an actual session) and that session says the user visited our application recently and was authenticated, is considered the basis of authentication for this new visit. This process can raise some security concerns:

Does the service ticket presented actually belong to the user presenting it?

In the CAS system, this is a CAS server issue and the client is helpless. However, the CAS server keeps track of service tickets, and they can only be used successfully once. Since typically the user requesting the service ticket from CAS will be redirected back to your application with the ticket, which you presumably will contact the CAS server to validate against rather quickly, the valid lifetime of a service ticket is generally short. The CAS server will also expire tickets after a reasonably short period of time even if it was not presented for validation. These two factors should make stealing CAS service tickets rather unlikely.

Obviously, if the cracker actually has the user's username and password, he can obtain valid service tickets for that user from the CAS server itself. This, of course, is an inherent issue in any password based authentication scheme.

A related but more serious issue in CAS is that when the user presents a valid username and password to the CAS server, it tries to place a cookie containing a ticket-granting ticket (TGT) on the user's machine. This TGT cookie is the secret to CAS' ability to provide single sign-on, as in subsequent visits (or redirections) to the CAS server, it can authenticate the user based on the TGT cookie instead of requiring the user to enter his username and password. The TGT cookie must have a reasonably long lifetime to be useful, and theft of the TGT cookie is a serious concern, as that typically grants the possessor the ability to obtain credentials for user to which the TGT was given.

Clearly, the TGT cookie demands protection. Typically it is only stored while the browser is running, and users should be told to exit the browser when finished accessing secure applications. This is particularly problematic when applications are being accessed from shared or public machines. The application can help with this by providing a means for the user to logout from CAS (deleting the TGT cookie and invalidating the TGT), but it can be difficult explaining this to an user, especially since it is not recommended to logout of each individual application when finished (as this breaks the single sign-on feature).

Particularly sensitive applications can make use of the CAS renew (or SimpleCAS forcePassword) mode which will require that the service ticket be from an actual entering of username and password and not simply presentation of a valid TGT.

Is the service ticket valid response really from the CAS server?

When your application sends a service ticket to the CAS server for validation and receives a response saying that it is, can we trust that this response was really from the CAS server? Attacks exist wherein the cracker manages to confuse your application server so the network traffic intended for the CAS server goes to a machine the cracker controls instead. Since this transaction occurs over HTTP, there already is a well examined and standard means to protect against this, the use of SSL. The service ticket validation request is sent using SSL, and by default SimpleCAS will only accept a valid response from the server if the server's certificate is signed by a trusted CA and the server name matches the name on the certificate. This should provide a high level of protection against such attacks.

Does the session id presently actually belong to the user presenting it?

This is actually the Achille's heel of this whole authentication scheme. After the first visit, the user (or their browser) simple presents a session id to us, from which we lookup a session stored on our server, and see if they are marked as ``authenticated''. Basically, as long as someone has an ``authenticated'' session (that is a session marking the owner of the session as having been authenticated), stealing that session id would allow a cracker to impersonate the ``authenticated'' user. This is actually not properly part of the CAS authentication scheme, as CAS has no requirement that sessions or other persistance between visits be used. However, the initial request to an application will involve two redirections, plus another request and response to validate the service ticket, resulting in a factor of five or so increased response time for the user. This is acceptable for the occasional CAS authentication cycle, but generally will tick off users if happens everytime they visit a page on your application.

Clearly session ids also need protection. PHP provides some of this, by generating cryptographically random session ids for the session, from a large pool of available ids, so it is extremely unlikely that a session id could simply be guessed.

SimpleCAS tries to assist in this matter as well. The authentication information portion of a session has its own expiration period (the authentication information is clearly deleted when the session as a whole expires, but SimpleCAS allows you to expire the authentication information more rapidly than the rest of the session). See the options authInfoExpiry, authInfoExpiryLastUse, and for forcePassword mode, forceExpiry and forceExpiryLastUse as well. SimpleCAS by default will also refuse to accept the authentication information in a session unless the IP address of the current user matches the IP address which created the authentication information (see the authInfoSameIP option). This option makes it difficult to impersonate an authenticated user by stealing session ids, but unfortunately can fail to authenticate valid users if they are behind a proxy (and I hear AOL users fall into that category). Another strong defense against session id hijacking is the autoChangeSessionIDs which causes the user's session id to change every time they are reauthenticated by the CAS server (i.e. a service ticket they presented is validated; this can occur with either explicit (they have to type in their username and password) or implicit (CAS silently authenticates based on TGT) authentication).

Authentication versus Authorization

The CAS scheme, and therefore SimpleCAS, only deals with authentication. CAS establishes that the user visiting your web site has a certain username, e.g. georgew or binladen. Your application is still responsible for determining whether or not that user should be allowed to do something. So if it is for the launching of nuclear missiles, do not give binladen the ability to launch them (I will leave the decision on georgew to you).


Bugs, Limitions, Etc.

No known bugs at this time. Please send bug reports to the author.

SimpleCAS has no support for proxy or proxy-granting portions of the CAS specification. I do not see it being added anytime soon.

SimpleCAS is only tested on PHP 4.4.4. I think is should run on PHP4 above 4.3.3. It might run on PHP5, but I would not count on it.

It would be nice to add options to use ssl session id and even client side certificates to further reduce threat of session id hijacking.


SEE ALSO

phpCAS ( http://esup-phpcas.sourceforge.net/ )


AUTHOR

Thomas M. Payerle, <payerle@wam.umd.edu>


COPYRIGHT AND LICENSE

Copyright (C) 2006 by University of Maryland

This library is free software; you can redistribute it and/or modify it under the same terms of the Lesser Gnu Public License (LGPL), as published by the Free Software Foundation. A copy of the LGPL should have been included with this code (COPYRIGHT.txt), you can also obtain online at http://www.gnu.org/licenses/lgpl.txt