30.4 Security Testing
Table of Contents
Why Security Testing Matters for Authentication
Authentication code is a primary target for attackers. If there is a bug here, someone may:
- Log in as another user.
- Bypass login completely.
- Steal or reuse tokens.
- Reset passwords without permission.
Security testing for your authentication system is about actively trying to break it in a controlled way, before attackers do.
You do not need to be a security expert to start. You only need a checklist, some basic tools, and a repeatable process.
Goal of security testing for authentication:
Try realistic attack scenarios against registration, login, tokens, password reset, and email verification, and verify that the system behaves securely in every case.
Basic Security Test Checklist
When you test your authentication system, you can use a simple checklist. For each item, you will try to write tests (automated or manual) that confirm the system is safe.
A compact checklist:
| Area | Key Questions |
|---|---|
| Registration | Can I create accounts with invalid or duplicate data? |
| Login | Can I bypass login or guess passwords too quickly? |
| Sessions / Tokens | Can I steal, reuse, or forge tokens? |
| Password Reset | Can I reset another user's password? |
| Email Verification | Can I skip verification or reuse verification links? |
| Rate limiting | Can I brute-force login or reset codes? |
| Input handling | Can I inject SQL or break JSON parsing? |
| Error messages | Do errors leak sensitive information? |
Use this checklist both when you test manually (with a browser or tools like Postman) and when you write automated tests.
Testing Registration
Registration looks simple, but it can leak information or allow weak inputs that later become vulnerabilities.
Test cases for registration
Create tests for these situations:
- Weak password rejection
- Try passwords like
"123456","password","qwerty". - Expected: The API returns a clear validation error.
- The error should not reveal password rules in extreme detail, but it must guide the user enough.
- Password policy
- Test boundaries: minimum length, maximum length.
- Example: If minimum is 8 characters:
- 7 characters -> fail.
- 8 characters -> pass.
- Test with long passwords, for example 100 characters, to ensure no crash or truncation.
- Email uniqueness
- Create account with email
"user@example.com". - Try to create another account with
"user@example.com". - Expected: 409 Conflict or 400 Bad Request. Response must not say whether the email exists in a way that leaks extra data. A simple message like
"Email already registered"is acceptable in many applications, but be consistent. - Username / email enumeration through error messages
- Try registering with:
- Existing email.
- New email.
- Check if error responses differ in a way that reveals if an email is already registered.
- Decide your policy:
- Either you allow “Email already used” at registration, or you do not reveal this.
- Important: Be consistent with login and password-reset flows.
- Input validation
Test invalid data:
- Invalid email format:
"not-an-email". - Very long email: 255+ characters.
- Unicode, spaces, or control characters in username/email.
- Empty fields.
Expected:
- 400 Bad Request with clear validation errors.
- No internal server error.
- Unexpected / extra fields
- Send extra fields like
"is_admin": truein the registration payload. - Expected: Server ignores or rejects them, but never sets elevated roles from user input.
Simple JSON examples to test:
// Valid registration
{
"email": "user@example.com",
"password": "Str0ng_Password_123",
"name": "Alice"
}
// Attempt to escalate privileges
{
"email": "evil@example.com",
"password": "Str0ng_Password_123",
"name": "Mallory",
"is_admin": true
}
Your test should confirm that "is_admin": true is ignored or rejected.
Testing Login and Brute-Force Protection
Login is where attackers will try password guessing, token theft, and login bypass.
Functional login tests
- Correct credentials
- Given a valid user, login returns:
- 200 OK.
- A valid session cookie or access token.
- Incorrect password
- Wrong password for existing email:
- Expected: 401 Unauthorized or 400 Bad Request.
- Message should be generic, for example
"Invalid credentials". - Nonexistent email
- Email not in database with any password:
- Expected: same response as incorrect password, so that attacker cannot distinguish.
- Disabled or unverified accounts
- For unverified accounts:
- Either disallow login until verified, or allow with limited access.
- For disabled / banned users:
- Login should fail gracefully, not reveal internal flags.
Brute-force and rate limiting tests
To prevent brute-force attacks, you should test both:
- Application-side validation.
- Rate limiting, if implemented.
Example test scenarios:
- Multiple wrong passwords
- Send 10 failed login attempts for the same email, same IP.
- Expected:
- Either increasing delay, or a lockout, or a 429 Too Many Requests response.
- No account lockout should be permanent without a safe unlock method.
- Account lockout behavior
- After N failed attempts (for example 5):
- Check that valid login is still possible or that user receives a clear message and a secure unlock pathway.
- Ensure lockout can not be abused as a denial of service.
For example, attacker should not be able to lock out any user forever. - Distributed attempts
- Many failed attempts from different IPs for the same account.
- If you have IP-based or user-based rate limits, test both.
In automated tests, you can simulate this with a loop:
for i in range(10):
response = client.post("/login", json={
"email": "user@example.com",
"password": f"wrong-{i}"
})Then check that after some attempts, the behavior changes appropriately.
Testing Session and Token Security
If you use sessions or JSON Web Tokens (JWT), you must ensure they cannot be easily stolen, forged, or misused.
Testing token structure and lifetime
For JWTs:
- Check token contents
- Decode the token without verifying the signature (most libraries can do this).
- Ensure it does not contain sensitive data such as:
- Password hashes.
- Security answers.
- Internal secrets.
- Expiration
- Inspect the
expclaim to ensure it is present and has a reasonable lifetime. - Issued-at and other claims
- Check
iat(issued at) andsub(subject, usually user ID) are correct.
Testing token forgery and reuse
Create tests for these scenarios:
- Tampered token
- Take a valid token and modify the payload, for example change
"role": "user"to"role": "admin". - Keep the same signature.
- Expected: Server must reject it, because the signature is invalid for modified content.
- Unsigned or wrongly signed tokens
- Create a token with no signature (algorithm
"none"). - Create a token signed with a wrong secret key.
- Expected: Server rejects these tokens.
- Expired tokens
- Create a token with an expiration time in the past.
- Expected: API returns 401 Unauthorized or 403 Forbidden and a clear message that token has expired.
- Token revocation behavior
- If your application supports revoking tokens (for example logout, forced logout, or rotation):
- Get a token.
- Revoke or rotate it.
- Try using the old token.
- Expected: It fails.
Example: Testing expired token in Python-like pseudocode
expired_token = create_jwt({"sub": user_id, "exp": past_time})
response = client.get("/protected-endpoint", headers={
"Authorization": f"Bearer {expired_token}"
})
assert response.status_code == 401Session cookie tests
If you use cookies instead of JWT:
- Verify
HttpOnly,Secure, andSameSiteattributes. - Check that the cookie is not accessible from JavaScript.
- Check that the cookie is only sent over HTTPS.
You can inspect cookies in browser developer tools or through automated tests.
Testing Password Reset Flows
Password reset is often the weakest part of authentication. Attackers try to reset passwords of other users.
Flow overview to test
Typical password reset flow:
- User requests reset with email.
- System sends a password reset link with a token.
- User clicks the link and sets a new password.
- Token becomes invalid after first use or after expiration.
You should test each step.
Requesting a password reset
Tests:
- Generic responses
- Request reset for:
- Existing email.
- Nonexistent email.
- Expected: Same generic response, for example
"If this email is registered, we have sent a reset email." - This prevents email enumeration.
- Rate limiting
- Request many resets for the same email or from the same IP.
- Expected:
- Throttling or 429 Too Many Requests.
- No spam of reset emails.
- Input validation
- Invalid email formats.
- Empty email.
- Extremely long email values.
Reset tokens
After the user receives a reset link such as:
https://example.com/reset-password?token=<token>
you should test:
- Single use
- Use the token once to change password. It should succeed.
- Use the same token again. Expected: It fails with an error like
"Invalid or expired token". - Expiration
- Create a token that is already expired, or wait past expiration.
- Expected: Token is rejected.
- Token binding to user
- Use a reset token intended for user A while logged in as user B, or send it for user B's email.
- Expected: It only works for the correct account.
- Guessability
- Ensure tokens are long and random. For example 32+ bytes of randomness, base64 or hex encoded.
- Test that short or simple tokens are not accepted if you manually try to craft them.
- Password re-use and history (if implemented)
- If your system prevents reusing the last password, test that you cannot reset to the same password as before.
Example bad and good token examples:
| Token | Problem |
|---|---|
12345 | Too short, guessable |
user@example.com | Derived directly from user identity |
random-32-byte-value | Good, high entropy |
Testing Email Verification Flows
Email verification is similar to password reset in structure but often less protected, which can be dangerous.
Common tests
- Verification without login
- Click verification link while not logged in.
- Expected: Email is verified or a clear message is shown, but you are not automatically logged in as that user unless that is an explicit feature.
- Reuse of verification link
- Use the same verification link multiple times.
- Expected: First time succeeds, later times:
- Either show “email already verified” without errors.
- Or show “invalid or expired link.”
In all cases, the link must not be able to change ownership of an email.
- Changing email before verification
- Register with one email, then change the email address in DB (if allowed) before verification.
- Use old verification link.
- Verify that this does not verify the wrong email or user.
- Expired verification link
- Try verifying after expiration.
- Expected: Link fails and user must request a new one.
- Tampering with verification codes
- If you use short codes (for example 6 digits) in email:
- Try invalid formats, like letters, longer numbers, leading zeros.
- Test brute-force rate limiting for verification codes, similar to login.
Testing Input Validation and Error Messages
Input validation is both a security and usability concern.
Input validation tests
Test every authentication endpoint:
/register/login/password-reset/request/password-reset/confirm/verify-email
For each one:
- Wrong data type
- Send number where string is expected, or null.
- Example:
{
"email": 12345,
"password": null
}- Expected: 400 Bad Request with a clear validation error.
- Missing required fields
- Omit
"password"or"email". - Unexpected fields
- Add
"role": "admin"during login, or"verified": trueduring registration. - Server must ignore or reject them, but never accept them.
- Boundary values
- Maximum lengths, minimum lengths, empty strings.
Error message tests
You do not want to leak secrets in error messages.
Check for each endpoint:
- No stack traces to the client
- Internal errors must be logged on server side, not returned as raw stack traces.
- No SQL or implementation details
- Error should not say
"SQL error near column password_hash". - No detailed existence hints
- Prefer a unified message for “user not found” and “wrong password”.
Example of good vs bad messages:
| Scenario | Bad message | Better message |
|---|---|---|
| Wrong password | "Password incorrect for email user@x.com" | "Invalid credentials" |
| Nonexistent email | "No account with this email" | "Invalid credentials" |
| Reset for unknown mail | "Email not found" | "If this email is registered..." |
Automated Security Tests vs Manual Testing
Both styles are important.
Automated tests
Automated tests live in your project and run with your test framework (for example pytest).
What to automate:
- All normal and invalid flows for registration and login.
- Token expiration and misuse tests.
- Single-use and expiry behavior of password reset and verification tokens.
- Basic rate limiting and brute-force behavior (with small limits just for test environment).
Benefits:
- They run on every change and in CI.
- They help prevent reintroducing old vulnerabilities.
Example of a simple automated security test (in pseudocode):
def test_login_does_not_reveal_user_existence(client, db_user):
# Nonexistent email
response1 = client.post("/login", json={
"email": "unknown@example.com",
"password": "any"
})
# Correct email, wrong password
response2 = client.post("/login", json={
"email": db_user.email,
"password": "wrong"
})
assert response1.status_code == 401
assert response2.status_code == 401
assert response1.json()["detail"] == response2.json()["detail"]This test checks that your login endpoint does not allow user enumeration.
Manual testing
Manual tests use tools like:
- Browser and developer tools.
- Postman or HTTPie or curl.
- Browser storage and cookie inspector.
- JWT decode tools.
Manual tests are useful for:
- Trying unexpected flows that are hard to automate.
- Inspecting cookies and tokens.
- Checking UI behavior (for example if front-end leaks tokens).
A healthy process:
- Design tests for new security sensitive features.
- Automate most of them.
- Regularly perform manual checks according to your checklist.
Using Tools for Security Testing
You can also use basic security tools to extend your testing.
HTTP and API tools
- Postman / Insomnia
For sending custom requests, manipulating headers, and trying strange payloads. - curl / HTTPie
Helpful for scripts and shell-based tests.
Static and dependency analysis
Although this is not specific to authentication, it is related:
- Use dependency scanners to find known vulnerabilities in libraries that implement hashing or JWT.
- Use static analysis tools (linters) that can catch obvious mistakes such as accidental logging of passwords.
Security scanners
There are specialized web security scanners. For beginners:
- Start with simple, free tools in a test environment only.
- Never scan systems you do not own or do not have permission to test.
Building a Security Testing Strategy for Your Project
For your Authentication System project, you can follow a simple strategy.
- List all authentication-related endpoints
Example:
POST /registerPOST /loginPOST /logoutPOST /password-reset/requestPOST /password-reset/confirmPOST /email/verifyGET /me
- For each endpoint, list threats
Example for /login:
- Brute force.
- User enumeration.
- Token theft.
- SQL injection.
- Convert threats into tests
Example:
- Brute force → Write a test that tries login repeatedly and checks for rate limiting.
- SQL injection → Send input such as
"test' OR '1'='1"and make sure the behavior is safe and consistent.
- Add tests to your test suite
- Use your chosen test framework.
- Run tests on every commit (CI).
- Review regularly
- When you add new authentication features, for example multi-factor authentication or social login, repeat the steps.
Key Principles to Remember
To summarize the most important ideas when testing authentication security:
Security Testing Principles for Authentication
- Test flows, not just single endpoints, especially for password reset and email verification.
- Ensure that error messages do not help attackers enumerate users or learn internal details.
- Confirm that tokens and links are:
- Long, random, and unguessable.
- Bound to a user and purpose.
- Single use or short lived when appropriate.
- Test rate limiting for login, reset, and verification codes to resist brute force.
- Automate as many security tests as possible and run them in CI.
- Never log passwords, reset tokens, or raw authentication secrets.
If you follow these principles in your Authentication System project, you will already be ahead of many real-world applications in terms of security testing quality.
Views: 14
KAHIBARO