I had just finished the standard installation of ActiveAdmin when I noticed that authentication was implemented using the fantastic Devise Gem. I had already been planning on using Devise for authentication within my application so I initially thought integration would be a snap. However, as development progressed it became clear that all of the pieces were not quite fitting together.
Realization
After exploring the idea of using one login for ActiveAdmin and the application I ran into a rather large road block. By default ActiveAdmin uses an AdminUser Devise model to manage user authentication. Why is that such a big problem you ask?
Well, I really wanted to avoid having…
- the application users login via the ActiveAdmin interface
- a model named
AdminUserrepresenting an application user - two models that contained much of the same code and functionality
- to maintain two separate user management systems (Admin vs Application)
In most cases ActiveAdmin would be used by… well, administrators. However, that was not my situation. I had a slew of qualified people that needed access to both ActiveAdmin and the Application; having two sets of credentials was just not the optimal solution.
The Optimal Solution
What I really wanted was for both the ActiveAdmin and Application users to be…
- able to sign in/out via a single set of Devise controller/actions routes
- managed by single, Devise configured,
Usermodel - distinguished by a boolean flag — such as
user.superadmin? - restricted from accessing ActiveAdmin based on the above flag.
After chewing on the problem for a while, reading through ActiveAdmin documentation, and talking to a couple of colleagues I managed to come up with a way of implementing a solution that provided exactly what I was looking for.
Setting Up the ActiveAdmin/Application User Model
For the purposes of making this walkthrough more adaptable to other projects I’ve chosen to explain the entire process starting from scratch using a brand new Rails 3.1 project.
Step 0: Preparation
We start off by creating the classic Rails “blog” application…
$ rails new blog
Next, we open the project’s Gemfile and add the required ActiveAdmin and Devise gems…
# ==> File: /Gemfile # Active Admin + Required Gems gem 'activeadmin' gem 'sass-rails' gem "meta_search", '>= 1.1.0.pre' # Devise gem "devise", "~> 2.0.0"
We then install the gems with bundler and run the ActiveAdmin installation generator …
$ bundle install
... bundler output ...
$ rails generate active_admin:install
invoke active_record
create db/migrate/20120205064942_devise_create_admin_users.rb
create app/models/admin_user.rb
invoke test_unit
create test/unit/admin_user_test.rb
create test/fixtures/admin_users.yml
insert app/models/admin_user.rb
route devise_for :admin_users
gsub app/models/admin_user.rb
gsub config/routes.rb
insert db/migrate/20120205064942_devise_create_admin_users.rb
create config/initializers/active_admin.rb
create app/admin
create app/admin/dashboards.rb
route ActiveAdmin.routes(self)
generate active_admin:assets
create app/assets/javascripts/active_admin.js
create app/assets/stylesheets/active_admin.css.scss
create db/migrate/20120205014944_create_admin_notes.rb
create db/migrate/20120205014945_move_admin_notes_to_comments.rb
Note: You may see some Devise specific errors at this point, ignore them for now.
At this point the ActiveAdmin gem has been installed and its files and routes have been generated using the steps outlined in the official installation instructions. This is where we leave those instructions behind…
Step 1: Creating a new Devise User model
First thing’s first, let’s run the Devise 2.0 installation generator.
Note: Enter “Y” (yes) if you are prompted to overwrite the devise initializer file.
$ rails generate devise:install
conflict config/initializers/devise.rb
Overwrite blog/config/initializers/devise.rb? (enter "h" for help) [Ynaqdh] Y
force config/initializers/devise.rb
identical config/locales/devise.en.yml
Now, at this point one might ask: “Can’t I just simply rename the AdminUser model?” – Nope! Unfortunately there are no migrations, routes, or tests in place to support a model named User yet.
To fix that let’s go ahead and create them using the Devise model generator.
$ rails generate devise User invoke active_record create db/migrate/20120120053416_devise_create_users.rb create app/models/user.rb invoke test_unit create test/unit/user_test.rb create test/fixtures/users.yml insert app/models/user.rb route devise_for :users
Now that the User model has been created a superuser? boolean flag must be added. Additionally we need to insert an initial Superamin User record so that we may login and access ActiveAdmin later on. These two steps can be accomplished with a simple rails migration…
$ rails generate migration AddSuperadminToUser invoke active_record create db/migrate/20120122062203_add_superadmin_to_user.rb
# ==> File: /db/migrate/20120122062203_add_superadmin_to_user.rb class AddSuperadminToUser < ActiveRecord::Migration def up add_column :users, :superadmin, :boolean, :null => false, :default => false User.create! do |r| r.email = 'default@example.com' r.password = 'password' r.superadmin = true end end def down remove_column :users, :superadmin User.find_by_email('default@example.com').try(:delete) end end
Note: These temporary user credentials will be used later on in Step 4.
Finally, open up the User model and replace its content with the example code below – modify to taste.
# ==> File: /app/models/user.rb class User < ActiveRecord::Base devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable attr_accessible :email, :password, :password_confirmation, :remember_me end
Great, you’ve just configured what will be the unified User model!
Step 2: Reconfiguring ActiveAdmin
As it turns out ActiveAdmin has some nifty configuration settings that allow for its authentication behavior to be modified. Open up the ActiveAdmin initializer file and update it to reflect the configuration settings below…
# ==> File: config/initializers/active_admin.rb config.authentication_method = :authenticate_active_admin_user! config.current_user_method = :current_user config.logout_link_path = :destroy_user_session_path config.logout_link_method = :delete
There is a lot here so let’s quickly go over whats happening.
- The ActiveAdmin
current_user_methodandauthentication_methodconfiguration settings specify which methods to call when retrieving and authenticating a user, respectively. - When someone attempts to access any of the ActiveAdmin actions a call is made to both methods which are scoped to the Rails
ApplicationControllerinstance.
We’ve instructed ActiveAdmin to use a new custom authentication method: authenticate_active_admin_user! It doesn’t exist yet so we must define it in the ApplicationController …
class ApplicationController < ActionController::Base protect_from_forgery def authenticate_active_admin_user! authenticate_user! unless current_user.superadmin? flash[:alert] = "Unauthorized Access!" redirect_to root_path end end end
Perfect, ActiveAdmin is now properly configured to take advantage of the superadmin? user flag.
Step 3: Cleaning up the code left behind by ActiveAdmin
Now that the new Devise User model is setup we can start to cleanup the code and files leftover from the ActiveAdmin install generator. We start by deleting the AdminUser model and its associated migration file…
$ cd /path/to/your/rails/project $ rails destroy model AdminUser invoke active_record remove migration.rb remove app/models/admin_user.rb invoke test_unit remove test/unit/admin_user_test.rb remove test/fixtures/admin_users.yml $ rm ./db/migrate/*_devise_create_admin_users.rb
Next, we need to make the comments within ActiveAdmin work properly again. Open up the following migration files, do a simple Find & Replace for :admin_user to :user, then save.
20120212124753_create_admin_notes.rb20120212124754_move_admin_notes_to_comments.rb
Note: The timestamps of your migration filenames will be different.
The Devise routes that AdminAdmin generated for the AdminUser model still exist and must be removed. Open up the Rails routes.rb file and delete the following line of code…
devise_for :admin_users, ActiveAdmin::Devise.config
Great, the project has been clean of any left over files and all the features within ActiveAdmin will now work properly.
Step 4: Managing the Devise User model with ActiveAdmin
The User model is much more complex than your average ActiveRecord::Base class. It’s configured with Devise authentication options and now has a superadmin? flag that is responsible for controlling access to ActiveAdmin. As a result of this complexity we need to add some custom configuration.
The first thing we need to do is make ActiveAdmin aware of the User model by creating a corresponding registration file, like so…
$ cd /path/to/your/rails/project $ touch ./app/admin/user.rb
Open the /app/admin/user.rb file with an editor of your choice and copy/paste the ActiveAdmin model registration code below.
| Note: | This is the absolute bare minimum configuration that is required to manage users from within ActiveAdmin. Feel free to modify this registration file to suit your needs. Keep in mind that as the User model gains more complexity, the ActiveAdimin registration file will need to grow along with it. |
# ==> File: app/admin/user.rb ActiveAdmin.register User do form do |f| f.inputs "User Details" do f.input :email f.input :password f.input :password_confirmation f.input :superadmin, :label => "Super Administrator" end f.buttons end create_or_edit = Proc.new { @user = User.find_or_create_by_id(params[:id]) @user.superadmin = params[:user][:superadmin] @user.attributes = params[:user].delete_if do |k, v| (k == "superadmin") || (["password", "password_confirmation"].include?(k) && v.empty? && !@user.new_record?) end if @user.save redirect_to :action => :show, :id => @user.id else render active_admin_template((@user.new_record? ? 'new' : 'edit') + '.html.erb') end } member_action :create, :method => :post, &create_or_edit member_action :update, :method => :put, &create_or_edit end
Step 5: Wrapping It All Up
Now that everything has been created and configured we can finally pull the trigger on the migration files. So let’s go ahead and run them like so…
$ cd /path/to/your/rails/project $ rake db:migrate
It’s a good idea to setup some real user credentials so let’s start up the local rails server, by executing $ rails server and opening the browser to http://localhost:3000/users/sign_in.
| Note: | If page layout looks completely broken don’t worry! It just means that you need to style or replace the Devise sign in page. |
Sign in using the temporary email and password that was added via the migration created back in Step 1.
After you’ve logged in navigate to http://localhost:3000/admin, click on the Users tab, create a new super administrator user using the credentials of your choice and click save.
Lastly, sign out of ActiveAdmin, then sign in again with your new credentials and delete the temporary user.
That’s it, you’re done!
But! But! What About…
Some may be wondering why I did not use the optional user model parameter in ActiveAdmin install generator script.
Well, I didn’t use it in this post for a few of reasons. Mainly I wanted to make sure this walkthrough was applicable to the widest set of projects and situations. Thus, I wrote this post so that it could be followed by someone who was either starting a new Rails project or just needed to cherry pick some of the steps for their existing application.
I also found that running the ActiveAdmin installation generator over top of a pre-existing User model will cause code to be injected into that User model class file and migrations will be built that change the users table structure.
Lastly, the generator solution alone does not cover all of my other requirements that I listed above. ActiveAdmin would still use it’s own views for the authentication routes, and the superadmin? flag, configuration setting, and authentication method would still need be modified/created by hand.
Thus, I would recommend using it only if you’re aware of the pitfalls in doing so.
Environment
This walk through was tested and implemented using the following technologies…
- Rails 3.1
- Ruby 1.9.3
- ActiveAdmin 0.4.0
- Devise 2.0
Summary
This solution was made possible thanks to the work that Greg Bell and the ActiveAdmin Contributors put into making sure the flexibility of ActiveAdmin stayed a top priority. ActiveAdmin is a truly fantastic (and beautiful looking) gem that spares developers from spending extensive hours and resources building back-end administrative tools – I highly recommend using it.
If you have any suggestion on how to make this process better in anyway please contact me and I’ll make the appropriate updates.