At GVSU we don’t have single sign-on. We use Active Directory/LDAP authentication, but users need to log in to each service individually. For a library user, that might mean logging into the catalog to renew a book, then logging in again to check course reserves, and then logging in a third time to place an ILL request1. This only gets worse as we develop new tools that require user accounts.
We have a lot of tools we use for internal use, but we can’t use Active Directory to log in due to IT restrictions: Libstats, instruction statistics, our home-grown Library Labs CMS, our Sustainability Challenge game, and a new tool called CookieWatch that helps promote a culture of food sharing and notifies you when a coworker has brought in a snack. The problem is that all of these tools require different logins. This is annoying and silly.
So, I decided to do something about it.
A solution presents itself
IT has let us connect several of our services to Active Directory: Illiad (ILL), Ares (Course Reserves), Millennium (OPAC), and EZProxy. Of these, EZProxy isn’t tied to a single service, but rather authenticates users against Active Directory (or technically, LDAP), and then forwards them along to a specified service2.
I remembered an article I read a while back about how to turn EZProxy into a single sign-on system. Written in 2009 by Brice Stacey, it was more of a proof-of-concept article than a documentation of working code, but the snippets he provided were enough to get me started.
Since all of our internal login systems require a session variable for the username, I figured we could use EZProxy to generate the user session variable we could replace our existing login system with EZProxy3.
Brice’s plan revolved around EZproxy’s UserObjects, which are configurable blocks of session data that EZProxy can generate for a user session. EZProxy lets you set all kinds of session variables in a UserObject. To use them, though, we have to set them up in the EZProxy configuration. You’ll need to generate a secret key so that EZProxy knows that your login request is legitimate. You’ll also need to know the URL of the application you want EZProxy to authenticate for. On this example, we’ll say it’s
http://example.com. We’ll also say that your EZProxy URL is
Add these lines to your EZProxy config.txt to activate UserObjects:
Option UserObject Option UserObjectTestMode LocalWSKey kjshf9hkauy392jlkncq3FBC3F27F2CD4CBA25B0BE858F2A5C3823FEAB33kjyw9uhakjkjashd39uh RedirectSafe ezproxy.mylibrary.edu RedirectSafe example.com
Now we also need to add the URL of your app to EZProxy’s white list, so add another set of lines like this:
T My Awesome Application U http://example.com DJ example.com
See OCLC’s EZProxy manual for more on how to set up your config.txt file.
Now we need to tell the LDAP server what goodies we want in our UserObject. For this test, I just want the username, so I’ll add the following line before the
/LDAP in users.txt:
Set session:uid = login:user
Now we have EZProxy set up, so we need to handle the session variable once EZProxy passes it to us.
EZProxy UserObjects are XML elements that you can manipulate. I used a cURL function Brice wrote to grab the object:
<?php $wskey = 'kjshf9hkauy392jlkncq3FBC3F27F2CD4CBA25B0BE858F2A5C3823FEAB33kjyw9uhakjkjashd39uh'; $url = $_GET['token'] . '&service=getUserObject&wskey=' . $wskey; $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // Won't fail on errors curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $o = curl_exec($ch); curl_close($ch); ?>
Note that the secret key you added to config.txt should be the same as the key in $wskey.
The userObject will be stored as
$o. If you’re only grabbing the user name here, your UserObject will look like this4:
<?xml version="1.0" encoding="UTF-8"?> <userObjectResponse> <serviceStatus>OK</serviceStatus> <userDocument> <lastAuthenticated>2012-04-23T16:24:04Z</lastAuthenticated> <uid>USERNAME</uid> </userDocument> </userObjectResponse>
To get the username, we just need to parse the XML and specify what we want:
<?php $data = new SimpleXMLElement($o); $user = (string)$data->userDocument->uid; ?>
Putting it all together
Now we want to start a session, save the username as a session variable, and redirect back to the application. Simple:
<?php session_start(); // Open a new session $_SESSION['username'] = $user; // Save the session variable session_write_close(); // Make sure the username is written before we redirect $new_url = $_GET['newurl']; // URL of the app to authenticate header('Location: ' . $new_url); ?>
Because we are building these tools for internal use, we don’t need to write anything to handle new users who haven’t registered. We have a small users table in our database that helps us link users to several of the existing applications we’ve built, and the existing routine for grabbing user data comes from session variables and cookies. So we needed minimal tweaking to our existing apps to move logins to EZProxy.
Building the URL
EZProxy works by passing URLs to the login script in a GET request, so that after authentication you are directed to the service you logged into. Using EZProxy to log in to your own system is no different, but we need to include a few extra parameters, including a URL to build the UserObject, the URL for our script that will parse the userObject (EZLogin.php), and finally the URL of the app or site we’re logging into. Our new URL will look like this:
Going to this link will first take me to EZProxy, and after logging in I’m directed to their UserObjects scripts, then to my EZlogin.php script, and finally to my application, with a session variable set for the user’s username.
Now we’ve managed to use EZProxy to log into a system we built, which means our users don’t have to remember 13 different passwords to do their jobs. It also means we have a lot less code to worry about, since EZProxy does the heavy lifting for us. Next up, getting EZProxy to play nice with Illiad, Ares, and Millennium5.
You can grab the whole EZlogin.php file at Github, if you like.
- Penn State highlights this problem by sending their “My Library Account” link to a modal window asking the user to choose which system they want to log into. This solution seems forced to me, and puts the burden on the user. In addition, I think by presenting the systems in this way, the library is only highlighting the lack of integration the systems have. ↩
- For anyone outside the library, EZProxy is how we authenticate our users before sending them on to databases, journals, and other subscription services we provide. ↩
- I am not a security expert, but I did try my hand at exploiting this system by bypassing EZProxy and setting the session variable. All of my attempts failed, leading me to believe that it was reasonably secure. If you spot a huge security hole in this plan, please let me know: @mreidsma or email@example.com. ↩
- You can see a more beefed up version of the UserObject on the OCLC wiki. ↩
- Campus is planning to upgrade to Banner 9 within the year, which requires CAS, so maybe this whole project will be moot if they implement it outside of the Banner environment. ↩