Apache2::Controller::Auth::OpenID - OpenID for Apache2::Controller




 PerlLoadModule Apache2::Controller::Directives

 <Location /myapp>
     SetHandler modperl

     # uri to your login controller:
     A2C_Auth_OpenID_Login          login

     # uri to your logout controller:
     A2C_Auth_OpenID_Logout         logout

     # uri to your registration controller:
     A2C_Auth_OpenID_Register       register
     # you might want to put this outside the protected area, 
     # i.e. /other/register - you can use leading '/' for absolute uri

     # idle timeout in seconds, +2m, +3h, +4D, +6M, +7Y, or 'no timeout'
     # default is 1 hour.  a month is actually 30 days, a year 365.
     A2C_Auth_OpenID_Timeout        +1h

     # name of the openid table in database:
     A2C_Auth_OpenID_Table          openid
     # key of the username field in table:
     A2C_Auth_OpenID_User_Field     uname

     # key of the openid url field in table:
     A2C_Auth_OpenID_URL_Field      openid_url

     # if you use multiple DBI handles, name the one in pnotes
     # that you should use for reading the openid table:
     A2C_Auth_OpenID_DBI_Name       dbh

     # by default trust_root is the result of $r->construct_url(''),
     # i.e. the top of the site (see Apache::URI)
     A2C_Auth_OpenID_Trust_Root     http://myapp.tld/somewhere

     # set a random string used as salt with time() to sha secret

     # but that random salt will be reset if you restart server,
     # which may cause current logins to die, so you can specify
     # your own constant salt of arbitrary length
     A2C_Auth_OpenID_Consumer_Secret    abcdefg1234567

     # if you do not want to preserve GET/POST params 
     # across redirects to the OpenID server, use this flag:
     # A2C_Auth_OpenID_NoPreserveParams

     # if you do not overload get_uname() (see below), then
     # PerlHeaderParserHandlers must be invoked in order
     # to set up the dbi handle before checking auth
     # with the default method.  In this example,
     # MyApp::DBI::Connector is an Apache2::Controller::DBI::Connector
     # and MyApp::Session is an Apache2::Controller::Session::Cookie...
     # see those modules for more info.

     PerlInitHandler            MyApp::Dispatch 
     PerlHeaderParserHandler    MyApp::DBI::Connector
     PerlHeaderParserHandler    MyApp::Session
     PerlHeaderParserHandler    Apache2::Controller::Auth::OpenID


Implements an authentication mechanism for Apache2::Controller that uses OpenID.

This is NOT an AuthenPerlHandler. This is an implementation of a simple cookie-based mechanism that shows the browser a login page, where your controller should present and process an HTML form for logging in.

If you want an authentication handler that uses browser-based auth (the pop-up dialog implemented by HTTP auth protocol) use Apache::Authen::OpenID, which is not a part of Apache2::Controller but should work for you anyway.

Natively this depends on Apache2::Controller::Session::Cookie and Apache2::Controller::DBI::Connector being configured correctly, but you could always subclass this and overload the methods below to get information from other sources.

If no claimed ID is detected, the user is shown the login page. If an error occured, you'll find the Net::OpenID::Consumer error details in the session under {a2c}{openid}{errtext} and {a2c}{openid}{errcode}.


Whether redirecting or redispatching, stuff has to be saved in the session, so $r->notes->{a2c}{session_force_save} will be set.



If the uris for these pages are relative, not absolute, i.e. they are handled by the same controller that we're going to anyway, then it tries setting the uri and re-dispatching by grabbing the dispatch class name out of $r->pnotes->{a2c}{dispatch_class} and instantiating a new dispatch handler object.

(Dispatch can't keep the handler subref around in pnotes due to circular references, or reliably assume that we know at what location in the PerlInitHandler stack the dispatch handler coderef was stored by Apache, so we just create a new one - this is assured to be faster than creating an entire new request, which would do that anyway.)

So in this case, the content for the login, logout, or register pages will appear even though the browser uri still displays the requested protected URI.


If the uris for the internal pages are absolute, i.e. they might be handled by a different controller than the one that was dispatched, a redirect using Location HTTP header is used.


Any time the browser needs to go to an external page (the openid server), a redirect using a Location: HTTP header is used.



When it goes to your login, or register page, it stashes the user's uri into the session as {a2c}{openid}{previous_uri} and should preserve this for the return url. It uses $r->construct_url() as the trusted root.

When the user passes the authentication process ($csr->verified_identity), it sets $r->pnotes->{a2c}{openid_logged_in} for this request to let your handler know if you want to display a message like "You have successfully logged in" or something.


The whole point of OpenID is that the login mechanism is invisible - as long as the user can claim to own the url, and the auth server returns a positive response, then the user's session should continue.

So, this preserves GET parmas and the POST body through the login process and the redirect sequence to the OpenID server, including when the local session times out. If they come back after a while and click a submit button, but either their local session has timed out or their OpenID server session has timed out through whatever mechanism that uses, then after they log into OpenID and are redirected back to the protected area, the GET params and POST body are restored, and it will do what the user expected when they clicked the submit button.

This behavior is a feature, so it is enabled by default, but it may not be expected, so you can turn it off by using the directive flag A2C_Auth_OpenID_NoPreserveParams.


"Apache2::Controller::Auth::OpenID" in Apache2::Controller::Directives


If you want to provide a cache for Net::OpenID::Consumer to pass onto URI::Fetch, subclass this module and implement a method cache() that returns the appropriate cache object.


I have heard there are trickier things one can do to ensure the security of a session based cookie. This module just implements a simple association of a user with a session key by storing a flag and a last-accessed time value in the session hash, nothing fancier. If you have recommendations, please let me know.

This calls $r->connection->get_remote_host and saves it in the {a2c}{openid} section of the session hash. So if you don't want it to do DNS lookups, set directive HostNameLookups off.


Overloaded constructor will always throw an Apache2::Controller::X because this module does not work.


The only method which should be overloaded in your subclass is get_uname( $openid_url ) which returns the username string that corresponds to the openid url supplied by the cookie. When overloading, you get the RequestRec in $self->{r}.


 my $uname = $self->get_uname($openid_url);

Takes a string which is the supplied openid_url. You can overload get_uname to supply it by some other means, such as by LDAP.


"These aren't the methods you're looking for."

"These aren't the methods we're looking for."

"He can go about his business."

"You can go about your business."

"Move along."

"Move along. Move along."

You don't access these methods. This is internal documentation.


Calculate and return the hash of directive defaults. (Some of these are based on the current <Location> of the handler.)


Correct trailing double /'s etc. in the openid url.


Make sure the config directives are assigned or use defaults.

If uri = login uri, process accordingly.

If uri = logout uri, delete session hash login flags and return OK.


To save memory and be clean, when the object is destroyed, the package-space var %params_hash is cleared.

process() has to populate a package space variable %params_hash with the params contents, so we won't create a closure that includes the request object when we construct the params subroutine for the cached openid CSR object. Otherwise, the CSR keeps a reference to our handler object around, which contains a reference to the request object, and then neither the request nor the handler object are cleaned up after this handler exits. (Apparently Apache doesn't execute DESTROY until the next time it has to run this handler. Doesn't quite make sense, but that's the way it behaved.)


If the uri is relative, qualifies it by prepending current location. Otherwise just returns the uri.


 return $self->redirect_to($uri);

If one of the three internal URIs, use redispatch().

Otherwise, use location_redirect.


 return $self->location_redirect($uri);

Set the Location header and return REDIRECT.

Forces the session to be saved in the cleanup handler.


 return $self->redispatch($uri);

For the internal pages (login, logout, register), if they are relative, re-dispatch them and return OK, else if absolute, set location and return redirect.

If where == register or login, and the current uri is not register or login, stash the current uri in session->{a2c}{openid}{previous}{uri}, and if A2C_Auth_OpenID_NoPreserveParams is NOT set, then it stashes the get args and post body in ...{previous}{get} and ...{previous}{post} for reattaching to the request after successful authentication on the return from the auth server.


Preserve the GET and POST params in the session.


Check the fields in the session hash to make sure they're logged in. Apply the directive timeout to make sure. Don't change anything though. Just return if not logged in, or return 1 if logged in.


Log the user out by clearing the relevant fields in the session hash.








Mark Hedges, <hedges at>


Copyright 2008-2010 Mark Hedges, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.