Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Allowing http token auth to set the token_authentication_key if missing from params #2271

Merged
merged 6 commits into from

2 participants

Rob Hurring José Valim
Rob Hurring

Allows the user to configure devise to allow token auth headers to set the missing "token_authentication_key" param with a new setting in the config "allow_authorization_to_set_auth_token". When set to true it will parse out the token from the request headers and update the param for "token_authentication_key".

robhurring added some commits
Rob Hurring robhurring Allow http token authorization to set token_authentication_key in pla…
…ce of passing it in via params

It will not override existing token_authentication_key params if they are present.
3025b7e
Rob Hurring robhurring fix hanging line on method call 22a8cfe
José Valim
Owner

Thanks @robhurring. However, you explained how the feature is implemented, you haven't explained why you need it thought. Could please tell us why? Why the current token mechanism is not enough?

Rob Hurring

There is really nothing in particular lacking with the current basic auth flow for authenticating, but for an API we felt using token auth was semantically better. The reasoning behind this was more for allowing the API client to pass in options with the token which the app can use. I didn't include that piece of the code in this PR since I wasn't exactly sure how to make it feel natural in devise.

In our current implementation we're passing in a request signature with the token, the signature (and any other metadata) is then bubbled up to the app in env['devise.token_options'] and handled from there. It can also be used to handle token expiration or other use cases. If this sounds like it could be useful for devise I can commit those change as well.

lib/devise/models/token_authenticatable.rb
@@ -82,7 +82,7 @@ def authentication_token
generate_token(:authentication_token)
end
- Devise::Models.config(self, :token_authentication_key, :expire_auth_token_on_timeout)
+ Devise::Models.config(self, :token_authentication_key, :allow_authorization_to_set_auth_token, :expire_auth_token_on_timeout)
José Valim Owner

Can we call this configuration option something like allow_token_authenticatable_via_headers or something like it?

I like that much better -- fixed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/devise/strategies/token_authenticatable.rb
@@ -38,11 +38,24 @@ def remember_me?
# Try both scoped and non scoped keys.
def params_auth_hash
- if params[scope].kind_of?(Hash) && params[scope].has_key?(authentication_keys.first)
- params[scope]
- else
- params
+ auth_key = authentication_keys.first
+
+ return_params = \
José Valim Owner

We don't need the \. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
José Valim
Owner

In our current implementation we're passing in a request signature with the token, the signature (and any other metadata) is then bubbled up to the app in env['devise.token_options'] and handled from there.

This approach is also fine. We could include the options in env['devise.token_options'] as well. Could you please update your pull request?

Thanks a lot!

José Valim
Owner

Thanks a lot @robhurring! Sorry for the delay.

Maybe instead of merging into the params hash, we could add a valid_token_auth? check as well, similar to what we do with http auth:

https://github.com/robhurring/devise/blob/547439d94c32a63a11260f96e3713b88c6b6ac68/lib/devise/strategies/authenticatable.rb#L70

Rob Hurring

@josevalim no worries, its been a pretty busy month for me as well. I made those changes and mimicked the http auth flow pretty close. Give it a glance when you have a minute and let me know if theres any other changes that need to be made.

José Valim josevalim merged commit 1b8fd7c into from
José Valim
Owner

Perfect pull request! I have merged it, sorry for the delay!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 9, 2013
  1. Rob Hurring

    Allow http token authorization to set token_authentication_key in pla…

    robhurring authored
    …ce of passing it in via params
    
    It will not override existing token_authentication_key params if they are present.
  2. Rob Hurring
Commits on Feb 10, 2013
  1. Rob Hurring
  2. Rob Hurring

    renaming devise option "allow_authorization_to_set_auth_token" to "al…

    robhurring authored
    …low_token_authenticatable_via_headers"
Commits on Mar 4, 2013
  1. Rob Hurring
  2. Rob Hurring
This page is out of date. Refresh to see the latest.
4 lib/devise.rb
View
@@ -177,6 +177,10 @@ module Strategies
mattr_accessor :token_authentication_key
@@token_authentication_key = :auth_token
+ # Allow HTTP token authorization to set token_authentication_key
+ mattr_accessor :allow_token_authenticatable_via_headers
+ @@allow_token_authenticatable_via_headers = true
+
# Skip session storage for the following strategies
mattr_accessor :skip_session_storage
@@skip_session_storage = []
2  lib/devise/models/token_authenticatable.rb
View
@@ -82,7 +82,7 @@ def authentication_token
generate_token(:authentication_token)
end
- Devise::Models.config(self, :token_authentication_key, :expire_auth_token_on_timeout)
+ Devise::Models.config(self, :token_authentication_key, :allow_token_authenticatable_via_headers, :expire_auth_token_on_timeout)
end
end
end
31 lib/devise/strategies/token_authenticatable.rb
View
@@ -14,6 +14,10 @@ def store?
super && !mapping.to.skip_session_storage.include?(:token_auth)
end
+ def valid?
+ super || valid_for_token_auth?
+ end
+
def authenticate!
resource = mapping.to.find_for_token_authentication(authentication_hash)
return fail(:invalid_token) unless resource
@@ -36,6 +40,33 @@ def remember_me?
false
end
+ # Check if the model accepts this strategy as token authenticatable.
+ def token_authenticatable?
+ mapping.to.allow_token_authenticatable_via_headers
+ end
+
+ # Check if this is strategy is valid for token authentication by:
+ #
+ # * Validating if the model allows http token authentication;
+ # * If the http auth token exists;
+ # * If all authentication keys are present;
+ #
+ def valid_for_token_auth?
+ token_authenticatable? && auth_token.present? && with_authentication_hash(:token_auth, token_auth_hash)
+ end
+
+ # Extract the auth token from the request
+ def auth_token
+ @auth_token ||= ActionController::HttpAuthentication::Token.
+ token_and_options(request)
+ end
+
+ # Extract a hash with attributes:values from the auth_token.
+ def token_auth_hash
+ request.env['devise.token_options'] = auth_token.last
+ {authentication_keys.first => auth_token.first}
+ end
+
# Try both scoped and non scoped keys.
def params_auth_hash
if params[scope].kind_of?(Hash) && params[scope].has_key?(authentication_keys.first)
5 lib/generators/templates/devise.rb
View
@@ -182,6 +182,11 @@
# Defines name of the authentication token params key
# config.token_authentication_key = :auth_token
+ # Tell if authentication through HTTP Token Auth is enabled. True by default.
+ # Any extra options passed along with the options will be available in the
+ # env['devise.token_options'] hash
+ # config.allow_token_authenticatable_via_headers = false
+
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
44 test/integration/token_authenticatable_test.rb
View
@@ -129,6 +129,46 @@ class TokenAuthenticationTest < ActionDispatch::IntegrationTest
end
end
+ test 'authenticate with valid authentication token key and value through http header' do
+ swap Devise, :token_authentication_key => :secret_token do
+ sign_in_as_new_user_with_token(:token_auth => true)
+
+ assert_response :success
+ assert_match '<email>user@test.com</email>', response.body
+ assert_equal request.env['devise.token_options'], {}
+ assert warden.authenticated?(:user)
+ end
+ end
+
+ test 'authenticate with valid authentication token key and value through http header, with options' do
+ swap Devise, :token_authentication_key => :secret_token do
+ signature = "**TESTSIGNATURE**"
+ sign_in_as_new_user_with_token(:token_auth => true, :token_options => {:signature => signature, :nonce => 'def'})
+
+ assert_response :success
+ assert_match '<email>user@test.com</email>', response.body
+ assert_equal request.env['devise.token_options'][:signature], signature
+ assert_equal request.env['devise.token_options'][:nonce], 'def'
+ assert warden.authenticated?(:user)
+ end
+ end
+
+ test 'authenticate with valid authentication token key and value through http header without allowing token authorization setting is denied' do
+ swap Devise, :token_authentication_key => :secret_token, :allow_token_authenticatable_via_headers => false do
+ sign_in_as_new_user_with_token(:token_auth => true)
+
+ assert_response :unauthorized
+ assert_nil warden.user(:user)
+ end
+ end
+
+ test 'does not authenticate with improper authentication token value in header' do
+ sign_in_as_new_user_with_token(:token_auth => true, :auth_token => '*** INVALID TOKEN ***')
+
+ assert_response :unauthorized
+ assert_nil warden.user(:user)
+ end
+
private
def sign_in_as_new_user_with_token(options = {})
@@ -140,6 +180,10 @@ def sign_in_as_new_user_with_token(options = {})
if options[:http_auth]
header = "Basic #{Base64.encode64("#{VALID_AUTHENTICATION_TOKEN}:X")}"
get users_path(:format => :xml), {}, "HTTP_AUTHORIZATION" => header
+ elsif options[:token_auth]
+ token_options = options[:token_options] || {}
+ header = ActionController::HttpAuthentication::Token.encode_credentials(options[:auth_token], token_options)
+ get users_path(:format => :xml), {}, "HTTP_AUTHORIZATION" => header
else
visit users_path(options[:auth_token_key].to_sym => options[:auth_token])
end
Something went wrong with that request. Please try again.