lynndylanhurley / lynndylanhurley/devise_token_auth

Security issue: database_authenticatable is implicitly added by concern, allowing 2FA bypass with devise-two-factor

Open
#1,682 0 comments 0 reactions 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

## Security issue

When using `DeviseTokenAuth::Concerns::User` together with [devise-two-factor](https://github.com/devise-two-factor/devise-two-factor), the concern implicitly enables `:database_authenticatable`, which can lead to a **2FA bypass vulnerability**.

## Context

- Devise-based application
- Using `devise_token_auth`
- Adding 2FA via `devise-two-factor`

Example model:

```ruby
class User < ApplicationRecord
include DeviseTokenAuth::Concerns::User

devise :two_factor_authenticatable, :two_factor_backupable,
otp_backup_code_length: 10,
otp_number_of_backup_codes: 10

devise :confirmable, :recoverable, :trackable,
:timeoutable, :lockable
end
```

## Problem

The concern contains the following logic:

```rb
# Hack to check if devise is already enabled
if method_defined?(:devise_modules)
devise_modules.delete(:omniauthable)
else
devise :database_authenticatable, :registerable,
:recoverable, :validatable, :confirmable
end
```

Because concerns are typically included at the top of the model, devise_modules is not yet defined at that point, so it always falls into the else branch.

As a result, `:database_authenticatable` is implicitly added, regardless of the developer’s intended configuration.

Relying on developers to include the concern after calling devise is not safe, since:
- it goes against common Rails conventions (concerns are usually declared at the top)
- this requirement is not explicitly documented

## Security impact

According to devise-two-factor documentation:

> Loading both :database_authenticatable and :two_factor_authenticatable in a model is a security issue. It will allow users to bypass two-factor authentication regardless of how otp_required_for_login is set due to the way Warden handles cascading strategies!

This means the final configuration becomes:

```rb
User.devise_modules
# => [:database_authenticatable, :two_factor_authenticatable, ...]
```

This creates a silent insecure state, where:
- The application appears correctly configured
- 2FA is enabled
- But authentication can bypass 2FA entirely

## Root cause

The concern makes an irreversible authentication decision before the model's Devise configuration is fully defined. This breaks composability with other Devise extensions and prevents detecting conflicts like `two_factor_authenticatable`.

## Suggested approaches

Some possible directions:

1. Do not implicitly include `:database_authenticatable`
- Let the application define authentication modules explicitly
2. Allow configuration of default Devise modules
- e.g. via initializer
3. Detect and prevent unsafe combinations
- Raise or warn if both `:database_authenticatable` and `:two_factor_authenticatable` are present

## Contribution

I’d be happy to open a PR to address this. Before doing so, I’d like guidance on which approach aligns best with the project’s direction.

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 DeviseTokenAuth::Concerns::User and inspect the devise_modules branch shown in the issue. Reproduce the model configuration with devise-two-factor, then seek maintainer guidance on which proposed approach to implement. Done means the concern no longer silently creates the unsafe database_authenticatable and two_factor_authenticatable combination.

Written by the indexing model from the issue text.

Assessment

Tech stack
rails, ruby
Domain
authentication, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.