Shahzad Bhatti Welcome to my ramblings and rants!

August 12, 2010

Implementing a Single Sign-on solution for WordPress (PHP) and Rails applications using Central Authentication Service (CAS)

Filed under: SSO — admin @ 2:36 pm

I am starting a new Rails project that would be using WordPress for CMS functionality and I needed a way to share the user sessions between WordPress and Rails. Though, I found a couple of WordPress plugins, but I decided to use Central Authentication Service (CAS). I found two server implementations, where the first one was in Java and second one was in Ruby. I had some experience with the Java based CAS server back in 2005, so I decided to use it. Here are the key installation and configuration steps for setting up the Single-Sign-On solution:

Create Rails Project

I had already installed Rails 2.3, so I created a simple project using:

 rails ssoapp
 

Install WordPress

I downloaded latest 3.0 version of WordPress and unzipped it under ssoapp/public directory.

Configuring PHP for Apache

I decided to use Apache for hosting WordPress and Rails applications. As I was using Mac, it already had Apache installed on my machine. All, I had to do was to uncomment following line from /etc/apache2/httpd.conf file:

 LoadModule php5_module        libexec/apache2/libphp5.so
 

I then restarted Apache using

 sudo /usr/sbin/apachectl restart
 

I created a database for wordpress and defined a username/password as follows:

 create database wp
 CREATE USER 'wp'@'localhost' IDENTIFIED BY 'wp';
 GRANT ALL ON *.* TO 'wp'@'localhost';
 

I specified database settings in wp-config.php as follows:

 define('DB_NAME', 'wp');
 
 define('DB_USER', 'wp');
 
 define('DB_PASSWORD', 'wp');
 
 define('DB_HOST', 'localhost');
 

and created an admin account by pointing my browser to http://localhost/wp/wp-admin/install.php.

Installing Phusion Passenger on Apache

I installed passenger gem as follows:

 gem install passenger
 

And installed passenger module for Apache using:

 passenger-install-apache2-module
 

I then added following lines to /etc/apache2/httpd.conf file:

 LoadModule passenger_module /Users/sbhatti/.rvm/gems/ruby-1.9.1-p378/gems/passenger-2.2.15/ext/apache2/mod_passenger.so                                                                                                                                                                                          
 PassengerRoot /Users/sbhatti/.rvm/gems/ruby-1.9.1-p378/gems/passenger-2.2.15
 PassengerRuby /Users/sbhatti/.rvm/rubies/ruby-1.9.1-p378/bin/ruby
 

I created a file called ssoapp.conf under /etc/apache2/users with following contents:

 <VirtualHost localhost:80>
       ServerName localhost
       DocumentRoot /Users/sbhatti/src/yoga-rails/public
       RailsEnv development
       <Directory /Users/sbhatti/src/yoga-rails/public>
          Allow from all
          Options Indexes -MultiViews
       </Directory>
       <Location /wp>
          PassengerEnabled off 
          AllowOverride all 
       </Location>
       <Location /cas>
          PassengerEnabled off 
          AllowOverride all 
       </Location>
 </VirtualHost>
 

Configuring SSL for Apache

I needed SSL for some functionality so I created self-signed keys and certificates a follows:

 openssl genrsa -des3 -out server.key 1024
 openssl req -new -key server.key -out server.csr 
 

and copied those files to /etc/apache2 directory. I then modified /etc/apache2/httpd.conf by uncommenting following line:

 LoadModule ssl_module libexec/apache2/mod_ssl.so
 

and adding

 Listen 443
 

I then modified /etc/apache2/users/ssoapp.conf and added following contents:

 <IfModule mod_ssl.c>
   AddType application/x-x509-ca-cert .crt
   AddType application/x-pkcs7-crl .crl
 
   SSLProtocol all -SSLv2
   SSLPassPhraseDialog builtin
   SSLSessionCache dbm:/var/run/ssl_scache
   SSLSessionCacheTimeout 300
   SSLMutex file:/var/run/ssl_mutex
   SSLRandomSeed startup builtin
 <VirtualHost localhost:443>
       ServerName localhost
       DocumentRoot /Users/sbhatti/src/yoga-rails/public
     ServerAdmin sbhatti@peak6.com
     ErrorLog /tmp/error_log
     TransferLog /tmp/access_log
     SSLEngine on
     SSLProtocol all -SSLv2
     SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
     SSLCertificateFile /etc/apache2/server.crt
     SSLCertificateKeyFile /etc/apache2/server.key
     <Files ~ "\.(cgi|shtml|phtml|php3?)$">
       SSLOptions +StdEnvVars
     </Files>
       <Directory /Users/sbhatti/src/yoga-rails/public>
          Allow from all
          Options Indexes -MultiViews
         SSLOptions +StdEnvVars
       </Directory>
       <Location /wp>
          PassengerEnabled off
          AllowOverride all
       </Location>                                                                                                                                                                                                                                                                                                          
       <Location /cas>
          PassengerEnabled off 
          AllowOverride all 
       </Location>
     SetEnvIf User-Agent ".*MSIE.*" \
     nokeepalive ssl-unclean-shutdown \
     downgrade-1.0 force-response-1.0
     CustomLog /var/log/httpd/ssl_request_log \
     "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
 </VirtualHost>
 </IfModule>
 

Basically, above configuration allows all HTTP and HTTPS requests be sent to my SSO rails app except /wp requests, which are sent to the WordPress.

Password Encoding

I decided to use WordPress users table (wp_users) as primary users table. WordPress by default uses PHPPass library for encrypting passwords. The latest WordPress uses combination of salt, MD5 and iterations for encrypting, but I wanted to use simple MD5 for now, so I edited ./wp-includes/pluggable.php and replaced default hash encoding to MD5 as follows:

 function wp_hash_password($password) {
    return md5($password, FALSE);
 }
 

I then reset the password in Mysql database as follows:

 update wp_users set user_pass = md5('hello') where user_login = 'admin';
 

Setting CAS Server

I downloaded and unzipped CAS Server. I needed to configure CAS server to use the wp_users table for authenticating users, so I added following dependencies to the cas-server-webapp/pom.xml:

         <dependency>
                 <groupId>mysql</groupId>
                 <artifactId>mysql-connector-java</artifactId>
                 <version>5.0.5</version>
         </dependency>
         <dependency>
                 <groupId>commons-dbcp</groupId>
                 <artifactId>commons-dbcp</artifactId>
                 <version>1.4</version>
         </dependency>
         <dependency>
                 <groupId>${project.groupId}</groupId>
                 <artifactId>cas-server-support-jdbc
                 </artifactId>
                 <version>${project.version}</version>
         </dependency>
 

I then added following beans to the cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml:

         <bean id="md5PasswordEncoder"
                 class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
                 <constructor-arg index="0" value="MD5" />
         </bean>
         <bean id="wpDataSource" class="org.apache.commons.dbcp.BasicDataSource">
                 <property name="driverClassName">
                         <value>com.mysql.jdbc.Driver</value>
                 </property>
                 <property name="url">
                         <value>jdbc:mysql://localhost:3306/wp
                         </value>
                 </property>
                 <property name="username">
                         <value>wp</value>
                 </property>
                 <property name="password">
                         <value>wp</value>
                 </property>
         </bean>
 

and removed default authentication class org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler and added following bean instead:

         <property name="authenticationHandlers">
                 <list>
         ...
                         <bean
                                 class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">
                                 <property name="tableUsers">
                                         <value>wp_users</value>
                                 </property>
                                 <property name="fieldUser">
                                         <value>user_login</value>
                                 </property>
                                 <property name="fieldPassword">
                                         <value>user_pass</value>
                                 </property>
                                 <property name="dataSource" ref="wpDataSource" />
                                 <property name="passwordEncoder" ref="md5PasswordEncoder" />
                         </bean>
 
                 </list>
         </property>
 

Above bean configuration specifies MD5 for hashing passwords.

Creating WAR file for CAS-Server

I then created the CAS war file by typing:

 mvn clean install
 

Install and Configure Tomcat

The CAS-server requires Java application server so I downloaded and installed latest Apache Tomcat. Next, I needed to setup SSL for secure login so I created another set of certificate and private key using keytool as follows:

 keytool -genkey -alias ssoapp -keypass ssoapp -keystore ssoapp.ks -storepass ssoapp
 

I copied ssoapp.ks to the conf directory under Apache tomcat and modified conf/server.xml and added following contents:

         <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
                 maxThreads="150" scheme="https" secure="true" clientAuth="false"
                 sslProtocol="TLS" keystoreFile="${catalina.home}/conf/ssoapp.bin"
                 keystorePass="ssoapp" />
 

I then started the Apache Tomcat using:

 bin/startup.sh 
 

Configuring CAS-client for WordPress

First, I downloaded phpCAS client library and unzipped it under public directory of my Rails app. I used wpCas plugin for WordPress, which uses phpCAS library for connecting to the CAS server. I installed the plugin under wp-contents/plugins directory and logged in to WordPress as admin. I clicked the plugins link from left navigation and then activated the plugin. After activating it, I clicked the settings link from the left navigation and configured the CAS as follows:

Now, the moment of truth. I opened another instance of browser and pointed to http://localhost/wp, which showed my blog.

I then clicked on “Log in” link, which showed me certificate warning due to self-signed certificate. After accepting it, it took me to the CAS login page, e.g.

Voila, after typing my username and password, it took me back to the WordPress.

Setting up CAS-Client for Rails

I used rubycas-client plugin for Rails and installed it as follows:

   ./script/plugin install git://github.com/gunark/rubycas-client.git
 

I then modified config/environment.rb file and added:

   require 'casclient'
   require 'casclient/frameworks/rails/filter'                                                                                                                                                                                                                                                                              
   CASClient::Frameworks::Rails::Filter.configure(
     :cas_base_url => "https://localhost:8443/cas"
   )
 

One gotcha in above configuration is that you have to place it at the end of configuration file as you get load error if you place it inside initializer block.
I then added before_filter CASClient::Frameworks::Rails::Filter to all controllers that needed authentication and added CASClient::Frameworks::Rails::Filter.logout(self) where I needed to logout. After changing my controller, I went back to the Rails app and sure enough it worked like a charm!

Summary

Though, there were many small pieces to get this working, but I am happy with this solution. This would help create seamless experience to the users, though the other half of the experience depends on unified user interface.

References



Powered by WordPress