Created
May 4, 2011 08:08
-
-
Save toretore/954909 to your computer and use it in GitHub Desktop.
Rails authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
User authentication with Rails | |
This is all you need to have fully working and secure authentication in Rails. | |
Why use this instead of <bloated auth framework>? Well, there are a number of | |
reasons, but I guess you'll just have to go through a few apps where you | |
learn the hard way why the idea of a "fully featured" authentication | |
framework that doesn't get on your nerves is a mirage. | |
Don't simply copy and paste this without understanding everything it does. It's | |
a starting point and proof of concept more than anything else. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ApplicationController < ActionController::Base | |
#The currently logged in user, if any | |
def current_user | |
#Use defined? to avoid additional finds when user is not found | |
@_current_user = session[:current_user_id] && User.find_by_id(session[:current_user_id]) unless defined?(@_current_user) | |
@_current_user | |
end | |
#Set the current user. Pass nil to unset, i.e. log out | |
def current_user=(user) | |
session[:current_user_id] = user && user.id | |
remove_instance_variable('@_current_user') if defined?(@_current_user) #Force reload in current_user | |
current_user | |
end | |
def logged_in? | |
!!current_user | |
end | |
def logged_out? | |
!logged_in? | |
end | |
def require_login | |
unless logged_in? | |
flash[:error] = 'You must be logged in to view this page' | |
redirect_to new_login_url | |
end | |
end | |
def require_logout | |
if logged_in? | |
flash[:error] = "You can't be logged in when viewing this page" | |
redirect_to login_url | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LoginsController < ApplicationController | |
skip_before_filter :require_login, :only => [:new, :create] | |
before_filter :require_logout, :only => [:new, :create] | |
def new | |
end | |
#POST /login?username=foo&password=bar | |
#Creates the login, AKA logs the user in | |
def create | |
if user = User.find_by_email(params[:email]) | |
if user.password == params[:password] | |
self.current_user = user | |
redirect_to root_url | |
else | |
flash.now[:error] = 'Wrong password' | |
render 'new' | |
end | |
else | |
flash.now[:error] = "Could not find user with email \"#{params[:email]}\"" | |
render 'new' | |
end | |
end | |
#DELETE /login | |
#Destroys the login, AKA logs the user out | |
def destroy | |
self.current_user = nil | |
redirect_to new_login_url | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'test_helper' | |
class LoginsControllerTest < ActionController::TestCase | |
attr_reader :controller, :request, :response | |
setup do | |
@user = Factory.create(:user, :email => 'humbaba', :password => 'password') | |
end | |
test "should create login with matching username and password" do | |
post :create, :email => 'humbaba', :password => 'password' | |
assert controller.logged_in? | |
assert_equal @user, controller.current_user | |
end | |
test "should not create login with non-matching username and password" do | |
post :create, :email => 'humbaba', :password => 'assword' | |
assert !controller.logged_in? | |
assert_nil controller.current_user | |
post :create, :email => 'enkidu', :password => 'password' | |
assert !controller.logged_in? | |
assert_nil controller.current_user | |
end | |
test "should be logged in already with current_user_id in session" do | |
get :show, {}, {:current_user_id => @user.id} | |
assert controller.logged_in? | |
assert_equal @user, controller.current_user | |
end | |
test "should not break on non-existing user's id in session" do | |
get :show, {}, {:current_user_id => 12345} | |
assert !controller.logged_in? | |
assert_nil controller.current_user | |
end | |
test "should destroy login" do | |
delete :destroy, {}, {:current_user_id => @user.id} | |
assert !controller.logged_in? | |
assert_nil controller.session[:current_user_id] | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
YourApp::Application.routes.draw do | |
resource :login | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'bcrypt' | |
class User < ActiveRecord::Base | |
def password | |
@password ||= password_hash && BCrypt::Password.new(password_hash) | |
end | |
def password=(password) | |
@password = nil #Force reload | |
self.password_hash = password && BCrypt::Password.create(password) #BCrypt will create a hash from nil | |
end | |
def self.authenticate(email, password) | |
if (user = find_by_email(email)) && user.password == password | |
user | |
else | |
nil | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Factory.define :user do |u| | |
u.sequence(:email){|n| "email#{n}@example.com" } | |
u.password 'password' | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'test_helper' | |
class UserTest < ActiveSupport::TestCase | |
setup do | |
@user = Factory.create(:user, :email => 'humb@ba', :password => 'password') | |
end | |
test "authenticate should return user when email and password match" do | |
assert_equal @user, User.authenticate('humb@ba', 'password') | |
end | |
test "authenticate should return nil when email and password do not match" do | |
assert_nil User.authenticate('humb@ba', 'assword') | |
assert_nil User.authenticate('ereshkigal', 'password') | |
end | |
test 'should be able to change password' do | |
assert @user.password == 'password' #assert_equal doesn't use ==, compare manually | |
@user.password = 'donkey' | |
assert @user.password == 'donkey' | |
@user.save! | |
assert User.find(@user.id).password == 'donkey' | |
assert User.authenticate('humb@ba', 'donkey') | |
end | |
test "should not treat nil as a password" do | |
@user.password = nil | |
assert_nil @user.password | |
assert_nil @user.password_hash | |
@user.password = 'notnil' | |
assert @user.password == 'notnil' | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment