Practical Python Security
Building a Secure Login System Using Hashing and PostgreSQL
Authentication is one of the most critical parts of any backend system.
A poorly designed login system can expose:
- User passwords
- Financial data
- Entire databases
In this article, we build a secure login system using Python, PostgreSQL, hashing, salting, and secure verification.
Why Plain Password Storage is Dangerous
password = "mypassword"
Storing raw passwords means:
- Anyone with database access can read them
- Data breaches expose users instantly
- No protection against attackers
Instead, we store hashed passwords.
Step 1 — Database Design (PostgreSQL)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE,
password_hash TEXT NOT NULL,
salt TEXT NOT NULL
);
Each user has a unique salt to ensure even identical passwords produce different hashes.
Step 2 — Hashing with Salt (Python)
import hashlib
import os
def hash_password(password):
salt = os.urandom(16)
hash_value = hashlib.sha256(salt + password.encode()).hexdigest()
return hash_value, salt.hex()
Same password will generate different hashes due to unique salt.
Step 3 — User Registration
import psycopg2
def register_user(username, password):
conn = psycopg2.connect(
dbname="auth",
user="postgres",
password="password",
host="localhost"
)
cur = conn.cursor()
hash_value, salt = hash_password(password)
cur.execute(
"INSERT INTO users (username, password_hash, salt) VALUES (%s, %s, %s)",
(username, hash_value, salt)
)
conn.commit()
conn.close()
Passwords are never stored directly — only hash and salt.
Step 4 — Login Verification
def verify_user(username, password):
conn = psycopg2.connect(
dbname="auth",
user="postgres",
password="password",
host="localhost"
)
cur = conn.cursor()
cur.execute(
"SELECT password_hash, salt FROM users WHERE username = %s",
(username,)
)
result = cur.fetchone()
if not result:
return False
stored_hash, salt = result
salt_bytes = bytes.fromhex(salt)
new_hash = hashlib.sha256(salt_bytes + password.encode()).hexdigest()
conn.close()
return new_hash == stored_hash
The system compares hashes — not actual passwords.
Step 5 — Token Generation
import secrets
def generate_token():
return secrets.token_hex(32)
Tokens are used for secure sessions and authentication.
Real Backend Flow
Registration
- User submits credentials
- System generates salt
- Password is hashed
- Stored in database
Login
- User submits credentials
- System fetches salt
- Password re-hashed
- Hashes compared
- Token generated
Why This System is Secure
- ✔ Database leaks do not expose passwords
- ✔ Salt prevents rainbow table attacks
- ✔ Hashing protects sensitive data
- ✔ Secure authentication flow
Real-World Improvements
- Use bcrypt or argon2
- Add rate limiting
- Enable HTTPS
- Multi-factor authentication
Key Concepts Learned
- Hashing vs Encryption
- Salting
- Secure storage
- Authentication flow
- Token-based sessions
Final Thought
A login system is not just a feature — it is a security boundary. Understanding hashing is essential for building secure backend systems.