🎯 Learning Objectives
- Understand authentication vs authorization
- Learn secure password storage techniques
- Implement session management
- Work with JWT (JSON Web Tokens)
- Apply Role-Based Access Control (RBAC)
These two concepts are fundamental to security but serve different purposes:
"Who are you?"
"What can you do?"
Proper password storage is critical. Never store passwords in plain text!
# VULNERABLE - Storing plain text passwords
# This is extremely dangerous!
users = {
"john": "password123", # NEVER!
"jane": "qwerty", # NEVER!
}
# If database is compromised, ALL passwords are exposed!
# Attackers can use these passwords on other sites too!
import bcrypt
def hash_password(password: str) -> str:
"""Hash a password using bcrypt."""
# Generate salt (work factor 12 is recommended)
salt = bcrypt.gensalt(rounds=12)
# Hash the password
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
return hashed.decode('utf-8')
def verify_password(password: str, hashed: str) -> bool:
"""Verify a password against a hash."""
return bcrypt.checkpw(
password.encode('utf-8'),
hashed.encode('utf-8')
)
# Example usage
password = "MySecureP@ssw0rd!"
hashed_password = hash_password(password)
print(f"Hashed: {hashed_password}")
# Output: $2b$12$LQv3c1yq.... (60 char string)
# Verify
is_valid = verify_password(password, hashed_password)
print(f"Valid: {is_valid}") # True
# Argon2 - Winner of Password Hashing Competition
# Recommended for new projects
import argon2
def hash_password_argon2(password: str) -> str:
"""Hash password using Argon2."""
ph = argon2.PasswordHasher(
time_cost=2, # Number of iterations
memory_cost=65536, # Memory usage in KB
parallelism=1, # Number of threads
hash_len=32, # Length of hash
salt_len=16 # Length of salt
)
return ph.hash(password)
def verify_password_argon2(password: str, hash: str) -> bool:
"""Verify password against Argon2 hash."""
ph = argon2.PasswordHasher()
try:
ph.verify(hash, password)
return True
except:
return False
# Example
hashed = hash_password_argon2("secure_password")
print(hashed)
# $argon2id$v=19$m=65536,t=2,p=1$...
HTTP is stateless - sessions maintain user state across requests.
import secrets
import hashlib
from datetime import datetime, timedelta
class SecureSession:
def __init__(self):
self.sessions = {}
self.secret_key = secrets.token_hex(32)
def create_session(self, user_id: int, remember_me: bool = False) -> str:
"""Create a new secure session."""
session_id = secrets.token_urlsafe(32)
# Store session data server-side
self.sessions[session_id] = {
'user_id': user_id,
'created_at': datetime.now(),
'expires_at': datetime.now() + (timedelta(days=30) if remember_me else timedelta(hours=1)),
'ip_address': None,
'user_agent': None
}
return session_id
def validate_session(self, session_id: str) -> dict:
"""Validate and return session data."""
session = self.sessions.get(session_id)
if not session:
return None
# Check expiration
if datetime.now() > session['expires_at']:
del self.sessions[session_id]
return None
return session
def destroy_session(self, session_id: str):
"""Logout - destroy session."""
if session_id in self.sessions:
del self.sessions[session_id]
def regenerate_session(self, old_session_id: str) -> str:
"""Regenerate session ID to prevent session fixation."""
old_session = self.validate_session(old_session_id)
if not old_session:
return None
# Create new session
new_session_id = self.create_session(
old_session['user_id'],
remember_me=(datetime.now() + timedelta(days=30) > old_session['expires_at'])
)
# Destroy old session
self.destroy_session(old_session_id)
return new_session_id
# Example: Setting secure session cookie
from flask import Flask, make_response
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
@app.route('/login')
def login():
response = make_response(redirect('/dashboard'))
# Set session cookie with security flags
response.set_cookie(
'session_id',
session_id,
httponly=True, # JavaScript cannot access
secure=True, # HTTPS only
samesite='Strict', # CSRF protection
max_age=3600, # 1 hour
path='/' # Available on all paths
)
return response
JWT is a compact, URL-safe token format for securely transmitting claims between parties.
# JWT has three parts: Header.Payload.Signature
# Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
# 1. Header (Base64URL encoded)
{
"alg": "HS256", # Algorithm used for signature
"typ": "JWT"
}
# 2. Payload (Base64URL encoded)
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622 # Expiration time
}
# 3. Signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret_key
)
import jwt
from datetime import datetime, timedelta
import secrets
SECRET_KEY = secrets.token_hex(32) # Keep secret!
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(user_id: int, role: str) -> str:
"""Create a JWT access token."""
now = datetime.utcnow()
expire = now + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
payload = {
'sub': str(user_id), # Subject (user ID)
'role': role, # User role
'iat': now, # Issued at
'exp': expire # Expiration
}
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return token
def verify_token(token: str) -> dict:
"""Verify and decode JWT."""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise Exception("Token has expired")
except jwt.InvalidTokenError:
raise Exception("Invalid token")
# Example usage
token = create_access_token(user_id=123, role='admin')
print(f"Token: {token}")
# Verify
payload = verify_token(token)
print(f"User ID: {payload['sub']}")
print(f"Role: {payload['role']}")
RBAC assigns permissions to roles rather than individual users.
from enum import Enum
from functools import wraps
class Role(Enum):
ADMIN = "admin"
MODERATOR = "moderator"
USER = "user"
GUEST = "guest"
# Define permissions
PERMISSIONS = {
Role.ADMIN: ['read', 'write', 'delete', 'manage_users', 'view_logs'],
Role.MODERATOR: ['read', 'write', 'delete'],
Role.USER: ['read', 'write'],
Role.GUEST: ['read']
}
class User:
def __init__(self, user_id: int, username: str, role: Role):
self.user_id = user_id
self.username = username
self.role = role
def has_permission(self, permission: str) -> bool:
"""Check if user has specific permission."""
return permission in PERMISSIONS.get(self.role, [])
def can_access(self, required_role: Role) -> bool:
"""Check if user's role meets minimum requirement."""
role_hierarchy = {
Role.GUEST: 0,
Role.USER: 1,
Role.MODERATOR: 2,
Role.ADMIN: 3
}
return role_hierarchy[self.role] >= role_hierarchy[required_role]
def require_permission(permission: str):
"""Decorator to require specific permission."""
def decorator(func):
@wraps(func)
def wrapper(user: User, *args, **kwargs):
if not user.has_permission(permission):
raise PermissionError(f"Permission denied: {permission}")
return func(user, *args, **kwargs)
return wrapper
return decorator
# Example usage
admin = User(1, "admin_user", Role.ADMIN)
user = User(2, "regular_user", Role.USER)
print(f"Admin can delete: {admin.has_permission('delete')}") # True
print(f"User can delete: {user.has_permission('delete')}") # False
# Flask-RESTful with RBAC
from flask import Flask, jsonify, request
from functools import wraps
app = Flask(__name__)
def require_role(*allowed_roles):
"""Decorator to check user role."""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Get token from header
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({'error': 'No token provided'}), 401
# Decode token (simplified)
token = auth_header.replace('Bearer ', '')
user = verify_token(token) # Your verification function
if user['role'] not in allowed_roles:
return jsonify({'error': 'Insufficient permissions'}), 403
return f(user, *args, **kwargs)
return decorated_function
return decorator
# Usage
@app.route('/admin/users', methods=['GET'])
@require_role('admin')
def get_all_users(current_user):
# Only admins can access this
return jsonify({'users': [...]})
@app.route('/posts', methods=['POST'])
@require_role('user', 'moderator', 'admin')
def create_post(current_user):
# User, moderator, or admin can create
return jsonify({'message': 'Post created'})
MFA requires multiple forms of verification, significantly improving security.
Password, PIN, security questions
Phone, security key, smart card
Fingerprint, face recognition, iris scan
import pyotp
import qrcode
from io import BytesIO
import base64
def generate_secret():
"""Generate a new TOTP secret."""
return pyotp.random_base32()
def get_provisioning_uri(secret: str, email: str, issuer: str = "MyApp") -> str:
"""Get URI for QR code generation."""
totp = pyotp.TOTP(secret)
return totp.provisioning_uri(name=email, issuer_name=issuer)
def verify_code(secret: str, code: str) -> bool:
"""Verify TOTP code."""
totp = pyotp.TOTP(secret)
return totp.verify(code)
# Example usage
secret = generate_secret()
print(f"Secret: {secret}")
# Get QR code for Google Authenticator
uri = get_provisioning_uri(secret, "user@example.com", "CyberGuard")
print(f"URI: {uri}")
# Verify a code
code = input("Enter code from authenticator: ")
is_valid = verify_code(secret, code)
print(f"Valid: {is_valid}")
Build a complete authentication system:
"""
Secure Authentication System Requirements:
1. User registration with password hashing (bcrypt)
2. Login with password verification
3. JWT token generation
4. Protected routes
5. Role-based access control
"""
# Key Components:
# - User model with hashed passwords
# - Registration endpoint
# - Login endpoint with JWT
# - Decorators for protected routes
# - Role-based middleware
Build a complete authentication system with: