DEV Community

Mate Technologies
Mate Technologies

Posted on

πŸ” Building a Secure Login System with Python, Tkinter & Argon2 (Step-by-Step)

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

We store data like this:

{
  "alice": "$argon2id$v=19$..."
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Uppercase requirement

    if not re.search(r"[A-Z]", password):
        return "Password must contain an uppercase letter"
Enter fullscreen mode Exit fullscreen mode

Lowercase requirement

    if not re.search(r"[a-z]", password):
        return "Password must contain a lowercase letter"
Enter fullscreen mode Exit fullscreen mode

Digit requirement

    if not re.search(r"\d", password):
        return "Password must contain a digit"
Enter fullscreen mode Exit fullscreen mode

Special character requirement

    if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
        return "Password must contain a special character"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

This sets up:

Window size

Title

Background color

Fixed dimensions

Load users and show login screen

        self.users = load_users()
        self.create_login_screen()
Enter fullscreen mode Exit fullscreen mode

🧹 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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Password input

self.password_entry = tk.Entry(
    frame,
    font=('Helvetica', 14),
    show='*',
    width=30
)
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

πŸ“ 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()
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

Prevent duplicate users

if username in self.users:
    messagebox.showerror('Error', 'Username already exists')
Enter fullscreen mode Exit fullscreen mode

Confirm passwords match

if password != confirm:
    messagebox.showerror('Error', 'Passwords do not match')
Enter fullscreen mode Exit fullscreen mode

Enforce strong passwords

strength_error = validate_password_strength(password)
if strength_error:
    messagebox.showerror('Weak Password', strength_error)
    return
Enter fullscreen mode Exit fullscreen mode

Save user securely

self.users[username] = hash_password(password)
save_users(self.users)
Enter fullscreen mode Exit fullscreen mode

πŸ” 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)'
    )

Enter fullscreen mode Exit fullscreen mode

This simulates real-world behavior without email setup.

▢️ Step 13: Run the App

if __name__ == '__main__':
    root = tk.Tk()
    app = LoginApp(root)
    root.mainloop()
Enter fullscreen mode Exit fullscreen mode

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

Secure Login System

Top comments (0)