Authentication patterns including JWT, OAuth2, sessions, and multi-factor authentication for secure user identity verification
Scope: Authentication patterns, identity verification, credential management, MFA Lines: ~380 Last Updated: 2025-10-27
Activate this skill when:
Authentication: "Who are you?" - Verifying identity Authorization: "What can you do?" - Verifying permissions
# Authentication: Verify user identity
def authenticate(username: str, password: str) -> User:
user = db.get_user_by_username(username)
if user and verify_password(password, user.password_hash):
return user
raise AuthenticationError("Invalid credentials")
# Authorization: Check permissions (see security-authorization.md)
def authorize(user: User, resource: str, action: str) -> bool:
return user.has_permission(resource, action)
Bad - Never use:
# ❌ Plain text storage
user.password = request.password
# ❌ Weak hashing (MD5, SHA1)
user.password = hashlib.md5(request.password.encode()).hexdigest()
# ❌ No salt
user.password = hashlib.sha256(request.password.encode()).hexdigest()
Good - Use strong hashing:
import bcrypt
from argon2 import PasswordHasher
# ✅ bcrypt (widely used, battle-tested)
def hash_password_bcrypt(password: str) -> bytes:
salt = bcrypt.gensalt(rounds=12) # Cost factor
return bcrypt.hashpw(password.encode(), salt)
def verify_password_bcrypt(password: str, hash: bytes) -> bool:
return bcrypt.checkpw(password.encode(), hash)
# ✅ Argon2 (modern, recommended)
ph = PasswordHasher()
def hash_password_argon2(password: str) -> str:
return ph.hash(password)
def verify_password_argon2(password: str, hash: str) -> bool:
try:
ph.verify(hash, password)
return True
except:
return False
import re
from typing import List
def validate_password_strength(password: str) -> tuple[bool, List[str]]:
"""
Validate password meets security requirements.
Returns (is_valid, list_of_errors)
"""
errors = []
# Minimum length
if len(password) < 12:
errors.append("Password must be at least 12 characters")
# Character requirements
if not re.search(r'[A-Z]', password):
errors.append("Password must contain uppercase letter")
if not re.search(r'[a-z]', password):
errors.append("Password must contain lowercase letter")
if not re.search(r'\d', password):
errors.append("Password must contain digit")
if not re.search(r'[^A-Za-z0-9]', password):
errors.append("Password must contain special character")
# Check against common passwords (use library like commonpasswords)
if password.lower() in COMMON_PASSWORDS:
errors.append("Password is too common")
return (len(errors) == 0, errors)
import secrets
from datetime import datetime, timedelta
# Step 1: Generate reset token
def create_password_reset_token(user_id: int) -> str:
token = secrets.token_urlsafe(32)
expiry = datetime.utcnow() + timedelta(hours=1)
# Store hashed token
db.execute("""
INSERT INTO password_reset_tokens (user_id, token_hash, expires_at)
VALUES (?, ?, ?)
""", [user_id, hash_token(token), expiry])
return token # Send via email
# Step 2: Validate and reset
def reset_password(token: str, new_password: str) -> bool:
token_hash = hash_token(token)
# Find valid token
record = db.fetch_one("""
SELECT user_id FROM password_reset_tokens
WHERE token_hash = ? AND expires_at > ? AND used = FALSE
""", [token_hash, datetime.utcnow()])
if not record:
raise InvalidTokenError("Invalid or expired token")
# Update password
new_hash = hash_password(new_password)
db.execute("UPDATE users SET password_hash = ? WHERE id = ?",
[new_hash, record['user_id']])
# Mark token as used
db.execute("UPDATE password_reset_tokens SET used = TRUE WHERE token_hash = ?",
[token_hash])
return True
from flask import Flask, session, request
from datetime import timedelta
app = Flask(__name__)
app.secret_key = 'your-secret-key' # Use environment variable
app.permanent_session_lifetime = timedelta(hours=24)
@app.route('/login', methods=['POST'])
def login():
credentials = request.json
# Authenticate user
user = authenticate(credentials['username'], credentials['password'])
# Create session
session.permanent = True
session['user_id'] = user.id
session['username'] = user.username
session['role'] = user.role
return {"message": "Login successful"}
@app.route('/logout', methods=['POST'])
def logout():
session.clear()
return {"message": "Logout successful"}
# Middleware to require authentication
from functools import wraps
def require_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
return {"error": "Unauthorized"}, 401
return f(*args, **kwargs)
return decorated_function
@app.route('/protected')
@require_auth
def protected_route():
return {"user_id": session['user_id']}
from flask import Flask
from flask_session import Session
app = Flask(__name__)
# Configure secure session
app.config.update(
SESSION_COOKIE_SECURE=True, # HTTPS only
SESSION_COOKIE_HTTPONLY=True, # No JavaScript access
SESSION_COOKIE_SAMESITE='Lax', # CSRF protection
PERMANENT_SESSION_LIFETIME=3600, # 1 hour timeout
)
# Server-side session storage (Redis)
app.config.update(
SESSION_TYPE='redis',
SESSION_REDIS=redis.from_url('redis://localhost:6379')
)
Session(app)
See api-authentication.md for comprehensive JWT implementation details.
Quick reference:
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key"
def create_jwt(user_id: int) -> str:
payload = {
"sub": str(user_id),
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(hours=1)
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def verify_jwt(token: str) -> dict:
try:
return jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
raise AuthenticationError("Token expired")
except jwt.InvalidTokenError:
raise AuthenticationError("Invalid token")
import pyotp
import qrcode
from io import BytesIO
class MFAService:
@staticmethod
def setup_totp(user: User) -> dict:
"""Generate TOTP secret and QR code for user"""
secret = pyotp.random_base32()
# Store encrypted secret
user.mfa_secret = encrypt(secret)
user.mfa_enabled = False # Enable after verification
db.save(user)
# Generate provisioning URI
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(
name=user.email,
issuer_name="YourApp"
)
# Generate QR code
qr = qrcode.make(uri)
buffer = BytesIO()
qr.save(buffer, format='PNG')
return {
"secret": secret, # Show once for manual entry
"qr_code": buffer.getvalue()
}
@staticmethod
def verify_totp(user: User, code: str) -> bool:
"""Verify TOTP code"""
if not user.mfa_enabled:
return False
secret = decrypt(user.mfa_secret)
totp = pyotp.TOTP(secret)
# Allow 30-second window (1 step before/after)
return totp.verify(code, valid_window=1)
@staticmethod
def enable_mfa(user: User, code: str) -> bool:
"""Enable MFA after verifying setup code"""
if MFAService.verify_totp(user, code):
user.mfa_enabled = True
db.save(user)
return True
return False
from flask import Flask, request, session
@app.route('/login', methods=['POST'])
def login():
credentials = request.json
# Step 1: Verify username/password
user = authenticate(credentials['username'], credentials['password'])
if user.mfa_enabled:
# MFA required - issue temporary token
temp_token = create_temporary_token(user.id)
session['requires_mfa'] = True
session['temp_user_id'] = user.id
return {
"requires_mfa": True,
"temp_token": temp_token
}, 200
# No MFA - complete login
create_full_session(user)
return {"message": "Login successful"}, 200
@app.route('/login/mfa', methods=['POST'])
def login_mfa():
"""Complete MFA challenge"""
if not session.get('requires_mfa'):
return {"error": "No MFA challenge pending"}, 400
user_id = session['temp_user_id']
mfa_code = request.json['code']
user = db.get_user(user_id)
if MFAService.verify_totp(user, mfa_code):
# MFA verified - complete login
session.pop('requires_mfa')
session.pop('temp_user_id')
create_full_session(user)
return {"message": "Login successful"}, 200
return {"error": "Invalid MFA code"}, 401
import secrets
def generate_backup_codes(count: int = 10) -> List[str]:
"""Generate one-time backup codes"""
codes = [secrets.token_hex(4) for _ in range(count)] # 8-char codes
# Store hashed codes
hashed_codes = [hash_backup_code(code) for code in codes]
return codes, hashed_codes
def verify_backup_code(user: User, code: str) -> bool:
"""Verify and consume backup code"""
code_hash = hash_backup_code(code)
# Find and remove code
result = db.execute("""
DELETE FROM backup_codes
WHERE user_id = ? AND code_hash = ? AND used = FALSE
RETURNING id
""", [user.id, code_hash])
return result is not None
See api-authentication.md for full OAuth implementation.
Quick integration (Python/Authlib):
from authlib.integrations.flask_client import OAuth
oauth = OAuth(app)
# Register OAuth provider
oauth.register(
'google',
client_id='YOUR_CLIENT_ID',
client_secret='YOUR_CLIENT_SECRET',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid email profile'}
)
@app.route('/auth/google')
def google_login():
redirect_uri = url_for('google_callback', _external=True)
return oauth.google.authorize_redirect(redirect_uri)
@app.route('/auth/google/callback')
def google_callback():
token = oauth.google.authorize_access_token()
user_info = oauth.google.parse_id_token(token)
# Find or create user
user = User.find_or_create_by_email(user_info['email'])
create_full_session(user)
return redirect('/dashboard')
from datetime import datetime, timedelta
def check_login_attempts(username: str) -> bool:
"""Check if account is locked due to failed attempts"""
attempts = db.fetch_one("""
SELECT COUNT(*) as count, MAX(attempted_at) as last_attempt
FROM login_attempts
WHERE username = ? AND attempted_at > ?
""", [username, datetime.utcnow() - timedelta(minutes=15)])
if attempts['count'] >= 5:
# Lock account for 15 minutes
return False
return True
def record_failed_login(username: str):
"""Record failed login attempt"""
db.execute("""
INSERT INTO login_attempts (username, attempted_at, success)
VALUES (?, ?, FALSE)
""", [username, datetime.utcnow()])
def clear_login_attempts(username: str):
"""Clear attempts after successful login"""
db.execute("DELETE FROM login_attempts WHERE username = ?", [username])
Password Security:
Session Security:
Authentication Flow:
Token Security:
Attack: Automated password guessing Prevention:
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute") # Rate limit
def login():
# Check account lockout
if not check_login_attempts(username):
return {"error": "Account locked"}, 429
# Authenticate...
Attack: Using leaked credentials from other sites Prevention:
Attack: Forcing user to use known session ID Prevention:
from flask import session
@app.route('/login', methods=['POST'])
def login():
# Authenticate user
user = authenticate(...)
# Regenerate session ID
session.regenerate()
session['user_id'] = user.id
security-authorization.md - Access control and permissionssecurity-input-validation.md - Validating authentication inputssecurity-secrets-management.md - Storing authentication secretsapi-authentication.md - JWT and OAuth 2.0 implementationdatabase-security.md - Securing credential storageLast Updated: 2025-10-27 Format Version: 1.0 (Atomic)