Login Authentication Simulator
This project transforms control flow into a real system.
You are not writing:
if password == "123":
You are designing:
- A state machine
- A retry system
- A lock mechanism
- A deterministic branching architecture
This is how real authentication logic works.
Concept Overview
While watching, focus on:
- State transitions
- Retry logic
- Early termination
- Guard clauses
- Deterministic branching
Step 1 - Define System Requirements
Our system must:
- Ask for username and password
- Allow maximum 3 attempts
- Lock account after 3 failures
- Exit immediately on success
- Prevent infinite loops
- Clearly separate validation logic
Always define system behavior before writing code.
Step 2 - Model the State
Authentication system state includes:
- Stored username
- Stored password
- Attempt counter
- Lock status
We start simple.
Step 3 - Basic Version (State + Loop)
stored_username = "admin"
stored_password = "secure123"
attempts = 0
max_attempts = 3
while attempts < max_attempts:
username = input("Username: ")
password = input("Password: ")
if username == stored_username and password == stored_password:
print("Login successful.")
break
attempts += 1
print(f"Invalid credentials. Attempts left: {max_attempts - attempts}")
else:
print("Account locked.")
Execution Analysis
This demonstrates:
- While loop as state machine
- break for early success
- loop-else for lock behavior
- Controlled termination
Possible execution paths:
- Success on first attempt
- Success on second attempt
- Success on third attempt
- Three failures → lock
Deterministic.
loop-else executes only if no break occurred. Perfect for retry systems.
Step 4 - Add Guard Clauses
Refactor validation into function.
def validate_credentials(input_user, input_pass, stored_user, stored_pass):
if not input_user:
return False
if not input_pass:
return False
return input_user == stored_user and input_pass == stored_pass
This isolates logic.
Updated main loop:
while attempts < max_attempts:
username = input("Username: ")
password = input("Password: ")
if validate_credentials(username, password, stored_username, stored_password):
print("Login successful.")
break
attempts += 1
print(f"Invalid credentials. Attempts left: {max_attempts - attempts}")
Step 5 - Add Account Lock State
We now model lock explicitly.
is_locked = False
while attempts < max_attempts and not is_locked:
username = input("Username: ")
password = input("Password: ")
if validate_credentials(username, password, stored_username, stored_password):
print("Login successful.")
break
attempts += 1
if attempts >= max_attempts:
is_locked = True
print("Account locked.")
else:
print(f"Invalid credentials. Attempts left: {max_attempts - attempts}")
Now lock is explicit state.
Explicit state modeling improves clarity.
Step 6 - Prevent Timing Attacks (Conceptual)
Real systems do not reveal whether username or password failed.
Instead of:
if username != stored_username:
print("Invalid username")
elif password != stored_password:
print("Invalid password")
We use unified message:
print("Invalid credentials")
Security through uniform behavior.
Step 7 - Add Role-Based Logic
Now we extend system.
stored_users = {
"admin": {"password": "secure123", "role": "admin"},
"user": {"password": "userpass", "role": "user"}
}
Updated loop:
while attempts < max_attempts:
username = input("Username: ")
password = input("Password: ")
user_data = stored_users.get(username)
if user_data and user_data["password"] == password:
print("Login successful.")
if user_data["role"] == "admin":
print("Admin dashboard access granted.")
else:
print("User dashboard access granted.")
break
attempts += 1
print(f"Invalid credentials. Attempts left: {max_attempts - attempts}")
else:
print("Account locked.")
Now we have:
- Dictionary lookup
- Guard-style validation
- Pattern-driven branching
- Role-based decision tree
Step 8 - Add Lockout Persistence (Advanced)
Simulate lock storage:
locked_accounts = set()
if username in locked_accounts:
print("Account already locked.")
return
After max attempts:
locked_accounts.add(username)
Now lock persists across sessions.
Step 9 - Refactor Into Structured Architecture
Separate concerns:
def authenticate(username, password):
user_data = stored_users.get(username)
if not user_data:
return False
return user_data["password"] == password
def handle_success(user_data):
print("Login successful.")
if user_data["role"] == "admin":
print("Admin access granted.")
else:
print("User access granted.")
Main loop:
while attempts < max_attempts:
username = input("Username: ")
password = input("Password: ")
if authenticate(username, password):
handle_success(stored_users[username])
break
attempts += 1
Now logic is modular.
Step 10 - Analyze Execution Paths
Possible states:
- Valid login
- Invalid login
- Locked account
- Already locked
- Role-based routing
Each branch must be deterministic.
No ambiguous states allowed.
Common Mistakes in Login Systems
❌ Infinite retry loops
❌ Revealing which field failed
❌ Missing lock condition
❌ Deep nesting instead of guards
❌ Mixing authentication and routing logic
❌ No fallback behavior
Complexity Reflection
This simple project demonstrates:
- State-driven loops
- break semantics
- loop-else usage
- Guard clauses
- Pattern-driven role branching
- Decision tree design
- Deterministic control flow
This is not a toy script.
It is foundational backend logic.
Engineering Reflection Questions
Before finalizing, ask:
- Can this loop run forever?
- Are all paths covered?
- Is default behavior defined?
- Is state mutation clear?
- Are credentials handled safely?
- Is branching readable?
- Can new roles be added easily?
If not, refactor.
Final Insight
Authentication systems are decision engines.
They are not about checking a password.
They are about:
- Managing state
- Controlling execution
- Preventing abuse
- Handling edge cases
- Scaling behavior
This project trains you to:
Think in states. Design in branches. Terminate intentionally. Handle failure gracefully.
Control flow is system behavior.
And you just built one.
