N.B. Note the date on this article. It was written before Wordpress Version 5. I have not worked with Wordpress much since writing this, so I don’t know if it still works.
That said, I think the post still has value in its cross-application methodology.
I’ve been working on a Rails app that needs to integrate with an existing userbase found in a WordPress installation.
One problem I’ve encountered is that her main website that manages subscriptions and users is in WordPress. Users will register with that main website and may or may not have access to the Rails application, separately (depending on their subscription level). What I needed was a method to handle User Authentication in Rails that slaves to the subscription / account information held in WordPress. Ideally, this should all keep with the DRY principle to make it nice and Rails-y.
I wanted to avoid using any messy and potentially unsecure session cookie sharing, and I also wanted to avoid doing any database shadowing. I suspected that since all the data is up on databases on the same server, I should be able to just query it out, right?
Right!
Ok, so this was a little tricky and it took a couple hours to suss it all out, but here’s what I did. For this example, we’ll say that all of your databases are on the same host (mysql.host.com), though they don’t need to be.
Defining the settings
In your config/database.yml
file, you need to specify separate entries for your WordPress database. Thanks to fnichol for this gist.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
development:
adapter: mysql2
encoding: utf8
reconnect: false
database: rails_proj_development
pool: 5
username: rp_dev
password: wootwoot
host: localhost
wp_development:
adapter: mysql2
encoding: utf8
reconnect: false
database: wordpress
pool: 5
username: wp_user
password: hackable
host: localhost
table_prefix_name: wp_dev_
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: mysql2
encoding: utf8
reconnect: false
database: rails_proj_test
pool: 5
username: rp_test
password: wootwoot
host: localhost
wp_test:
adapter: mysql2
encoding: utf8
reconnect: false
database: wordpress
pool: 5
username: wp_user
password: hackable
host: localhost
table_prefix_name: wp_test_
production:
adapter: mysql2
encoding: utf8
reconnect: false
database: rails_proj_production
pool: 5
username: rp_prod
password: wootwoot
host: localhost
wp_production:
adapter: mysql2
encoding: utf8
reconnect: false
database: wordpress
pool: 5
username: wp_user
password: hackable
host: localhost
table_prefix_name: wp_
Note that in the wp_*
entries, I added a table_prefix_name
entry. This will help keep things clean (inclusion alone does not do all the heavy lifting, but it puts it in an obvious place that we can reference later). The prefixes allow you to optionally put all your WordPresses into one database, prefixing them with different prefixes. I wouldn’t say this is necessarily advisable but it’s at least an option, if your configuration is already like that.
Creating the Model
We can use ActiveRecord
to connect to this database, so that everything is seamless. You will need to create a different model for each table you want to connect to (this is consistent with the ActiveRecord
pattern anyways). You can either create the model manually, using your favorite text editor, or you can use the rails generator, but be sure to pass it –skip-migration, since you don’t want it to create a duplicate table in your Rails DB.
Here is the model I used for “User”. (fnichol does one for Post on his gist).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class User < ActiveRecord::Base
# define any "has_many" relationships here
establish_connection "wp_#{Rails.env}"
@prefix = Rails.application.config.database_configuration["wp_#{Rails.env}"]['table_prefix_name']
set_table_name "#{@prefix}users"
alias_attribute "id", "ID"
alias_attribute "login", "user_login"
alias_attribute "pass", "user_pass"
alias_attribute "nicename", "user_nicename"
alias_attribute "email", "user_email"
alias_attribute "url", "user_url"
alias_attribute "created_at", "user_registered"
alias_attribute "activation_key", "user_activation_key"
alias_attribute "status", "user_status"
alias_attribute "display_name", "display_name"
def readonly?
return true
end
def before_destroy
raise ActiveRecord::ReadOnlyRecord
end
end
So, first things first, do it just like you would any Rails AR model. Just make it a new class inheriting from AR:Base.
This first line creates the connection:
establish_connection "wp_#{Rails.env}"
If you used the naming scheme from the example database.yml file above, AND if your webhost handles Rails environments properly (mine sadly doesn’t 🙁 ), this should “just work.”
The prefix we created in the database.yml file earlier will now be pulled in:
@prefix = Rails.application.config.database_configuration["wp_#{Rails.env}"]['table_prefix_name']
We then use that prefix to specify the table name. If you were wanting to create a Post model, you would obviously just change it to posts. A taxonomy model might use terms.
set_table_name "#{@prefix}users"
The lines in the middle are just aliases to make the model more Rails-y. The only critical one is the first — you need to alias ID to be id so that Rails can locate the id field when doing relationships.
The last part is optional, though I recommend including it. Since Rails is a guest in the house of WordPress, you don’t want it knocking over vases, making out with WordPress’s daughter, or eating all the expired cupcakes and cookies in their cupboards like I used to do to my best friends in grade school (only that last part, not the second one and the first one only once).
Example:
Keeping the foreign table read-only will help ensure your Rails App doesn’t accidentally eat a Swiss Cake Roll that wasn’t yet expired.
Authenticating Against the Database
Password “hashing” is the process of encrypting the password to make it so bad guys and curious monkeys can’t easily tell what your passwords are.
It gets a little tricky here. If your wordpress was created AFTER version 2.5, then the account passwords will be hashed using a third-party library called “Phpass“. If they were created before, they will use an MD5 hash.
Since you are more than likely upgraded to the current version (because you are an awesome and smart wordpress user), bear in mind that even if your old accounts were created prior to v2.5 (because you’re old school, too!), if those passwords are changed or if new users are created, the new passwords will be hashed with Phpass. (If you login to your blog with your account, it will automatically update your password to the more secure encryption).
Like I said, tricky.
Either way, you’ll need Phpass for ruby. Fortunately, someone has already made a Gem for it: phpass-ruby
. Add gem 'phpass-ruby'
to your Gemfile, then bundle install
, and you should be all set with that.
MD5 digests can be done using
Digest::MD5.hexdigest("plain text password")
.
Consider this an incomplete solution, yet at least a starting point.
Add this block to your User
model.
1
2
3
4
def check_password plaintext_password
require 'phpass'
return Phpass.new(8).check(plaintext_password, pass)
end
I’m still researching the code to see if this will need to factor in the presence of any salts that you specify in your wp-config.php
. Supposedly those salts are factored into the passwords, but in pluggable.php, the only place I see wp_salt()
being used is in cookies and nonces. All this means is that you shouldn’t have to do anything extra provided your password is updated.
Testing it out
Once you’ve got that done. Test it out by jumping into your rails console.
If you cannot get the password to verify and you are absolutely positive you’re using the correct password, it likely means that your password is old-skool and needs to be updated. An MD5 password will appear in your database as only 0-9 and a-f, if you see any other characters, then it’s been modernized) You can make it hip by simply logging into your dashboard; WordPress will fix it when it checks.
If you’re getting errors about a “table not existing”, you can check to see if it’s using the correct table_name with this console command:
My model took some tweaking here mainly because my host has a very dysfunctional relationship with its dev/test/prod environments.
Pitfall of Belongs_to
I have tested it with a has_many
(which requires no modification to the table), but not with a belongs_to
. Since the latter requires that the table actually have an additional reference column for the foreign key, do this at your own risk. WordPress may modify the user table without you knowing and that could break your relationships.
My suggestion would be to create a proxy ownership model (“liaison”?) that belongs_to
your User
model. That proxy ownership model could then handle all other Rails relationships autonomously from the WordPress database. It makes your AREL a little less pretty (eg. some_group.liaisons.first.nicename
, assuming a Group
model has_many :liaisons
)
Applying it
In the real world context, you can now use it in whatever authentication framework you like. Since it’s using ActiveRecord
, you can employ it any place you would if it was in a Rails DB instead.
Don’t forget you also have access to the other details in the WP user table, such as “nice name”, their e-mail, etc. If you want to include their profile details, you can create a usermeta
model:
1
2
3
4
5
6
7
8
9
10
class User < ActiveRecord::Base
has_many :usermeta
...
end
class Usermeta < ActiveRecord::Base
belongs_to :user
...
end
Usermeta already contains a user_id
column in Rails-friendly format. You’ll have to write some model methods to extract all the data easily. I suggest making that one readonly as well.