Sessions and account lockout
A password check is a single moment. A session is a long-running proof that the check happened. We generate a random token on authenticate, store it with an expiry, and renew it every time the user acts. On every protected action we validate the token and grab the role back.
def authenticate(self, username: str, password: str) -> Optional[tuple[str, str]]:
if self._check_account_lockout(username):
return None
if username not in self.users:
return None
user = self.users[username]
computed = self._hash_password(password, user["salt"])
if user["password_hash"] != computed:
user["failed_attempts"] = user.get("failed_attempts", 0) + 1
user["last_attempt"] = datetime.now().isoformat()
self._save_users()
return None
user["failed_attempts"] = 0
self._save_users()
token = self._generate_session_token()
self.sessions[token] = {
"username": username,
"role": user["role"],
"expiry": time.time() + SESSION_EXPIRY_SECONDS,
}
self._save_sessions()
return token, user["role"]
Success resets the failed_attempts counter, mints a token, and writes the session row. Failure increments the counter so the next lookup can lock the account.
The auth lifecycle
Login mints a session. Every request validates and renews. Failure paths feed the lockout counter.
Fill in the blanks: Complete the session validator
Loading practice…
Quiz: Quiz
Loading practice…
Checkpoint: Auth design checkpoint
Loading practice…