lynndylanhurley / lynndylanhurley/devise_token_auth

BUG: config.enable_standard_devise_support = true returns wrong current_user

Open
#1,060 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Ruby
Stars
3.6k
Forks
1.1k
PR merge metrics
No merged PRs in 30d

Description

- devise_token_auth **[0.1.42]**
- Ruby **[2.4.2]**
- Rails **[5.1.4]**
- relevant Gemfile.lock
```
devise (4.3.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.2)
responders
warden (~> 1.2.3)
devise-i18n (1.5.0)
devise (>= 3.4)
devise_token_auth (0.1.42)
devise (> 3.5.2, <= 4.3)
rails (< 6)
```

* **Request and response headers**:
Described below.

* **Rails Stacktrace**: this can be found in the `log/development.log` of your API.
* **Environmental Info**: My app uses both `devise` and `devise_token_auth`, which I achieve by subclassing ApplicationController into `ApiController` and `ApplicationController`. One inheriting from `ApplicationController::Base` the other `ApplicationController::API`

* **Routes**: I'm using both `devise` and `devise_token_auth`

```
namespace :api do |api|
mount_devise_token_auth_for 'User', at: 'auth'
end

as :user do
# post 'users/password/change', to: 'users/passwords#change', as: 'change_user_password'
patch 'users/password/change', to: 'users/passwords#change', as: 'change_user_password'
get 'users/password/check-email', to: 'users/passwords#check_email', as: 'check_email_user_password'
get 'users/password/reset-success', to: 'users/passwords#reset_success', as: 'reset_success_user_password'
end

devise_for :users, skip: :registrations, controllers: {
confirmations: 'users/confirmations',
passwords: 'users/passwords',
# omniauth_callbacks: 'web/users/omniauth_callbacks',
# registrations: 'users/registrations',
sessions: 'users/sessions',
}

```

* **Gems**: Using devise web. Also using `paperTrail` gem, which writes a copy of the Users table over to UserVersion.

* **Custom Overrides**: I'm (trying) to namespace both devise and devise_token_auth into separate controllers. But I have run into problems completely doing this (explained below).

* **Custom Frontend**: iPhone & Android native clients, Postman for testing

## Bug setup

In my app, my API consumers can switch between child and parent users, with the Bearer token `uid / client / access-token` being the mechanism for how my API consumers can switch between users.

However they were complaining about getting back incorrect data, with data from the wrong user getting sent back to them. After investigating I found their concerns to be true, with `devise_token_auth` returning back incorrect users based on the headers it is sent.

To replicate. I created two users with the same password:

user 1: `dojouser17+marge@gmail.com`
user 2: `dojouser17+bart@gmail.com`

First, log in with user 1:

```
GET api/auth/sign_in
{
"email": "dojouser17+bart@gmail.com", "password": "That1234"
}
```

receiving correctly back headers corresponding to uid = `dojouser17+bart@gmail.com` / user 1:

```
access-token →z9fS0CuMUZk9_F1FpRY5Tw
cache-control →max-age=0, private, must-revalidate
client →AfYoRq09GaCmATnzAqjAgQ
content-type →application/json; charset=utf-8
etag →W/"1edac4bb16fda94888d87a447cbc0834"
expiry →1516659606
token-type →Bearer
transfer-encoding →chunked
uid →dojouser17+bart@gmail.com
x-content-type-options →nosniff
x-frame-options →SAMEORIGIN
x-request-id →3b1f7b69-03aa-4d54-9911-0aca229d195a
x-runtime →0.217444
x-xss-protection →1; mode=block
```

Next, log in with user 2:

```
GET api/auth/sign_in
{
"email": "dojouser17+marge@gmail.com", "password": "That1234"
}

```

receiving correctly back headers corresponding to uid = `dojouser17+marge@gmail.com` / user 2:

```
access-token →4jktXiC-jDG-yMRUboUL2A
cache-control →max-age=0, private, must-revalidate
client →GV1hagOF6CRWIZkvzBHtrA
content-type →application/json; charset=utf-8
etag →W/"aae490494e2e72440f4f0c8273f3dfaf"
expiry →1516660022
token-type →Bearer
transfer-encoding →chunked
uid →dojouser17+marge@gmail.com
x-content-type-options →nosniff
x-frame-options →SAMEORIGIN
x-request-id →ee93f722-3a71-4f9b-bfdf-2408e1d9cec1
x-runtime →0.223127
x-xss-protection →1; mode=block
```

## Bug

Now here's where the bug happens. When I copy the headers for these two separate users into two separate requests and hit an app endpoint requiring authentication. When `config.enable_standard_devise_support` is `true` the api returns back the incorrect current_user (confirmed with byebug), always defaulting to the first user that hit the endpoint (user 1), instead of user 2.

As proof, attached is a screenshot where you can see `dojouser17+marge@gmail.com` (user 2) sending a token request and getting a response back with authentication headers meant for `dojouser17+bart@gmail.com` (user 1):

![screen shot 2018-01-08 at 3 20 48 pm](https://user-images.githubusercontent.com/6710303/34697678-0639e7f8-f489-11e7-8069-9a3c3149aaf9.png)

Restarting the server had no effect on the Postman requests.

## Fix for now

When I changed `config.enable_standard_devise_support = false` the problem went away. Consequently I strongly suspect there is an issue with `DeviseTokenAuth.enable_standard_devise_support`, such as in the way it gets invoked within `set_user_by_token`:

```ruby

def set_user_by_token(mapping=nil)
# determine target authentication class
rc = resource_class(mapping)

# no default user defined
return unless rc

# gets the headers names, which was set in the initialize file
uid_name = DeviseTokenAuth.headers_names[:'uid']
access_token_name = DeviseTokenAuth.headers_names[:'access-token']
client_name = DeviseTokenAuth.headers_names[:'client']

# parse header for values necessary for authentication
uid = request.headers[uid_name] || params[uid_name]
@token ||= request.headers[access_token_name] || params[access_token_name]
@client_id ||= request.headers[client_name] || params[client_name]

# client_id isn't required, set to 'default' if absent
@client_id ||= 'default'

# check for an existing user, authenticated via warden/devise, if enabled
if DeviseTokenAuth.enable_standard_devise_support
devise_warden_user = warden.user(rc.to_s.underscore.to_sym)
if devise_warden_user && devise_warden_user.tokens[@client_id].nil?
@used_auth_by_token = false
@resource = devise_warden_user
@resource.create_new_auth_token
end
end

...
```

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at set_user_by_token, especially the enable_standard_devise_support branch, and reproduce the issue with the two users and separate uid, client, and access-token headers described above. Done means requests authenticated with each token consistently return the corresponding current_user while standard Devise support remains enabled.

Written by the indexing model from the issue text.

Assessment

Tech stack
rails, ruby
Domain
api, authentication
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.