A minimal authentication module for Rails
Ruby Shell
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
spec
.gitignore
CHANGELOG.md
Gemfile
MIT-LICENSE
README.md
Rakefile
mini_auth.gemspec
test_on_multiple_environments.sh

README.md

mini_auth

A replacement for has_secure_password of ActiveModel with some enhancements

Install

Add to your Gemfile:

gem "mini_auth"

or install as a plugin

$ cd RAILS_ROOT
$ rails plugin install git://github.com/kuroda/mini_auth.git

Requirements

  • Ruby on Rails 3.1 or 3.2

Synopsis

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name, :null => false
      t.string :password_digest, :null => true

      t.timestamps
    end
  end
end

class User < ActiveRecord::Base
  include MiniAuth
  
  attr_accessible :name
end

a = User.new(:name => "alice", :password => "hotyoga")
a.setting_password = true

a.save                              # => true
a.password_digest                   # => "$2a$10$F5YbEd..."

x = User.find_by_name("alice")
x.authenticate("wrong")             # => false
x.authenticate("hotyoga)            # => x

x.attributes = { :current_password => 'hotyoga', :new_password => 'almond' }
x.changing_password = true
x.save

y = User.find_by_name("alice")
y.authenticate("hotyoga)            # => false
y.authenticate("almond")            # => y

Usage

Migration

To use mini_auth, you must add the password_digest column to the relevant table.

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name, :null => false
      t.string :password_digest, :null => true

      t.timestamps
    end
  end
end

The raw (unencrypted) password will never be saved on the database. Only its hash value is recorded on the password_digest column.

Default behavior

The module MiniAuth introduces two basic attributes: setting_password and changing_password

When neither of them is set to true, you can NOT set or change the user's password_digest.

a = User.find_by_name("alice")
a.password = 'foobar'
a.password_digest_changed?          # => false
a.valid?                            # => true

Setting password

When the user's setting_password attribute is set to true, its password can be set without knowing the current password.

a = User.find_by_name("alice")
a.setting_password => true
a.update_attributes(:password => 'p@ssword')
a.authenticate("p@ssword")          # => a

Password can't be blank.

b = User.new(:name => "bob", :password => "")
b.setting_password => true
b.valid?                            # => false
b.errors[:password]                 # => "can't be blank"

Password can't be nil.

b = User.new(:name => "bob", :password => nil)
b.setting_password => true
b.valid?                            # => false
b.errors[:password]                 # => "can't be blank"

Password should be given.

b = User.new(:name => "bob")
b.setting_password => true
b.valid?                            # => false
b.errors[:password]                 # => "can't be blank"

Changing password

When the user's changing_password attribute is set to true, its password can NOT be set without knowing the current password. You should provide current_password and new_password attributes to change its password.

a = User.find_by_name("alice")
a.changing_password => true
a.update_attributes(:current_password => 'p@ssword', :new_password => 'opensesame')
a.authenticate("opensesame")        # => a

If the current_password is wrong, the validation fails.

a = User.find_by_name("alice")
a.changing_password => true
a.attributes = { :current_password => 'pumpkin', :new_password => '0000' }
a.valid?                            # => false
a.errors[:current_password]         # => [ "is invalid" ]

When both of the setting_password and the changing_password are set to true, only the latter is effective.

A user who has no password

You can save a user whose password_digest is nil.

c = User.new(:name => "carol")
c.save!
c.password_digest                   # => nil

Such a user can't get authenticated, but can exist as a temporary account or placeholder.

c.authenticate(nil)                 # => false

If you don't want such a user to be created, add a validation to your class.

class User < ActiveRecord::Base
  include MiniAuth
  
  validates :password_digest, :presence => true
end

Confirmation of password

The password_confirmation and new_password_confirmation attributes are created automatically.

c = User.find_by_name("carol")
c.setting_password = true
c.attributes = { :password => 'snowman', :password_confirmation => 'iceman' }
c.valid?                            # => false
c.errors[:password]                 # => [ "doesn't match confirmation" ]

a = User.find_by_name("alice")
a.changing_password => true
a.attributes = { :current_password => 'opensesame',
  :new_password => 'snowman', :new_password_confirmation => 'iceman' }
a.valid?                            # => false
a.errors[:new_password]             # => [ "doesn't match confirmation" ]

You don't have to use them, however.

c = User.find_by_name("carol")
c.setting_password = true
c.attributes = { :password => 'snowman' }
c.valid?                            # => true

Mass assignment security

The password_digest column is protected against mass assignment.

c.update_attributes(:password_digest => 'dummy')
c.password_digest                   # => nil (unchanged)

Similarly, the setting_password and changing_password attributes are protected.

c.attributes = { :setting_password => true, :password => '0000' }
c.setting_password?                 # => false

A class that includes Miniauth is forced to adopt the whitelist-principle regarding the mass assignment protection. That is, you have to enumerate the names of attributes that can be set via a hash by the attr_accessible method.

class User < ActiveRecord::Base
  include MiniAuth
  
  attr_accessible :name, :address, :phone
end

Note that the attributes password, password_confirmation, current_password, new_password, and new_password_confirmation are included in the whitelist by default.

If your class has a role such as :admin, you should enumerate the accessible attributes as follows:

class User < ActiveRecord::Base
  include MiniAuth
  
  attr_accessible :name, :address, :phone
  attr_accessible *(accessible_attributes(:default) + [ :is_admin ]), :as => :admin
end

For more information about mass assignment security, please refer to the Mass Assignment section of Rails Guides.

Random token

MiniAuth::RandomToken module provides an easy way to generate a random token and verify it.

The class method token takes a list of names and defines "generate_#{name}_token" and "verify_#{name}_token" methods dynamically.

class User < ActiveRecord::Base
  include MiniAuth::RandomToken
  
  attr_accessible :name, :address, :phone
  token :auto_login, :mail_confirmation
end

By calling generate_auto_login_token, you can generate a random hex string of 32 letters and set it to the auto_login_token column.

d = User.new(:name => "david")
d.generate_auto_login_token
d.auto_login_token                 # => "8d21d3830a3ef2aafe8a7c0388493883"

Call verify_auto_login_token to verify it. For example,

d.verify_auto_login_token(params[:token])

returns true if params[:token] equals to the generated token. Otherwise it returns false.

License

mini_auth is distributed under the MIT license. (MIT-LICENSE)

Copyright

Copyright (c) 2011-2012 Tsutomu Kuroda t-kuroda@oiax.jp.