Top authentication Questions

List of Tags
1760
Michiel de Mare

Form based authentication for websites

We believe that Stack Overflow should not just be a resource for very specific technical questions, but also for general guidelines on how to solve variations on common problems. "Form based authentication for websites" should be a fine topic for such an experiment.

It should include topics such as:

  • How to log in
  • How to remain logged in
  • How to store passwords
  • Using secret questions
  • Forgotten password functionality
  • OpenID
  • "Remember me" checkbox
  • Browser autocompletion of usernames and passwords
  • Secret URLs (public URLs protected by digest)
  • Checking password strength
  • E-mail validation
  • and much more ...

It should not include things like:

  • roles and authorization
  • HTTP basic authentication

Please help us by

  1. Suggesting subtopics
  2. Submitting good articles about this subject
  3. Editing the official answer
Answered By: Jens Roland ( 1215)

PART I: How To Log In

  1. As a rule, CAPTCHAs should be a last resort. They tend to be annoying, often aren't human-solvable, most of them are ineffective against bots, all of them are ineffective against cheap third-world labor (according to OWASP, the current sweatshop rate is $12 per 500 tests), and some implementations are technically illegal in some countries (see link number 1 from the MUST-READ list). If you must use a CAPTCHA, use reCAPTCHA, since it is OCR-hard by definition (since it uses already OCR-misclassified book scans).

  2. It is possible to prevent browsers from storing/retrieving a password with the autocomplete tag for forms/input fields. However in the real world, your customers will have many accounts on different systems; it compromises their security if they use the same password for every site. Can you expect them to remember different passwords for every site? There are some good password managers out there, however there are also bad ones - which will become a target for attackers.

  3. The only (currently practical) way to protect against login interception (packet sniffing) during login is by using a certificate-based encryption scheme (for example, SSL) or a proven & tested challenge-response scheme (for example, the Diffie-Hellman-based SRP). Any other method can be easily circumvented by an eavesdropping attacker. On that note: hashing the password client-side (for example, with JavaScript) is useless unless it is combined with one of the above - that is, either securing the line with strong encryption or using a tried-and-tested challenge-response mechanism (if you don't know what that is, just know that it is one of the most difficult to prove, most difficult to design, and most difficult to implement concepts in digital security). Hashing the password is effective against password disclosure, but not against replay attacks, Man-In-The-Middle attacks / hijackings, or brute-force attacks (since we are handing the attacker both username, salt and hashed password).

  4. After sending the authentication tokens, the system needs a way to remember that you have been authenticated - this fact should only ever be stored serverside in the session data. A cookie can be used to reference the session data. Wherever possible, the cookie should have the secure and HTTP Only flags set when sent to the browser. The httponly flag provides some protection against the cookie being read by a XSS attack. The secure flag ensures that the cookie is only sent back via HTTPS, and therefore protects against network sniffing attacks. The value of the cookie should not be predictable. Where a cookie referencing a non-existent session is presented, its value should be replaced immediately to prevent session fixation.

PART II: How To Remain Logged In - The Infamous "Remember Me" Checkbox

Persistent Login Cookies ("remember me" functionality) are a danger zone; on the one hand, they are entirely as safe as conventional logins when users understand how to handle them; and on the other hand, they are an enormous security risk in the hands of most users, who use them on public computers, forget to log out, don't know what cookies are or how to delete them, etc.

Personally, I want my persistent logins for the web sites I visit on a regular basis, but I know how to handle them safely. If you are positive that your users know the same, you can use persistent logins with a clean conscience. If not - well, then you're more like me; subscribing to the philosophy that users who are careless with their login credentials brought it upon themselves if they get hacked. It's not like we go to our user's houses and tear off all those facepalm-inducing Post-It notes with passwords they have lined up on the edge of their monitors, either. If people are idiots, then let them eat idiot cake.

Of course, some systems can't afford to have any accounts hacked; for such systems, there is no way you can justify having persistent logins.

If you DO decide to implement persistent login cookies, this is how you do it:

  1. First, follow Charles Miller's 'Best Practices' article Do not get tempted to follow the 'Improved' Best Practices linked at the end of his article. Sadly, the 'improvements' to the scheme are easily thwarted (all an attacker has to do when stealing the 'improved' cookie is remember to delete the old one. This will require the legitimate user to re-login, creating a new series identifier and leaving the stolen one valid).

  2. And DO NOT STORE THE PERSISTENT LOGIN COOKIE (TOKEN) IN YOUR DATABASE, ONLY A HASH OF IT! The login token is Password Equivalent, so if an attacker got his hands on your database, he/she could use the tokens to log in to any account, just as if they were cleartext login-password combinations. Therefore, use strong salted hashing (bcrypt / phpass) when storing persistent login tokens.

PART III: Using Secret Questions

Don't. Never ever use 'secret questions'. Read the paper from link number 5 from the MUST-READ list. You can ask Sarah Palin about that one, after her Yahoo! email account got hacked during the presidential campaign because the answer to her 'security' question was... (wait for it) ... "Wasilla High School"!

Even with user-specified questions, it is highly likely that most users will choose either:

  • A 'standard' secret question like mother's maiden name or favourite pet

  • A simple piece of trivia that anyone could lift from their blog, LinkedIn profile, or similar

  • Any question that is easier to answer than guessing their password. Which, for any decent password, is every question conceivable.

In conclusion, security questions are inherently insecure in all their forms and variations, and should never be employed in an authentication scheme for any reason.

A secondary question is often considered as adequate for fulfilling a requirement for two-factor authentication. While capturing some of the response via clicks rather than typing in theory provides protection against keylogger attacks, it is still just an extension to the password mechanism - and when users are presented with a text box instead of drop-downs on a phishing site, they rarely perceive this as abnormal. Note that you may be able to fulfill your two-factor obligations by using a long-lasting cookie (granted on submission of multiple authentication questions) in place of a security question asked each and every time - but at the expense of user convenience.

The only reason anyone still uses security questions by choice is that is saves the cost of a few support calls from users who can't remember their email passwords to get to their reactivation codes. At the expense of security and Sara Palin's reputation, that is. Worth it? You be the judge.

PART IV: Forgotten Password Functionality

I already mentioned why you should never use security questions for handling forgotten/lost user passwords. There are at least two more all-too-common pitfalls to avoid in this field:

  1. Don't RESET user's passwords no matter what - 'reset' passwords are harder for the user to remember, which means he/she MUST either change it OR write it down - say, on a bright yellow Post-It on the edge of his monitor. Instead, just let users pick a new one right away - which is what they want to do anyway.

  2. Always hash the lost password code/token in the database. AGAIN, this code is another example of a Password Equivalent, so it MUST be hashed in case an attacker got his hands on your database. When a lost password code is requested, send the plaintext code to the user's email address, then hash it, save the hash in your database -- and throw away the original. Just like a password or a persistent login token.

A final note: always make sure your interface for entering the 'lost password code' is at least as secure as your login form itself, or an attacker will simply use this to gain access instead. Making sure you generate very long 'lost password codes' (for example, 16 case sensitive alphanumeric characters) is a good start, but consider adding the same throttling that you do for logins.

PART V: Checking Password Strength

First, you'll want to read this small article for a reality check: The 500 most common passwords

Okay, so maybe the list isn't the canonical list of most common passwords on any system anywhere ever, but it's a good indication of how poorly people will choose their passwords when there is no enforced policy in place. Plus, the list looks frighteningly close to home when you compare it to the publicly available analyses of 40.000+ recently stolen MySpace passwords.

Well, enough MySpace-bashing for now. Moving on..

So: With no minimum password strength requirements, 2% of users use one of the top 20 most common passwords. Meaning: if an attacker gets just 20 attempts, 1 in 50 accounts on your website will be crackable.

Thwarting this requires calculating the entropy of a password and then applying a threshold. The National Institute of Standards and Technology (NIST) Special Publication 800-63 has a set of very good suggestions. That, when combined with a dictionary and keyboard layout analysis (for example, 'qwertyuiop' is a bad password), can reject 99% of all poorly selected passwords at a level of 18 bits of entropy. Simply calculating password strength and showing a visual strength meter to a user is insufficient. Unless it is enforced, users will ignore it.

PART VI: Much More - Or: Preventing Rapid-Fire Login Attempts

First, have a look at the numbers: Password Recovery Speeds - How long will your password stand up

If you don't have the time to look through the tables in that link, here's the gist of them:

  1. It takes virtually no time to crack a weak password, even if you're cracking it with an abacus

  2. It takes virtually no time to crack an alphanumeric 9-character password, if it is case insensitive

  3. It takes virtually no time to crack an intricate, symbols-and-letters-and-numbers, upper-and-lowercase password, if it is less than 8 characters long (a desktop PC can search the entire keyspace up to 7 characters in a matter of days or even hours)

  4. It would, however, take an inordinate amount of time to crack even a 6-character password, if you were limited to one attempt per second!

So what can we learn from these numbers? Well, lots, but we can focus on the most important part: the fact that preventing large numbers of rapid-fire successive login attempts (ie. the brute force attack) really isn't that difficult. But preventing it right isn't as easy as it seems.

Generally speaking, you have three choices that are all effective against brute-force attacks (and dictionary attacks, but since you are already employing a strong passwords policy, they shouldn't be an issue):

  • Present a CAPTCHA after N failed attempts (annoying as hell and often ineffective -- but I'm repeating myself here)

  • Locking accounts and requiring email verification after N failed attempts (this is a DoS attack waiting to happen)

  • And finally, login throttling: that is, setting a time delay between attempts after N failed attempts (yes, DoS attacks are still possible, but at least they are far less likely and a lot more complicated to pull off).

Best practice #1: A short time delay that increases with the number of failed attempts, like:

  • 1 failed attempt = no delay
  • 2 failed attempts = 2 sec delay
  • 3 failed attempts = 4 sec delay
  • 4 failed attempts = 8 sec delay
  • 5 failed attempts = 16 sec delay
  • etc.

DoS attacking this scheme would be very impractical, but on the other hand, potentially devastating, since the delay increases exponentially. A DoS attack lasting a few days could suspend the user for weeks.

To clarify: The delay is not a delay before returning the response to the browser. It is more like a timeout or refractory period during which login attempts to a specific account or from a specific IP address will not be accepted or evaluated at all. That is, correct credentials will not return in a successful login, and incorrect credentials will not trigger a delay increase.

Best practice #2: A medium length time delay that goes into effect after N failed attempts, like:

  • 1-4 failed attempts = no delay
  • 5 failed attempts = 15-30 min delay

DoS attacking this scheme would be quite impractical, but certainly doable. Also, it might be relevant to note that such a long delay can be very annoying for a legitimate user. Forgetful users will dislike you.

Best practice #3: Combining the two approaches - either a fixed, short time delay that goes into effect after N failed attempts, like:

  • 1-4 failed attempts = no delay
  • 5+ failed attempts = 20 sec delay

Or, an increasing delay with a fixed upper bound, like:

  • 1 failed attempt = 5 sec delay
  • 2 failed attempts = 15 sec delay
  • 3+ failed attempts = 45 sec delay

This final scheme was taken from the OWASP best-practices suggestions (link 1 from the MUST-READ list), and should be considered best practice, even if it is admittedly on the restrictive side.

As a rule of thumb however, I would say: the stronger your password policy is, the less you have to bug users with delays. If you require strong (case-sensitive alphanumerics + required numbers and symbols) 9+ character passwords, you could give the users 2-4 non-delayed password attempts before activating the throttling.

DoS attacking this final login throttling scheme would be very impractical. And as a final touch, always allow persistent (cookie) logins (and/or a CAPTCHA-verified login form) to pass through, so legitimate users won't even be delayed while the attack is in progress. That way, the very impractical DoS attack becomes an extremely impractical attack.

Additionally, it makes sense to do more aggressive throttling on admin accounts, since those are the most attractive entry points

PART VII: Distributed Brute Force Attacks

Just as an aside, more advanced attackers will try to circumvent login throttling by 'spreading their activities':

  • Distributing the attempts on a botnet to prevent IP address flagging

  • Rather than picking one user and trying the 50.000 most common passwords (which they can't, because of our throttling), they will pick THE most common password and try it against 50.000 users instead. That way, not only do they get around maximum-attempts measures like CAPTCHAs and login throttling, their chance of success increases as well, since the number 1 most common password is far more likely than number 49.995

  • Spacing the login requests for each user account, say, 30 seconds apart, to sneak under the radar

Here, the best practice would be logging the number of failed logins, system-wide, and using a running average of your site's bad-login frequency as the basis for an upper limit that you then impose on all users.

Too abstract? Let me rephrase:

Say your site has had an average of 120 bad logins per day over the past 3 months. Using that (running average), your system might set the global limit to 3 times that -- ie. 360 failed attempts over a 24 hour period. Then, if the total number of failed attempts across all accounts exceeds that number within one day (or even better, monitor the rate of acceleration and trigger on a calculated treshold), it activates system-wide login throttling - meaning short delays for ALL users (still, with the exception of cookie logins and/or backup CAPTCHA logins).

I also posted a question with more details and a really good discussion of how to avoid tricky pitfals in fending off distributed brute force attacks

PART VIII: Two-Factor Authentication and Authentication Providers

Credentials can be compromised, whether by exploits, passwords being written down and lost, laptops with keys being stolen, or users entering logins into phishing sites. Logins can be protected with two-factor authentication, which use out-of-band factors such as single-use codes received from a phone call, SMS message, or dongle. Several providers offer two-factor authentication services.

Authentication can be completely delegated to a single-sign-on service such as OAuth, OpenID or Persona (nee BrowserID), where another provider handles collecting credentials. This pushes the problem to a trusted third party. Twitter is an example of an OAuth provider, while Facebook provides a similar proprietary solution.

MUST-READ LINKS About Web Authentication

I see there are a few. Which ones are maintained and easy to use? What are their pros and cons?

Answered By: Jens Roland ( 309)

Update (May 14, 2010):

It turns out, the russian developer Ilya Konyukhov picked up the gauntlet after reading this and created a new auth library for CI based on DX Auth, following the recommendations and requirements below.

And the resulting Tank Auth is looking like the answer to the OP's question. I'm going to go out on a limb here and call Tank Auth the best authentication library for CodeIgniter available today. It's a rock-solid library that has all the features you need and none of the bloat you don't:

Tank Auth

Pros

  • Full featured
  • Lean footprint (20 files) considering the feature set
  • Very good documentation
  • Simple and elegant database design (just 4 DB tables)
  • Most features are optional and easily configured
  • Language file support
  • reCAPTCHA supported
  • Hooks into CI's validation system
  • Activation emails
  • Login with email, username or both (configurable)
  • Unactivated accounts auto-expire
  • Simple yet effective error handling
  • Uses phpass for hashing (and also hashes autologin codes in the DB)
  • Does not use security questions
  • Separation of user and profile data is very nice
  • Very reasonable security model around failed login attempts (good protection against bots and DoS attacks)

(Minor) Cons

  • Lost password codes are not hashed in DB
  • Includes a native (poor) CAPTCHA, which is nice for those who don't want to depend on the (Google-owned) reCAPTCHA service, but it really isn't secure enough
  • Very sparse online documentation (minor issue here, since the code is nicely documented and intuitive)

Download Tank Auth here


Original answer:

I've implemented my own as well (currently about 80% done after a few weeks of work). I tried all of the others first; FreakAuth Light, DX Auth, Redux, SimpleLogin, SimpleLoginSecure, pc_user, Fresh Powered, and a few more. None of them were up to par, IMO, either they were lacking basic features, inherently INsecure, or too bloated for my taste.

Actually, I did a detailed roundup of all the authentication libraries for CodeIgniter when I was testing them out (just after New Year's). FWIW, I'll share it with you:

DX Auth

Pros

  • Very full featured
  • Medium footprint (25+ files), but manages to feel quite slim
  • Excellent documentation, although some is in slightly broken English
  • Language file support
  • reCAPTCHA supported
  • Hooks into CI's validation system
  • Activation emails
  • Unactivated accounts auto-expire
  • Suggests grc.com for salts (not bad for a PRNG)
  • Banning with stored 'reason' strings
  • Simple yet effective error handling

Cons

  • Only lets users 'reset' a lost password (rather than letting them pick a new one upon reactivation)
  • Homebrew pseudo-event model - good intention, but misses the mark
  • Two password fields in the user table, bad style
  • Uses two separate user tables (one for 'temp' users - ambiguous and redundant)
  • Uses potentially unsafe md5 hashing
  • Failed login attempts only stored by IP, not by username - unsafe!
  • Autologin key not hashed in the database - practically as unsafe as storing passwords in cleartext!
  • Role system is a complete mess: is_admin function with hard-coded role names, is_role a complete mess, check_uri_permissions is a mess, the whole permissions table is a bad idea (a URI can change and render pages unprotected; permissions should always be stored exactly where the sensitive logic is). Dealbreaker!
  • Includes a native (poor) CAPTCHA
  • reCAPTCHA function interface is messy

FreakAuth Light

Pros

  • Very full featured
  • Mostly quite well documented code
  • Separation of user and profile data is a nice touch
  • Hooks into CI's validation system
  • Activation emails
  • Language file support
  • Actively developed

Cons

  • Feels a bit bloated (50+ files)
  • And yet it lacks automatic cookie login (!)
  • Doesn't support logins with both username and email
  • Seems to have issues with UTF-8 characters
  • Requires a lot of autoloading (impeding performance)
  • Badly micromanaged config file
  • Terrible View-Controller separation, with lots of program logic in views and output hard-coded into controllers. Dealbreaker!
  • Poor HTML code in the included views
  • Includes substandard CAPTCHA
  • Commented debug echoes everywhere
  • Forces a specific folder structure
  • Forces a specific Ajax library (can be switched, but shouldn't be there in the first place)
  • No max limit on login attempts - VERY unsafe! Dealbreaker!
  • Hijacks form validation
  • Uses potentially unsafe md5 hashing

pc_user

Pros

  • Good feature set for its tiny footprint
  • Lightweight, no bloat (3 files)
  • Elegant automatic cookie login
  • Comes with optional test implementation (nice touch)

Cons

  • Uses the old CI database syntax (less safe)
  • Doesn't hook into CI's validation system
  • Kinda unintuitive status (role) system (indexes upside down - impractical)
  • Uses potentially unsafe sha1 hashing

Fresh Powered

Pros

  • Small footprint (6 files)

Cons

  • Lacks a lot of essential features. Dealbreaker!
  • Everything is hard-coded. Dealbreaker!

Redux

According to the CodeIgniter wiki, Redux has been discontinued, but the Ion Auth fork is going strong: https://github.com/benedmunds/CodeIgniter-Ion-Auth

Someone who's used it should add a section here for Ion...

SimpleLoginSecure

Pros

  • Tiny footprint (4 files)
  • Minimalistic, absolutely no bloat
  • Uses phpass for hashing (excellent)

Cons

  • Only login, logout, create and delete
  • Lacks a lot of essential features. Dealbreaker!
  • More of a starting point than a library

Don't get me wrong: I don't mean to disrespect any of the above libraries; I am very impressed with what their developers have accomplished and how far each of them have come, and I'm not above reusing some of their code to build my own. What I'm saying is, sometimes in these projects, the focus shifts from the essential 'need-to-haves' (such as hard security practices) over to softer 'nice-to-haves', and that's what I hope to remedy.

Therefore: back to basics.

Authentication for CodeIgniter done right

Here's my MINIMAL required list of features from an authentication library. It also happens to be a subset of my own library's feature list ;)

  1. Tiny footprint with optional test implementation
  2. Full documentation
  3. No autoloading required. Just-in-time loading of libraries for performance
  4. Language file support; no hard-coded strings
  5. reCAPTCHA supported but optional
  6. Recommended TRUE random salt generation (e.g. using random.org or random.irb.hr)
  7. Optional add-ons to support 3rd party login (OpenID, Facebook Connect, Google Account, etc.)
  8. Login using either username or email
  9. Separation of user and profile data
  10. Emails for activation and lost passwords
  11. Automatic cookie login feature
  12. Configurable phpass for hashing (properly salted of course!)
  13. Hashing of passwords
  14. Hashing of autologin codes
  15. Hashing of lost password codes
  16. Hooks into CI's validation system
  17. NO security questions!
  18. Enforced strong password policy server-side, with optional client-side (Javascript) validator
  19. Enforced maximum number of failed login attempts with BEST PRACTICES countermeasures against both dictionary and DoS attacks!
  20. All database access done through prepared (bound) statements!

Note: those last few points are not super-high-security overkill that you don't need for your web application. If an authentication library doesn't meet these security standards 100%, DO NOT USE IT!

Recent high-profile examples of irresponsible coders who left them out of their software: #17 is how Sarah Palin's AOL email was hacked during the Presidential campaign; a nasty combination of #18 and #19 were the culprit recently when the Twitter accounts of Britney Spears, Barack Obama, Fox News and others were hacked; and #20 alone is how Chinese hackers managed to steal 9 million items of personal information from more than 70.000 Korean web sites in one automated hack in 2008.

These attacks are not brain surgery. If you leave your back doors wide open, you shouldn't delude yourself into a false sense of security by bolting the front. Moreover, if you're serious enough about coding to choose a best-practices framework like CodeIgniter, you owe it to yourself to at least get the most basic security measures done right.


<rant>

Basically, here's how it is: I don't care if an auth library offers a bunch of features, advanced role management, PHP4 compatibility, pretty CAPTCHA fonts, country tables, complete admin panels, bells and whistles -- if the library actually makes my site less secure by not following best practices. It's an authentication package; it needs to do ONE thing right: Authentication. If it fails to do that, it's actually doing more harm than good.

</rant>

/Jens Roland

How can I validate a username and password against Active Directory? I simply want to check if a username and password are correct.

Answered By: marc_s ( 220)

If you work on .NET 3.5 or newer, you can use the System.DirectoryServices.AccountManagement namespace and easily verify your credentials:

// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
    // validate the credentials
    bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

It's simple, it's reliable, it's 100% C# managed code on your end - what more can you ask for? :-)

Read all about it here:

Are there any existing user authentication libraries for node.js? In particular I'm looking for something that can do password authentication for a user (using a custom backend auth DB), and associate that user with a session.

Before I wrote an auth library, I figured I would see if folks knew of existing libraries. Couldn't find anything obvious via a google search.

-Shreyas

Answered By: Jared Hanson ( 120)

If you are looking for an authentication framework for Connect or Express, Passport is worth investigating: https://github.com/jaredhanson/passport

(Disclosure: I'm the developer of Passport)

I developed Passport after investigating both connect-auth and everyauth. While they are both great modules, they didn't suit my needs. I wanted something that was more light-weight and unobtrusive.

Passport is broken down into separate modules, so you can choose to use only what you need (OAuth, only if necessary). Passport also does not mount any routes in your application, giving you the flexibility to decide when and where you want authentication, and hooks to control what happens when authentication succeeds or fails.

For example, here is the two-step process to setup form-based (username and password) authentication:

passport.use(new LocalStrategy(
  function(username, password, done) {
    // Find the user from your DB (MongoDB, CouchDB, other...)
    User.findOne({ username: username, password: password }, function (err, user) {
      done(err, user);
    });
  }
));

app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    // Authentication successful. Redirect home.
    res.redirect('/');
  });

Additional strategies are available for authentication via Facebook, Twitter, etc. Custom strategies can be plugged-in, if necessary.