Skip to content

Commit

Permalink
Tidy up token authentication implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Feb 2, 2010
1 parent 4878bdb commit 3781a0f
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 49 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rdoc
@@ -1,3 +1,7 @@
* enhancements
* Added gemspec to repo
* Added token authenticatable (by github.com/grimen)

== 0.9.1

* bug fix
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/sessions_controller.rb
Expand Up @@ -18,7 +18,7 @@ def create
set_flash_message :notice, :signed_in
sign_in_and_redirect(resource_name, resource, true)
else
set_now_flash_message :alert, warden.message || :invalid
set_now_flash_message :alert, (warden.message || :invalid)
build_resource
render_with_scope :new
end
Expand Down
4 changes: 4 additions & 0 deletions generators/devise_install/templates/devise.rb
Expand Up @@ -53,6 +53,10 @@
# Time interval to unlock the account if :time is enabled as unlock_strategy.
# config.unlock_in = 1.hour

# ==> Configuration for :token_authenticatable
# Defines name of the authentication token params key
# config.token_authentication_key = :auth_token

# ==> General configuration
# Load and configure the ORM. Supports :active_record (default), :mongo_mapper
# (requires mongo_ext installed) and :data_mapper (experimental).
Expand Down
4 changes: 2 additions & 2 deletions lib/devise.rb
Expand Up @@ -133,8 +133,8 @@ module Orm
@@mailer_sender

# Authentication token params key name of choice. E.g. /users/sign_in?some_key=...
mattr_accessor :authentication_token_param_key
@@authentication_token_param_key = :auth_token
mattr_accessor :token_authentication_key
@@token_authentication_key = :auth_token

class << self
# Default way to setup Devise. Run script/generate devise_install to create
Expand Down
40 changes: 23 additions & 17 deletions lib/devise/models/token_authenticatable.rb
Expand Up @@ -10,9 +10,7 @@ module Models
# You can overwrite configuration values by setting in globally in Devise (+Devise.setup+),
# using devise method, or overwriting the respective instance method.
#
# +authentication_token_param_key+ - Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
#
# +reset_authentication_token_on+ - Defines which callback hooks that should trigger a authentication token reset.
# +token_authentication_key+ - Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
#
# == Examples:
#
Expand All @@ -23,20 +21,29 @@ module TokenAuthenticatable
def self.included(base)
base.class_eval do
extend ClassMethods

before_save :ensure_authentication_token!
before_save :ensure_authentication_token
end
end

# Generate new authentication token (a.k.a. "single access token").
def reset_authentication_token
self.authentication_token = self.class.authentication_token
end

# Generate new authentication token and save the record.
def reset_authentication_token!
reset_authentication_token
self.save
end

# Generate authentication token unless already exists.
def ensure_authentication_token!
self.reset_authentication_token!(false) if self.authentication_token.blank?
def ensure_authentication_token
self.reset_authentication_token if self.authentication_token.blank?
end

# Generate new authentication token (a.k.a. "single access token").
def reset_authentication_token!(do_save = true)
self.authentication_token = self.class.authentication_token
self.save if do_save
# Generate authentication token unless already exists and save the record.
def ensure_authentication_token!
self.reset_authentication_token! if self.authentication_token.blank?
end

# Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
Expand All @@ -46,12 +53,11 @@ def valid_authentication_token?(incoming_auth_token)
end

module ClassMethods

::Devise::Models.config(self, :authentication_token_param_key)
::Devise::Models.config(self, :token_authentication_key)

# Authenticate a user based on authentication token.
def authenticate_with_token(attributes = {})
token = attributes[::Devise.authentication_token_param_key]
def authenticate_with_token(attributes)
token = attributes[self.token_authentication_key]
resource = self.find_for_token_authentication(token)
resource if resource.try(:valid_authentication_token?, token)
end
Expand All @@ -73,8 +79,8 @@ def authentication_token
# self.find_by_authentication_token(token, :conditions => conditions)
# end
#
def find_for_token_authentication(token, conditions = {})
self.find_by_authentication_token(token, :conditions => conditions)
def find_for_token_authentication(token)
self.find(:first, :conditions => { :authentication_token => token})
end

end
Expand Down
4 changes: 2 additions & 2 deletions lib/devise/strategies/token_authenticatable.rb
Expand Up @@ -25,9 +25,9 @@ def authenticate!
# Detect authentication token in params: scoped or not.
def authentication_token(scope)
if params[scope]
params[scope][::Devise.authentication_token_param_key]
params[scope][mapping.to.token_authentication_key]
else
params[::Devise.authentication_token_param_key]
params[mapping.to.token_authentication_key]
end
end

Expand Down
36 changes: 15 additions & 21 deletions test/integration/token_authenticatable_test.rb
Expand Up @@ -3,8 +3,8 @@
class TokenAuthenticationTest < ActionController::IntegrationTest

test 'sign in user should authenticate with valid authentication token and proper authentication token key' do
swap Devise, :authentication_token_param_key => :secret_token do
sign_in_as_new_user_with_token(:auth_token_key => :secret_token, :auth_token => VALID_AUTHENTICATION_TOKEN)
swap Devise, :token_authentication_key => :secret_token do
sign_in_as_new_user_with_token(:auth_token_key => :secret_token)

assert_response :success
assert_template 'users/index'
Expand All @@ -14,30 +14,22 @@ class TokenAuthenticationTest < ActionController::IntegrationTest
end

test 'user signing in with valid authentication token - but improper authentication token key - return to sign in form with error message' do
# FIXME: For some reason I18n value is not respected. Always render defalt one. =S
# store_translations :en, :devise => {:sessions => {:unauthenticated => 'Ouch!'}} do
# assert 'Ouch!', I18n.t('devise.sessions.unauthenticated') # for paranoia

swap Devise, :authentication_token_param_key => :donald_duck_token do
sign_in_as_new_user_with_token(:auth_token_key => :secret_token, :auth_token => VALID_AUTHENTICATION_TOKEN)

assert_redirected_to new_user_session_path(:unauthenticated => true)
follow_redirect!
swap Devise, :token_authentication_key => :donald_duck_token do
sign_in_as_new_user_with_token(:auth_token_key => :secret_token)
assert_redirected_to new_user_session_path(:unauthenticated => true)
follow_redirect!

# assert_contain 'Ouch!'
assert_contain 'Sign in'
assert_not warden.authenticated?(:user)
end
# end
assert_contain 'You need to sign in or sign up before continuing'
assert_contain 'Sign in'
assert_not warden.authenticated?(:user)
end
end

test 'user signing in with invalid authentication token should return to sign in form with error message' do
store_translations :en, :devise => {:sessions => {:invalid_token => 'LOL, that was not a single character correct.'}} do
sign_in_as_new_user_with_token(:auth_token => '*** INVALID TOKEN ***')

assert_redirected_to new_user_session_path(:invalid_token => true)
follow_redirect!
assert_equal users_path(Devise.authentication_token_param_key => '*** INVALID TOKEN ***'), session[:"user.return_to"]

assert_response :success
assert_contain 'LOL, that was not a single character correct.'
Expand All @@ -49,12 +41,14 @@ class TokenAuthenticationTest < ActionController::IntegrationTest
private

def sign_in_as_new_user_with_token(options = {}, &block)
options[:auth_token_key] ||= Devise.authentication_token_param_key
options[:auth_token_key] ||= Devise.token_authentication_key
options[:auth_token] ||= VALID_AUTHENTICATION_TOKEN

user = create_user(options)
user.authentication_token = VALID_AUTHENTICATION_TOKEN
user.save
visit users_path(options[:auth_token_key].to_sym => (options[:auth_token] || VALID_AUTHENTICATION_TOKEN))
yield if block_given?

visit users_path(options[:auth_token_key].to_sym => options[:auth_token])
user
end

Expand Down
18 changes: 12 additions & 6 deletions test/models/token_authenticatable_test.rb
Expand Up @@ -11,14 +11,20 @@ class TokenAuthenticatableTest < ActiveSupport::TestCase

test 'should reset authentication token' do
user = new_user

user.reset_authentication_token!(false)
user.reset_authentication_token
previous_token = user.authentication_token

user.reset_authentication_token!(false)
user.reset_authentication_token
assert_not_equal previous_token, user.authentication_token
end

test 'should ensure authentication token' do
user = new_user
user.ensure_authentication_token
previous_token = user.authentication_token
user.ensure_authentication_token
assert_equal previous_token, user.authentication_token
end

test 'should test for a valid authentication token' do
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
user = create_user
Expand All @@ -29,15 +35,15 @@ class TokenAuthenticatableTest < ActiveSupport::TestCase
test 'should authenticate a valid user with authentication token and return it' do
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
user = create_user
User.any_instance.stubs(:confirmed?).returns(true)
user.confirm!
authenticated_user = User.authenticate_with_token(:auth_token => user.authentication_token)
assert_equal authenticated_user, user
end

test 'should return nil when authenticating an invalid user by authentication token' do
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
user = create_user
User.any_instance.stubs(:confirmed?).returns(true)
user.confirm!
authenticated_user = User.authenticate_with_token(:auth_token => user.authentication_token.reverse)
assert_nil authenticated_user
end
Expand Down

0 comments on commit 3781a0f

Please sign in to comment.