ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


CAS+: Single Sign-On with Jifty (Part 1)
Pages: 1, 2, 3

Service ticket validation

Once the CAS server has verified the identity of the user--either by an explicit login or by checking that the user has already logged in and has a valid login session already in place--the CAS server will send the user back to the web service with a Service Ticket set in the "ticket" parameter. To complete the transaction, the web service requesting the identity of the user must then finish the transaction by contacting the CAS server directly.



By passing the communication up to this point through the user's browser, the user is allowed to control the transaction to some extent. If she doesn't want the web service requesting authentication to know her identity, she is able to stop the transaction. It also provides a straightforward mechanism for presenting the login page for verifying her credentials. At some point, however, the web service must be able to contact the CAS server directly or else the user could easily pretend to be someone she is not. This is one reason why the Service Ticket is passed rather than directly passing back the identity or credentials. The other reason for the Service Ticket is that it prevents the web service from ever gaining direct access to the user's credentials. That is, the web service consuming the user's identity never knows her password, only that the CAS server vouches for her identity.

The next step then, is for the web service to contact the CAS server directly to verify that the Service Ticket belongs to a valid user and to find out what the identity of that user is. The web service does this by using its own web client to directly contact the CAS server via the "/serviceValidate" page.[4] In CAS+, this is handled in the dispatcher as follows:

on 'serviceValidate' => run {
    my $validate = Jifty->web->new_action(
        class     => 'Validate',
        arguments => {
            service => get 'service',
            ticket  => get 'ticket',
            pgtUrl  => get 'pgtUrl',
            renew   => get 'renew',
        },
    );
    $validate->run;
   
    set result => $validate->result;
    show '/serviceValidate';
};

Here, the Validate action (lib/CASPlus/Action/Validate.pm) is called and then the "serviceValidate" template is used to return the response.

The "service" parameter here is what it has been all along, the URL of the service requesting login. This must be the exact URL used in the redirect, and it serves to help validate the authenticity of the web service. The "ticket" parameter is the Service Ticket that was passed back through the redirect (or at least that the user has claimed was passed in the redirect). Again, this must be the exact value that was passed back by CAS. The "pgtUrl" is used in proxying authentication, which will be discussed in Part 2 of this article. If a web service doesn't need to proxy authentication, which would be the typical case, then this parameter wouldn't be given. And the "renew" parameter is set if the service wants to verify that the user did just log in. This is used as the other half of the Login Renewal process discussed above.

The Validate action checks to see that a Service Ticket matches the given service URL and that it is associated with a valid login session. If the service sets the "renew" parameter, the Validate action performs the additional check that the Service Ticket being tested was the result of a fresh login (probably forced using the "renew" parameter to "/login"). If any of these tests fail, the Validate action returns failure.

Furthermore, there are two additional tests that must pass. First, the Service Ticket being validated must not be too old. Typically, "too old" means that it was issued more than five minutes ago. In CAS+, this time period is configurable. If the Service Ticket is too old, it is invalid and Validate will fail.

Second, the Service Ticket can only be validated once. If the Service Ticket being checked has already been validated once before, then the Validate action also fails. This latter test is important in case a person copies a link and sends it to someone else, and that link happens to include a Service Ticket parameter. In that case, the web service would attempt to validate the Service Ticket for the other person a second time and fail, which would probably result in a new redirect to login. If the other user is already logged in, then an immediate redirect back to the source with a new Service Ticket would be made, and the other user may not even be aware of the transaction happening that allows her to see the page correctly anyway.

If all of these tests succeed, then the Validate action returns success.

The dispatcher passes both success and failure on to the "serviceValidate" template (share/web/templates/serviceValidate). As of this writing, all the templates are written using Mason, which is the original templating system available with Jifty.[5]

<?xml version="1.0"?>
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
% if ($result->success) {
    <cas:authenticationSuccess>
        <cas:user>
            <% $result->content('username') %>
        </cas:user>
% if ($result->content('proxy_granting_ticket')) {
        <cas:proxyGrantingTicket>
            <% $result->content('proxy_granting_ticket') %>
        </cas:proxyGrantingTicket>
% }
    </cas:authenticationSuccess>
% } else {
    <cas:authenticationFailure 
            code="<% $result->content('code') %>">
        <% $result->error %>
    </cas:authenticationFailure>
% }
</cas:serviceResponse>
<%args>
$result
</%args>
<%init>
$r->content_type('application/xml');
</%init>

You can see that the response is in XML, and that the Validate action sets several variables on the result object that are used to tailor the response. On success, a message like the following is returned.

<?xml version="1.0"?>
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
    <cas:authenticationSuccess>
        <cas:user>sterling</cas:user>
    </cas:authenticationSuccess>
</cas:serviceResponse>

The web service can parse this message to know that the validation was successful and to learn the user's login name. The CAS protocol does not specify any other information about the user, just the name. This name could be used to grab information from an LDAP server or another external service if additional information is required. The CAS 3.0 protocol also specifies extension mechanisms for adding additional details about the user to the protocol. The CAS+ implementation I have written will implement a mechanism for sharing additional attributes about the user as well.

On failure, a different message is returned. For example, if the Service Ticket were invalid because a renewal was requested, but the Service Ticket was not gained from a fresh login, the web service would get the following message:

<?xml version="1.0"?>
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
    <cas:authenticationFailure 
            code="INVALID_TICKET">
        Service ticket ST-DHF81OKF08100JGC192389HNFN92VN3ZX 
        is not a renewal.
    </cas:authenticationFailure>
</cas:serviceResponse>

The response includes both a regular identifier to help the service automatically identify and respond to the problem and a textual error that can be used for logging to help the administrator diagnose problems later. (I highly recommend NOT giving CAS error messages to the end user.) You can see the CAS Protocol Specification for information on the various codes and their meanings.

Putting It Together

This server may now be used by a client web service to determine the identity of a user. This is done when the web service redirects the user to CAS to check for login. The CAS server asks the user for credentials, either in the form of a username and password or in a Ticket Granting Cookie, which is used to resume an existing session. Once the CAS server is satisfied that the user is who he claims to be, it redirects the user back to the originating web service (possibly without the user being aware of the transaction taking place). The web service receives this new request with the Service Ticket attached and then uses that ticket to verify and retrieve the identity of the user.

All of this takes place in just a few steps and really only requires three URLs to be implemented on the server side (though some of the decisions behind each URL can be complicated). I've presented the basics of that implementation here as I've done it in Jifty.

What's Next

So far, I've covered the really key features of how to implement the Central Authentication Service server in Jifty. What I've covered so far are the features provided by the CAS 1.0 protocol (though, I've covered validation using the CAS 2.0 implementation, which provides most of the same features but with a nicer response format).

As of CAS 2.0, additional features were added to the protocol to cover proxied authentication. With proxy authentication, you gain the ability to provide authentication to non-web services that are being proxied by a web service. For example, if you have a portal that provides access to an IMAP mail server, your portal can use CAS for login and then use CAS to indirectly pass identity information to the IMAP server in a way that the IMAP server can directly verify. I will be covering this process in Part 2 of this article.

[1] http://nosheep.net/story/single-sign-on-definition

[2] Normally, Jifty runs actions automatically on the basis of the forms submitted, but that won't work for this case, because I'm attempting to duplicate a protocol that doesn't include the monikers and action parameters that Jifty normally does. However, Jifty was flexible enough to allow me to do this without any difficulty.

[3] Only most of the time, because a browser might be implemented to allow the replay using a new LT. The CAS protocol authors have noted that at least some versions of Safari have this problem.

[4] This is assuming the CAS 2.0 protocol. In CAS 1.0, the URL was "/validate" and had a completely different response format. A nice thing to note about the CAS+ implementation is that both "/validate" and "/serviceValidate" are handled using the same action, Validate, but a different response template handles the difference in the protocol version responses.

[5] Jifty has recently added support for a new template system called Template::Declare and there has been discussion of adding support for the Template Toolkit.

Andrew Sterling Hanenkamp is a proud Kansan and spends most of his time hacking Perl, his web site, avoiding yard work, and with his wife and son.


Return to ONLamp.com.



Sponsored by: