In this tutorial, weβll build a secure desktop login system using:
Python
Tkinter for the GUI
Argon2 for modern password hashing
JSON for simple user storage
This guide is beginner-friendly and explains why each piece existsβnot just what it does.
π§± Step 1: Import Required Modules
We start by importing everything we need.
import tkinter as tk
from tkinter import messagebox
import json
import os
import re
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
What each module does:
tkinter β builds the GUI
messagebox β shows pop-up alerts
json β stores users in a file
os β checks if files exist
re β validates password strength
argon2 β securely hashes passwords (much safer than SHA256)
βοΈ Step 2: App Configuration
We define where users are stored and configure the password hasher.
USER_FILE = 'users.json'
This file will store usernames and hashed passwords.
π Configuring Argon2
ph = PasswordHasher(
time_cost=3,
memory_cost=65536, # 64 MB
parallelism=4,
hash_len=32,
salt_len=16
)
Why Argon2?
Designed to resist brute-force attacks
Uses memory + CPU (harder to crack)
Industry-recommended for password storage
πΎ Step 3: Loading and Saving Users
Load users from disk
def load_users():
if os.path.exists(USER_FILE):
with open(USER_FILE, 'r') as f:
return json.load(f)
return {}
If the file doesnβt exist yet, we return an empty dictionary.
Save users to disk
def save_users(users):
with open(USER_FILE, 'w') as f:
json.dump(users, f, indent=4)
We store data like this:
{
"alice": "$argon2id$v=19$..."
}
β οΈ Passwords are never stored in plain text.
π Step 4: Password Hashing & Verification
Hash a password
def hash_password(password: str) -> str:
return ph.hash(password)
This converts a plain password into a secure hash.
Verify a password
def verify_password(password: str, stored_hash: str) -> bool:
try:
return ph.verify(stored_hash, password)
except VerifyMismatchError:
return False
Instead of comparing strings, we let Argon2 safely verify the password.
π§ Step 5: Password Strength Validation
We enforce strong passwords.
def validate_password_strength(password: str) -> str | None:
if len(password) < 12:
return "Password must be at least 12 characters long"
Uppercase requirement
if not re.search(r"[A-Z]", password):
return "Password must contain an uppercase letter"
Lowercase requirement
if not re.search(r"[a-z]", password):
return "Password must contain a lowercase letter"
Digit requirement
if not re.search(r"\d", password):
return "Password must contain a digit"
Special character requirement
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
return "Password must contain a special character"
If everything passes
return None
Returning None means the password is strong.
π₯οΈ Step 6: Creating the Main App Class
class LoginApp:
def __init__(self, root):
self.root = root
self.root.title('Secure Login System')
self.root.geometry('450x550')
self.root.configure(bg='#1f2f3a')
self.root.resizable(False, False)
This sets up:
Window size
Title
Background color
Fixed dimensions
Load users and show login screen
self.users = load_users()
self.create_login_screen()
π§Ή Step 7: Clearing the Screen
We reuse the same window for multiple screens.
def clear_screen(self):
for widget in self.root.winfo_children():
widget.destroy()
This prevents stacking widgets on top of each other.
π Step 8: Login Screen UI
Title
tk.Label(
self.root,
text='Welcome Back!',
font=('Helvetica', 28, 'bold'),
bg='#1f2f3a',
fg='#ffffff'
).pack(pady=30)
Username input
tk.Label(frame, text='Username', fg='#bdc3c7', bg='#1f2f3a').pack(anchor='w')
self.username_entry = tk.Entry(frame, font=('Helvetica', 14), width=30)
self.username_entry.pack(ipady=6)
Password input
self.password_entry = tk.Entry(
frame,
font=('Helvetica', 14),
show='*',
width=30
)
The show='*' hides typed characters.
Buttons
tk.Button(frame, text='Login', command=self.login).pack()
tk.Button(frame, text='Register', command=self.create_register_screen).pack()
tk.Button(frame, text='Forgot Password?', command=self.forgot_password).pack()
π Step 9: Registration Screen
The registration screen reuses the same pattern:
Username
Password
Confirm password
Register button
Back button
This keeps the UI consistent and easy to maintain.
β
Step 10: Login Logic
def login(self):
username = self.username_entry.get()
password = self.password_entry.get()
Verify credentials
if username in self.users and verify_password(password, self.users[username]):
messagebox.showinfo('Success', f'Welcome {username}!')
else:
messagebox.showerror('Error', 'Invalid username or password')
No password leaks. No plain-text comparisons.
π§Ύ Step 11: Registration Logic
Basic validation
if not username or not password:
messagebox.showerror('Error', 'All fields are required')
Prevent duplicate users
if username in self.users:
messagebox.showerror('Error', 'Username already exists')
Confirm passwords match
if password != confirm:
messagebox.showerror('Error', 'Passwords do not match')
Enforce strong passwords
strength_error = validate_password_strength(password)
if strength_error:
messagebox.showerror('Weak Password', strength_error)
return
Save user securely
self.users[username] = hash_password(password)
save_users(self.users)
π Step 12: Forgot Password (Mock)
def forgot_password(self):
username = self.username_entry.get()
if username in self.users:
messagebox.showinfo(
'Password Reset',
f'Password reset link sent to {username} (mock)'
)
This simulates real-world behavior without email setup.
βΆοΈ Step 13: Run the App
if __name__ == '__main__':
root = tk.Tk()
app = LoginApp(root)
root.mainloop()
This launches the GUI event loop.
π― Final Thoughts
You now have:
A secure login system
Modern password hashing
Clean UI navigation
Strong validation rules
π Ideas to extend:
Email-based password reset
Encrypted user storage
Role-based access (admin/user)
Dark/light themes

Top comments (0)