Skip to content

Commit

Permalink
Added 'Authorization' header with bearer token (#1534)
Browse files Browse the repository at this point in the history
* Sets authorization header name

* Writes and reads Authorization token

* On decoding bearer token, if it is an invalid base64, rescues to empty hash

* Added controller tests for Authorization header
  • Loading branch information
rhiroshi authored Jun 24, 2022
1 parent 798255e commit 8f44a8c
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 7 deletions.
17 changes: 14 additions & 3 deletions app/controllers/devise_token_auth/concerns/set_user_by_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ def set_user_by_token(mapping = nil)
uid_name = DeviseTokenAuth.headers_names[:'uid']
access_token_name = DeviseTokenAuth.headers_names[:'access-token']
client_name = DeviseTokenAuth.headers_names[:'client']
authorization_name = DeviseTokenAuth.headers_names[:"authorization"]

# Read Authorization token and decode it if present
decoded_authorization_token = decode_bearer_token(request.headers[authorization_name])

# gets values from cookie if configured and present
parsed_auth_cookie = {}
Expand All @@ -45,10 +49,10 @@ def set_user_by_token(mapping = nil)
end

# parse header for values necessary for authentication
uid = request.headers[uid_name] || params[uid_name] || parsed_auth_cookie[uid_name]
uid = request.headers[uid_name] || params[uid_name] || parsed_auth_cookie[uid_name] || decoded_authorization_token[uid_name]
@token = DeviseTokenAuth::TokenFactory.new unless @token
@token.token ||= request.headers[access_token_name] || params[access_token_name] || parsed_auth_cookie[access_token_name]
@token.client ||= request.headers[client_name] || params[client_name] || parsed_auth_cookie[client_name]
@token.token ||= request.headers[access_token_name] || params[access_token_name] || parsed_auth_cookie[access_token_name] || decoded_authorization_token[access_token_name]
@token.client ||= request.headers[client_name] || params[client_name] || parsed_auth_cookie[client_name] || decoded_authorization_token[client_name]

# client isn't required, set to 'default' if absent
@token.client ||= 'default'
Expand Down Expand Up @@ -128,6 +132,13 @@ def update_auth_header

private

def decode_bearer_token(bearer_token)
return {} if bearer_token.blank?

encoded_token = bearer_token.split.last # Removes the 'Bearer' from the string
JSON.parse(Base64.strict_decode64(encoded_token)) rescue {}
end

def refresh_headers
# Lock the user record during any auth_header updates to ensure
# we don't have write contention from multiple threads
Expand Down
10 changes: 8 additions & 2 deletions app/models/devise_token_auth/concerns/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,20 @@ def build_auth_header(token, client = 'default')
# client may use expiry to prevent validation request if expired
# must be cast as string or headers will break
expiry = tokens[client]['expiry'] || tokens[client][:expiry]

{
headers = {
DeviseTokenAuth.headers_names[:"access-token"] => token,
DeviseTokenAuth.headers_names[:"token-type"] => 'Bearer',
DeviseTokenAuth.headers_names[:"client"] => client,
DeviseTokenAuth.headers_names[:"expiry"] => expiry.to_s,
DeviseTokenAuth.headers_names[:"uid"] => uid
}
headers.merge!(build_bearer_token(headers))
end

def build_bearer_token(auth)
encoded_token = Base64.strict_encode64(auth.to_json)
bearer_token = "Bearer #{encoded_token}"
{DeviseTokenAuth.headers_names[:"authorization"] => bearer_token}
end

def update_auth_header(token, client = 'default')
Expand Down
3 changes: 2 additions & 1 deletion lib/devise_token_auth/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class Engine < ::Rails::Engine
self.enable_standard_devise_support = false
self.remove_tokens_after_password_reset = false
self.default_callbacks = true
self.headers_names = { 'access-token': 'access-token',
self.headers_names = { 'authorization': 'Authorization',
'access-token': 'access-token',
'client': 'client',
'expiry': 'expiry',
'uid': 'uid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,51 @@ class DeviseTokenAuth::TokenValidationsControllerTest < ActionDispatch::Integrat
@token = @auth_headers['access-token']
@client_id = @auth_headers['client']
@expiry = @auth_headers['expiry']

@authorization_header = @auth_headers.slice('Authorization')
# ensure that request is not treated as batch request
age_token(@resource, @client_id)
end

describe 'using only Authorization header' do
describe 'using valid Authorization header' do
before do
get '/auth/validate_token', params: {}, headers: @authorization_header
end

test 'token valid' do
assert_equal 200, response.status
end
end

describe 'using invalid Authorization header' do
describe 'with invalid base64' do
before do
get '/auth/validate_token', params: {}, headers: {'Authorization': 'Bearer invalidtoken=='}
end

test 'returns access denied' do
assert_equal 401, response.status
end
end

describe 'with valid base64' do
before do
valid_base64 = Base64.strict_encode64({
"access-token": 'invalidtoken',
"token-type": 'Bearer',
"client": 'client',
"expiry": '1234567'
}.to_json)
get '/auth/validate_token', params: {}, headers: {'Authorization': "Bearer #{valid_base64}"}
end

test 'returns access denied' do
assert_equal 401, response.status
end
end
end
end

describe 'vanilla user' do
before do
get '/auth/validate_token', params: {}, headers: @auth_headers
Expand Down

0 comments on commit 8f44a8c

Please sign in to comment.