How to Harden Login APIs: From Brute‑Force to MITM Protection

This article examines common login security risks such as brute‑force attacks, captcha bypass, IP blocking, and man‑in‑the‑middle threats, and proposes layered defenses including password‑retry limits, captcha, phone verification, HTTPS, and encrypted payloads to significantly raise the attack cost.

Architecture Digest
Architecture Digest
Architecture Digest
How to Harden Login APIs: From Brute‑Force to MITM Protection

01 Preface

When learning web backend development, many newcomers focus only on implementing login functionality without considering security aspects. This article discusses what additional security measures should be taken when designing a login interface.

02 Security Risks

Brute‑Force Attacks

Publicly exposed sites are vulnerable to password‑guessing attacks. An attacker can obtain usernames and iterate through possible passwords until a correct one is found.

# password dictionary
password_dict = []
# login endpoint
login_url = ''

def attack(username):
    for password in password_dict:
        data = {'username': username, 'password': password}
        content = requests.post(login_url, data).content.decode('utf-8')
        if 'login success' in content:
            print('got it! password is : %s' % password)

To mitigate this, we can introduce captcha verification after a certain number of failed attempts.

Captcha

fail_count = get_from_redis(fail_username)
if fail_count >= 3:
    if captcha is None:
        return error('需要验证码')
    check_captcha(captcha)
    success = do_login(username, password)
    if not success:
        set_redis(fail_username, fail_count + 1)

Note that simple image captchas can be broken by OCR; third‑party sliding captchas are an alternative but not foolproof.

Login Rate Limiting

fail_count = get_from_redis(fail_username)
locked = get_from_redis(lock_username)
if locked:
    return error('拒绝登录')
if fail_count >= 3:
    if captcha is None:
        return error('需要验证码')
    check_captcha(captcha)
    success = do_login(username, password)
    if not success:
        set_redis(fail_username, fail_count + 1)
if fail_count + 1 >= 10:
    # lock account for 5 minutes
    set_redis(lock_username, true, 300)

However, locking accounts can be abused by attackers who iterate over many usernames, causing legitimate users to be locked out.

IP Limiting

ip = request['IP']
fail_count = get_from_redis(fail_ip)
if fail_count > 10:
    return error('拒绝登录')
# other login logic
success = do_login(username, password)
if not success:
    set_redis(fail_ip, true, 300)

IP‑based limits may affect multiple users behind the same NAT and can be evaded with VPNs.

Phone Verification

Binding a phone number to an account enables a second factor. After three failed attempts, require a captcha; after ten failed attempts, require a phone verification code in addition to the password.

03 Other Measures

Record operation logs for each login and sensitive action (IP, device, etc.).

Send alert messages (SMS/email) on abnormal logins.

Reject weak passwords during registration or password change.

Avoid exposing username existence checks to prevent enumeration.

04 Postscript

With increasing data protection regulations, developers must continuously improve user data security. Future articles will share additional practices.

CaptchaMITMHTTPSIP blockinglogin securityBrute-force protection
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.