Security issue
When using DeviseTokenAuth::Concerns::User together with 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:
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:
# 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:
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:
- Do not implicitly include
:database_authenticatable
- Let the application define authentication modules explicitly
- Allow configuration of default Devise modules
- 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.
Security issue
When using
DeviseTokenAuth::Concerns::Usertogether with devise-two-factor, the concern implicitly enables:database_authenticatable, which can lead to a 2FA bypass vulnerability.Context
devise_token_authdevise-two-factorExample model:
Problem
The concern contains the following logic:
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_authenticatableis implicitly added, regardless of the developer’s intended configuration.Relying on developers to include the concern after calling devise is not safe, since:
Security impact
According to devise-two-factor documentation:
This means the final configuration becomes:
This creates a silent insecure state, where:
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:
:database_authenticatable:database_authenticatableand:two_factor_authenticatableare presentContribution
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.