This short note might not align with someone's opinion or the reality at the time of reading
I am simply trying to find the right design that doesn't annoy users while ensuring security for online applications

Often, I see online resources accepting a login-password pair at once without requiring any second factor

For a book store, this might be acceptable
But for a more serious application, it's not great because this approach is vulnerable to brute-force attacks, even though it's convenient for users

Below, I will discuss security measures and their user-friendliness

CAPTCHA!

CAPTCHA is an effective way to stop most unprepared attackers from brute-forcing passwords.
But let's be honest — it directly impacts users, and some CAPTCHAs are outright annoying

  • Classic CAPTCHA (entering symbols from an image): the least convenient
  • “Selecting traffic lights”: slightly better but still frustrating
  • Google Invisible reCAPTCHA: convenient for users as it requires no action, but it's absolute — users have almost no chance to prove they're not robots
  • Cloudflare Turnstile: similar to invisible reCAPTCHA but may show a checkbox to click if necessary

The last approach is less irritating, but even here there's room for optimization:
For example, if your login form is used on average once per minute during regular working hours, you can enable CAPTCHA only when usage exceeds twice or ten times the average rate

This way, CAPTCHA activates automatically during brute-force attacks and stops bots, while regular users are affected only during attacks

Remember site-specific busy days (e.g., Black Friday, Christmas), so apply this approach thoughtfully

Image description

Split Data Entry!

Most popular platforms today use step-by-step data entry.
For example:

Step 1: The website sends the user's login to /api/auth/login

  • The site creates a temporary token
  • Saves it for a few seconds
  • Sends it back to the client
  • This step works regardless of whether the user exists in the system

Step 2: The app sends this token and the user's password to /api/auth/pwd

  • The endpoint retrieves the login from the cache using the token
  • Processes it as a login-password pair

The temporary token can be anything: a cookie, JWT, encrypted value, or signed string — not necessarily cached

The key point: splitting login and password entry into two calls with an intermediate token stops most brute-force tools, example:

Image description

CSRF Tokens

The previous point can be partly solved using CSRF tokens
It's enough to study your framework and learn how to turn on this feature. It can also help against brute-force attacks. But I want to note that many tools for attacking web applications can work with many standard CSRF token systems
In any case, they will be useful for the whole application, not just for login
Plus, they will reduce the number of simple attacks

Add Delays

If an attacker bypasses CAPTCHA and can easily implement sequential API calls for login and password, a good method to create difficulties for the attacker is adding a random delay to one of the steps

Firstly, this slows down the attack process
Secondly, it protects against user enumeration attacks by reducing the likelihood of the attacker identifying which usernames exist in the system based on API response delays

Key considerations:

  • Delays should not allow the attacker to cause system denial
  • They should not frustrate users, so the faster the API works, the better

This protection can be activated only when normal metrics are exceeded, i.e. during an actual attack. It is important to implement this automatically rather than manually

Balance is crucial:

  • If the user exists, the service may perform additional operations such as reading data from the database, adding 200-300 milliseconds of delay
  • If the user does not exist and the API responds as quickly as possible, a random delay of 150-250 milliseconds should be added to prevent detection of whether a user exists
  • Ensure you test your application’s behavior if 10,000 such requests arrive simultaneously

Rate Limits

It is crucial to limit the number of authentication method calls per IP

For example, the following metrics can be considered normal: one user logs into their profile an average of 5 times a day. Even if it's a group of 100 users from the same office (same IP), 500 logins over 12 hours from one IP address are considered normal, even if they all decide to log in at the same time

However, in this scenario, calling the authentication method 1000 times per minute would appear as a significant anomaly

Thus, you can respond only when a specific limit is exceeded for a particular IP address

Keep in mind that today attackers may use multiple IP addresses

Therefore, it is worth monitoring overall login metrics as well, similar to how CAPTCHA was discussed earlier

Possible actions when suspecting an attack:

  • Require CAPTCHA for the suspicious IP
  • Introduce delays
  • Block attackers (HTTP 429)
  • Block and respond with a message claiming your Ruby server cannot process the request, even if your server is actually written in Go 😉

Weak Passwords

If lack of second factor is acceptable for a website, but password is required, I strongly recommend taking following measures:

  • Reject most common passwords, examples can be found here
  • Or use k-Anonymity algorithm to check if user's password was leaked and warn users about it (note the algorithm - user's password is NOT sent to an external service)
  • Don't require special characters, numbers and uppercase letter, because users tend to:
  • Better: place motivating password strength meter on site to clearly show if password is good
  • Can also add link to choose password manager there. Even if it works in 1 out of 1000 cases, it's worth it (if thinking about common good)

Passwordless Login

Some users will still be at risk when using passwords because their favorite password "has been with them for 20 years and no one has hacked it yet"

While this may be true, is it worth taking such risks? If not, a reasonable alternative is to stop using passwords in favor of magic links

How it works:

  • User enters their login
  • Site sends a login link to user's email (see example)
  • Requirements for this link:
    • Works for limited time, e.g. 15 minutes
    • One-time use only
    • Only works from same IP, device or at least city where the login was started
    • Access token in link should be long and preferably hidden from user to make it hard for attacker to get user to share link value. Long random tokens also prevent brute force attempts
  • This is convenient for users and site physically cannot allow password leaks

Note - I mean an actual link, not a 5-digit code sent by email that user could copy/paste into phishing site

This approach doesn't distinguish between registration and login, which simplifies new user onboarding

Some users may prefer passkeys to fully replace passwords, but users need time to get used to this technology before it's mandated for everyone. Sites should be prepared for users losing their passkey. This is extra work and should be offered as an option for now

Mobile App Login

Many mobile apps replicate login functionality from the main website, fully duplicating its features. This creates additional work when the login process is updated, such as implementing new security measures like passkeys, email codes, Google Authenticator, or SMS verification

As a result, developers must not only update the web application but also repeat the changes for iOS and Android apps

Beyond the time spent on development and testing, this duplication may lead to extra costs for maintenance, bug fixing, or even vulnerabilities due to the separate yet duplicated logic

A better solution is to implement mobile authentication using OAuth 2.0. Simply put, the mobile app opens a familiar website for the user, which provides session data

This approach improves Time to Market, reduces potential security issues, and can enhance the user experience. For example, users who save passwords in their browser or password manager won’t need to enter any login data

Credential Service Provider (CSP)

There are 3 options today:

  • Use passkeys. Big companies already use this because it protects against phishing. However, some users can't use it due to old devices or because they don't want to. This is similar to CSP: a third-party service gives you a token for validation
  • Use login through other platforms (Google/Microsoft/Facebook). This solves login/password headaches for the website. The main thing is to implement the authentication protocol correctly and support as many platforms as possible, but integrating with each platform takes time
  • Use Identity and Access Management services. These services integrate with other platforms, so you only need to integrate with one service. This way:
    • You cover most users
    • You can manage security requirements in one place
    • You don't have to make many implementations and risk errors
    • You don't have to constantly fix it due to new threats

Conclusion

The most secure authentication methods are those that don't require users to use passwords

When using passwords, user convenience is often sacrificed

Applications must thoughtfully put a lot of effort into balancing convenience and protection for their users

I hope this note was helpful to someone