Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce config to allow for password complexity #5727

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

kykyi
Copy link

@kykyi kykyi commented Nov 22, 2024

In relation to #5591

This PR introduces application config to allow for password complexity to be granularly managed with new validation options for:

  • presence of a lower case letter
  • presence of a upper case letter
  • presence of a number
  • presence of a special character (from a list of special characters that is configurable)

These are all false by default, and configurable like:

Devise.setup do |config|
    config.password_complexity = {
      require_upper: false,    
      require_lower: false,    
      require_digit: false,   
      require_special_character: false,
      allowed_special_characters: nil
  }
end

Note

  • I haven't run any linting, I couldn't find instructions on what config was used for that 😄

@kykyi kykyi force-pushed the feautre/provide-config-options-for-password-complexity branch 2 times, most recently from 98a037a to a6301cc Compare November 22, 2024 04:03
@kykyi
Copy link
Author

kykyi commented Nov 24, 2024

Hey @nashby if I could please request a review 😄

@kykyi
Copy link
Author

kykyi commented Dec 3, 2024

Polite bump @nashby @carlosantoniodasilva 😇

@datpmt
Copy link

datpmt commented Dec 9, 2024

how about modify the following configurations in the initializer file as below?

config.password_complexity = {
  upper: 1,    # At least 1 uppercase letter
  lower: 2,    # At least 2 lowercase letters
  digit: 3,    # At least 3 digits
  special: 4,  # At least 4 special characters
}

@kykyi
Copy link
Author

kykyi commented Dec 9, 2024

how about modify the following configurations in the initializer file as below?

config.password_complexity = {
  upper: 1,    # At least 1 uppercase letter
  lower: 2,    # At least 2 lowercase letters
  digit: 3,    # At least 3 digits
  special: 4,  # At least 4 special characters
}

Thanks @datpmt seems like an elegant solution ✅ One issue which I could see arise however could be a clash between this and password length minimums? For ex, if you set the above, but stuck with the default 8 character minimum, you couldn't satisfy all the configured preferences. I think something like this could use your nicer syntax but also be more ergonomic with the wider validation system:

config.password_complexity = {
  upper: true,    # require upper
  lower: false,    # don't require lower
  digit: true,    # require digit
  special: true,  # require special character
  special_characters: ["!", "?", "@", "\"]
}

What do you think?

@datpmt
Copy link

datpmt commented Dec 9, 2024

stuck with the default 8 character minimum

config.password_complexity = {
  upper: true,       # require upper
  lower: false,      # don't require lower
  digit: true,       # require digit
  # special: true,   # redundant
  special_characters: ["!", "?", "@", "\"] # empty <=> special: false
}

@kykyi Ah I see. Cool! Let do it! 👍

@kykyi
Copy link
Author

kykyi commented Dec 10, 2024

@datpmt updated to use your dict style ✅

def with_password_requirement(requirement, value)
# Change the password requirement and restore it after the block is executed
original_password_complexity= User.public_send("password_complexity")
original_value = original_password_complexity[requirement]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless assignment to variable - original_value.

@kykyi kykyi force-pushed the feautre/provide-config-options-for-password-complexity branch from 36e5f42 to 776a657 Compare December 15, 2024 22:37
@kykyi kykyi requested a review from datpmt December 17, 2024 00:55
@nvasilevski
Copy link

nvasilevski commented Dec 18, 2024

Sorry for stirring the pot but I wanted to cross-post an opinion that presence of upper-cased letters, special characters and numbers has very little to do with password strength and what really contributes to the password strength is the length of the password. I'm genuinely worried that enforcing these password requirements from default will only contribute to poor user experience and potentially less secure passwords overall

More context - rails/rails#53984 (comment)

Upd: I overlooked the fact that all these requirements are disabled by default which is good. So perhaps it's still useful for applications that have to comply with regulations that are out of their control. I just don't think that setting these requirements should be encouraged

@kykyi
Copy link
Author

kykyi commented Dec 18, 2024

Agreed @nvasilevski I think whilst this change pushes users to increase the entropy of their passwords, setting these and forgetting could lead devs into a false sense of security ➕

@timdiggins
Copy link

I agree with @nvasilevski - here's a specific argument against complexity requirements from the UK's National Cyber Security Centre: https://www.ncsc.gov.uk/collection/passwords/updating-your-approach#PasswordGuidance:UpdatingYourApproach-Donotusecomplexityrequirements

Recommend closure of the issue for Devise

@kykyi kykyi closed this Dec 19, 2024
@fthobe
Copy link

fthobe commented Jan 5, 2025

Picking up on a friendly invitation @nvasilevski given here
Mates, I would like to break some glass here.

I do not believe that what you write is wrong, the contrary, I believe some very valid points have been made. I am, as probably also you, sure that we are going towards a mixture of MFA and / or passwordless login and that's overall a good thing, but the environments around us are frequently not there yet.

The problem is the world:

  • password complexity / length policies are frequenty requirements to pass audits;
  • password complexity / length should have a reference implementation to avoid errors in implementation;
  • higher complexity while not scaling as well as length is still better than nothing.

I would also mention that the Password Guidance of the NCSC that you linked is not the only opinion and as much as I love the UK, this guideline is derived from NIST. The reasoning behind it was that statistics had shown that strong requirements create weaker user generated passwords:
Simply users went with "myf!stname$" instead of "some-very-s3cret-words-1n-a-$afe" as they struggled to remember too complex and to long passwords that needed to be changed frequently. Same applied to permutations "my-secretpassword$" became "my-secretpassword$$" upon frequently forced changes. Something that thanks to hashes and salts can (fortunately) not be tracked by a platform.

But all of there guidance needs to be seen in context of the remaining document that advises also:

  1. having password managers;
  2. having preferably time generated OTP tokens;
  3. being hosted on a system by somebody who understands crafting secure servers and infrastructure.

I think @kykyi made a great PR covering everything from configurability to effective resolution of the issue, maybe you could give it a look with different eyes (fearing weak integrations instead of fearing false sense of security) and we add some lines into documentation to make this a better PR covering also that the entire UTF-8 character set is only as great as the password length.

I'd really appreciate a feedback from you guys on this and I really appreciate what you are doing here, just keep in mind that stuff like NIST / NCSC guidelines are forward looking and not backward looking and need to be seen in context of the entire document. In a perfect world we would all have a password manager and OTP token for every password, but well, we all have grandmothers / fathers and parents having issues remembering 5 letters, alfanumeric or not, struggling to use a password manager. Given what devise is, it should have everything on board to make an informed decision on this topic and implement it.

@kykyi
Copy link
Author

kykyi commented Jan 5, 2025

Thanks for the perspective and for weighing in @fthobe 🙏 . I'll reopen and wait for a maintainer to merge/comment/close just so the issue can be resolved 😄

@kykyi kykyi reopened this Jan 5, 2025
@fthobe
Copy link

fthobe commented Jan 5, 2025

Thanks for the perspective and for weighing in @fthobe 🙏 . I'll reopen and wait for a maintainer to merge/comment/close just so the issue can be resolved 😄

Actually @nvasilevski did not obligate you to close it, sometimes things need some time to get traction and sometimes topics move in waves.

Be patient, open source is neither fast nor democratic 😅

@fthobe
Copy link

fthobe commented Jan 7, 2025

@timdiggins Hey, I would be super interested in your oppinion on my comment :)

@fthobe
Copy link

fthobe commented Jan 16, 2025

Ping @nvasilevski @datpmt @timdiggins

Could you retake a look at this PR and the comments made above.

@datpmt
Copy link

datpmt commented Jan 16, 2025

Ping @nvasilevski @datpmt @timdiggins

Could you retake a look at this PR and the comments made above.

First of all, I completely agree that the length of a password significantly impacts its strength.

In fact, many websites and company security policies require users to apply complexity rules to their passwords. This means that web applications using Devise have a valid reason to implement password complexity as a feature. Whether to enable it or not is up to the owner’s discretion.

I believe Devise should offer this option.

@fthobe
Copy link

fthobe commented Jan 16, 2025

I believe Devise should offer this option.

@datpmt Is this PR for you acceptable or does it need additional work?

@timdiggins
Copy link

timdiggins commented Jan 16, 2025

@fthobe Im not a maintainer/committee here so my 10c isn't worth so much 😀

A pragmatic response: My sense is that there isn't much maintainer involvement and devise is going into decline - there are CI-fix and bug fix PRs that have had no maintainer involvement so I don't think there's feature development will gain any traction.

@fthobe
Copy link

fthobe commented Jan 16, 2025

@timdiggins a lot of stuff still relies on it so we need to work with what we have.

I honestly think it's a very mature product.

Anyhow I'd really appreciate your opinion.

@carlosantoniodasilva does this PR have a shot to be integrated?

@datpmt
Copy link

datpmt commented Jan 16, 2025

I believe Devise should offer this option.

@datpmt Is this PR for you acceptable or does it need additional work?

I will take the time to run it locally and provide a review as soon as possible.

@datpmt
Copy link

datpmt commented Mar 7, 2025

@kykyi @fthobe
I am just someone who supports this pull request and has given some reviews from my personal perspective. I am not the person who can merge this pull request. You know, I am still struggling with my busy life, but I am still keeping an eye on this pull request.

@fthobe
Copy link

fthobe commented Mar 7, 2025

@nashby what can be done to bring this over the finish line?

@codepilotsf
Copy link

Yes please!!!

# require_upper: false,
# require_lower: false,
# require_digit: false,
# special_characters: "!?@#$%^&*()_+-=[]{}|:;<>,./"
Copy link

@salzig salzig Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO: We shouldn't limit the list of allowed special characters, even if this is only a suggestion because it isn't enabled by default. If the user is able remember and write any of this characters «∑€®†Ω¨⁄øπ•±å‚∂ƒ©ªº∆@œæ‘≤¥≈ç√∫~µ∞…»„‰¸˝ˇÁÛØ∏°ÅÍ™ÏÌÓıˆflŒÆ’≥‡ÙÇ◊‹›˘˛÷— (just examples), we should make sure they can be used with suggested default.

What's wrong with asking for any non word character? Something like \W

/\W/ - A non-word character ([^a-zA-Z0-9_]). – https://ruby-doc.org/core-2.5.8/Regexp.html#class-Regexp-label-Character+Classes

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, updated the code, looks much cleaner now 🙏

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @salzig I really appreciate this, but we do not all have identical systems. Special character support is not universal and some characters simply do not work everywhere.

Copy link

@salzig salzig Mar 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, not all work everywhere, but the default should not reduce security in unnecessary manner. Special characters is still configurable.

Edit: @kykyi why did you remove the option to alter the special characters? If we add this at all, it’s a sadly necessary flexibility to reduce special characters to a specific list. It’s just the default that should not limit too hard.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I can revert to allow a configurable list of special chars

@salzig
Copy link

salzig commented Mar 8, 2025

I think it's good to question the default of 6 to 128 characters, especially since it is configurable it should maybe suggest a more "demanding" default of at least 12 characters.

But I don't like this implementation.

There are multiple ways to have secure passwords, enforcing one way would reduce the number of possible passwords that any user could use. Suggesting a naive at least one character of 4 character-classes isn't considered the single best approach, so we shouldn't use it as suggestion.

If there is any specific requirement you have to comply with, use a specific password validator. Maybe it would be good to have gems like pci_dss_password_validator, password_validator_bsi, password_rules_ncsc, nist_password_validator, … and suggest with a comment that those could be used to comply with local regulations. But choosing a global default (devise is used everywhere) should (IMHO) be considered out of scope.

To describe it more dystopian: Attackers would be happy to know that with a devise application there is a good chance the password is following a specific pattern. If we want to improve the security of users using applications build with devise, we shouldn't make it easier for attackers. Which means making it harder for attackers should be our top priority.

Want to make it harder for attackers? Rate-Limit the login per user. -> have a look into Rack::Attack or similar.

Memorized secrets SHALL be at least 8 characters in length if chosen by the subscriber. … No other complexity requirements for memorized secrets SHOULD be imposed. https://pages.nist.gov/800-63-3/sp800-63b.html#5111-memorized-secret-authenticators

Using complexity requirements (that is, where staff can only use passwords that are suitably complex) is a poor defence against guessing attacks. https://www.ncsc.gov.uk/collection/passwords/updating-your-approach#PasswordGuidance:UpdatingYourApproach-Donotusecomplexityrequirements

A strong password can be 'short and complex' or 'long and less complex'. … It is 20 to 25 characters long and uses two types of character (e.g. a sequence of words). … It is eight characters long, uses three types of character and is also protected by multi-factor authentication. This is the recommended method, generally speaking. https://www.bsi.bund.de/EN/Themen/Verbraucherinnen-und-Verbraucher/Informationen-und-Empfehlungen/Cyber-Sicherheitsempfehlungen/Accountschutz/Sichere-Passwoerter-erstellen/sichere-passwoerter-erstellen_node.html

Example 1: Passwords must be composed of at least 12 characters including upper-case and lower-case
letters, numbers and special characters to be chosen in a list of at least 37 possible special characters.
Example 2: Passwords must be at least 14 characters long, including upper-case and lower-case letters
and numbers, with no required special characters.
Example 3: Passphrases based on words in the French language must consist of at least 7 words. https://www.cnil.fr/sites/cnil/files/atoms/files/draft_recommendation_-_passwords_and_other_unshared_secrets_version_for_public_consultation.pdf

@fthobe
Copy link

fthobe commented Mar 8, 2025

@salzig thank for your joining the conversation. I believe all your points are valid and that this PR does not represent the ultimate solution, but simply throws something most of us do anyway in a standardised procedure.

Right now most devise installations (especially those using spree or solidus) and plenty of other apps using transaction relevant data or even store cc tokens will have one or another way to raise the complexity scheme of passwords. In my school of thought, creating outcome variability in the way that this done does not ease maintenance or security and having this standardised might not be perfect but a step forward.

@kykyi kykyi force-pushed the feautre/provide-config-options-for-password-complexity branch 2 times, most recently from ee6bea3 to 44dca10 Compare March 9, 2025 03:56
@kykyi kykyi force-pushed the feautre/provide-config-options-for-password-complexity branch from 44dca10 to 3c86a71 Compare March 9, 2025 04:12
@kykyi
Copy link
Author

kykyi commented Mar 9, 2025

Hey all, I've updated the README and included an updated for \W based on your suggestion @salzig 🙏

I agree this PR won't make passwords on Devise impenetrable. But given how Devise is an out-of-the-box, batteries-included solution for auth, I think these changes set a minimum level of password complexity which (along with an increase in min password length) pushes users of Devise applications to use better passwords.

@fthobe
Copy link

fthobe commented Mar 9, 2025

@nashby and @carlosantoniodasilva
There has been no issue having had this much traction on devise in years (look at the reactions to the PR). It's a sought after feature, can we get this out of door somehow?

@salzig
Copy link

salzig commented Mar 9, 2025

If the real intend is to increase the default security without arguing about what way of enforcing patterns would be the best, than you should support #5685. That really helps the default a lot.

And we don't have to stop argue about how we can improve even further, but that one is really the base to having a more modern default.

@fthobe
Copy link

fthobe commented Mar 9, 2025

If the real intend is to increase the default security without arguing about what way of enforcing patterns would be the best, than you should support #5685. That really helps the default a lot.

Man, you wouldn't believe how much I agree with you, I know this seems like a crusade. But I feel like I can be for long passwords and at the same time believe that many people due to external requirements need password policies and that we should find a default implementation for this.

…lso provide a configurable list of special chars
password_complexity[:allowed_special_characters]
end

def password_contains_special_character
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@salzig @fthobe this allows both approaches: \W and check based on configured special chars 👍

Copy link

@fthobe fthobe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me @kykyi
But I'd really prefer also @salzig and @gregmolnar to look over this.

@kuahyeow
Copy link

kuahyeow commented Mar 10, 2025

The argument is that there should be a standardised approach to implementing password complexity requirements. But this will be default disabled in Devise. Why not in a separate gem ? The presence of five config options also implies there is no standardisation.

@fthobe
Copy link

fthobe commented Mar 10, 2025

hey @kuahyeow , thank you for participating in the discussion. As outlined above >10m Downloads of gems adding this functionality clearly indicate that the need of 30% of Devise installations is exactly this feature.

I fail to see yet one topic related argument why we can’t reduce implementation variability by adding exactly this. All I read is arguing how longer passwords are better completely missing the Problem: having Code fragmentation for such a basic Feature in one of the most sensitive parts of the rails ecosystem.

@salzig
Copy link

salzig commented Mar 10, 2025

As outlined above >10m Downloads of gems adding this functionality clearly indicate that the need of 30% of Devise installations is exactly this feature.

30% is an argument for a separate Gem, not for inclusion. IMHO: only something used by ~80-90% of the users should be considered for inclusion. It's just more source code the maintainers have to work with, and there is already a lot.

@kykyi
Copy link
Author

kykyi commented Mar 13, 2025

Throwing this into the ring, not by any means peer reviewed or backed by data, but since devise is kind of a roll-your-own Auth0 in some respects, I wanted to share that they define complexity like this:

  • None (default): at least 1 character of any type.
  • Low: at least 6 characters.
  • Fair: at least 8 characters including a lower-case letter, an upper-case letter, and a number.
  • Good: at least 8 characters including at least 3 of the following 4 types of characters: a lower-case letter, an upper-case letter, a number, a special character (such as !@#$%^&*).
  • Excellent: at least 10 characters including at least 3 of the following 4 types of characters: a lower-case letter, an upper-case letter, a number, a special character (such as !@#$%^&*). Not more than 2 identical characters in a row (for example, 111 is not allowed).

So these changes would give devise similar configurability which would be Good at minimum by their standard.

https://auth0.com/docs/authenticate/database-connections/password-strength

@crespire
Copy link

Putting a +1 to this, as it would have come in handy for our app, I will have to implement by hand for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.