authtower central authentication for web apps

Introduction

Sometimes I'll get in the mood to write a web application. The one thing I really hate about making them is the authentication piece -- it's always the same thing over again: user profile, sign up, edit preferences, etc. Totally tedious and a waste of time.

To deal with it, I decided to write my own central authentication application. I thought about using OpenID in some way, but it just wasn't what I was looking for. I wanted to be able to have authentication and profile management in one central location. I ended up building AuthTower.

AuthTower is a very simple application written with CakePHP. Users can sign up for an account and maintain their profile. That's it. There's an XMLRPC backend that other websites can use to hook in.

How Authentication Works

There's a single XMLRPC method that does the authentication. It's wrapped up in a CakePHP controller component:

    class XmlRpcComponent extends Object {
            var $controller = true;
            var $server;

            function authenticate($method, $params) {
                    $User =& new User();
                    $u = $User->findByUsername($params[0]);
                    if ($u) {
                            if ($params[1] == $u['User']['password']) {
                                    $u['User']['password'] = null;
                                    return $u['User'];
                            } else {
                                    return False;
                            }
                    } else {
                            return False;
                    }
            }
    }

Basically, it's looking up the user's profile and returning it. Even though I'm storing the passwords as md5 hashes, I still null'd it out so it wouldn't transfer.

Then there's an XMLRPC controller that responds to XMLRPC calls. I was unable to get CakePHP's built-in web services support working, so I just hacked my way with help from the PHP XMLRPC page.

The XMLRPC Controller:

    class XmlRpcController extends AppController {
            var $name = 'XmlRpc';
            var $uses = array('User');
            var $components = array('XmlRpc','RequestHandler');
            var $autoRender = False;

            var $server = "";

            var $beforeFilter = array('registerMethods');

            var $allowed = array('127.0.0.1','192.168.1.1');

            function registerMethods() {
                    $this->server = xmlrpc_server_create();
                    xmlrpc_server_register_method($this->server, "authenticate", \ 
                        array(&$this->XmlRpc, "authenticate"));
            }


            function index() {
                if ($this->RequestHandler->isPost()) {
                  if (array_search(env('REMOTE_ADDR'), $this->allowed)) {
                    $this->layout = "blank";
                    $request = $GLOBALS['HTTP_RAW_POST_DATA'];
                    $response = xmlrpc_server_call_method($this->server,$request,null);
                    header("Content-type: application/xml");
                    print $response;
                  } else {
                    return False;
                  }
                } else {
                    $this->redirect("/users/index");
                }
            }
    }

With this in place, an XMLRPC request will be processed when someone goes to the /xmlrpc/index page.

How to Authenticate

On the client side, authentication is as simple as calling an XMLRPC method. Well, being used to Python's XMLRPC library, I thought it would be simple. Took a couple tries, but I got it working in the end.

Taken right from the PHP XMLRPC comments, here's the function that does the dirty work:

 function do_call($host, $port, $request) {

    $fp = fsockopen($host, $port, $errno, $errstr);
    $query = "POST /xmlrpc/index HTTP/1.0\nUser_Agent: Whatever\n \
        Host: ".$host."\nContent-Type: text/xml\n \
        Content-Length: ".strlen($request)."\n\n".$request."\n";

    if (!fputs($fp, $query, strlen($query))) {
        $errstr = "Write error";
        return 0;
    }

    $contents = '';
    while (!feof($fp)) {
        $contents .= fgets($fp);
    }

     fclose($fp);
     return $contents;
 }

And finally, here's the login function:

    function login() {
        $this->pageTitle = "Log in to your Account";

        if (!empty($this->data)) {
            $this->mrClean->cleanArray($this->data['User']);

            $host = 'authtower.terrarum.net';
            $port = 80;
            $p = array($this->data['User']['username'], \
                md5($this->data['User']['password']));
            $request = xmlrpc_encode_request('authenticate', $p);
            $response = do_call($host, $port, $request);
            $response = (substr($response, strpos($response, "\r\n\r\n")+4));
            $r = xmlrpc_decode($response);

            if ($r) {
                foreach ($r as $k => $v) {
                    $this->Session->write($k, $v);
                }
                $this->redirect("/latest/index");
            } else {
                $this->set('error', "Incorrect username or password.");
            }

        }
    }

Once the user is authenticated, this function will create a session with the user's profile filled in. The client webpage is now able to work with the user's profile in any way they'd like.

Limitations

Right now there is no way to update the profile from the client webpage. Of course, one of my main goals was to keep all profile activity central in AuthTower, but there are other times when a profile might need updated. For example, keeping track of how many articles a user has written.

Conclusion

AuthTower solved a big annoyance of mine: constantly re-writing user authentication schemes. I don't expect this to be used widely -- it was just something I made for myself to solve my own problems. However, if you're interested in using AuthTower, please let me know. I can either post more of the source code or just allow your website access to it so you can authenticate from it.

Please feel free to play with AuthTower and let me know what you think. Sign up for an account and test it out at SomeSite.

I apologize if this article is kind of haphazard, but I didn't want to post every little piece code from the application.