Authentication memverifikasi siapa user. Session management memastikan siapa mereka tetap diketahui di request berikutnya. Keduanya adalah fondasi untuk authorization dan non-repudiation.
Tujuan Pembelajaran
- Memahami password storage (hashing, salt, algorithms)
- Implement secure session management
- Understand JWT (JSON Web Tokens) dan security implications
- MFA implementation dan recovery codes
- Brute force defense (rate limiting, account lockout)
Password Storage
✅ Strong Algorithms
# ✅ GOOD - Argon2id (OWASP recommended)
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash("user_password") # Store this
# Verify:
ph.verify(hashed, "user_password") # Returns True/False
# ✅ GOOD - bcrypt
import bcrypt
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(b"password", salt)
# Verify:
bcrypt.checkpw(b"password", hashed)
# ❌ BAD - MD5, SHA1 (too fast, rainbow tables exist)
import hashlib
hashed = hashlib.md5(password).hexdigest() # Never do this!
Session Token Generation
# ✅ SECURE - Cryptographically random
import secrets
session_id = secrets.token_urlsafe(32) # 256-bit random
# ❌ INSECURE - Predictable
import time
session_id = str(int(time.time())) # Guessable!
Secure Session Storage
# ✅ SECURE - HttpOnly, Secure, SameSite
@app.route('/login', methods=['POST'])
def login():
user = authenticate(request.form['email'], request.form['password'])
if not user:
return 'Invalid credentials', 401
session_id = secrets.token_urlsafe(32)
# Store session server-side (database, Redis)
Session.create(session_id, user.id, expires_in=3600)
# Send HttpOnly cookie
resp = make_response({'success': True})
resp.set_cookie(
'__Host-session',
session_id,
secure=True, # HTTPS only
httpOnly=True, # JS can't access
sameSite='Lax', # CSRF protection
max_age=3600
)
return resp
JWT (JSON Web Token) Security
# ✅ SECURE JWT usage
import jwt
from datetime import datetime, timedelta
# Generate
payload = {
'user_id': 123,
'exp': datetime.utcnow() + timedelta(hours=1),
'iat': datetime.utcnow()
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
# Verify
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
# If invalid or expired, exception raised
Common JWT Mistakes
# ❌ DANGEROUS - No signature verification
header, payload, sig = token.split('.')
decoded = json.loads(base64.b64decode(payload))
# Attacker can modify payload without signature check!
# ❌ DANGEROUS - Using 'none' algorithm
# JWT header: {"alg": "none"}
# Attacker send: header.payload. (no signature)
# ✅ SECURE - Strict algorithm check
jwt.decode(token, SECRET_KEY, algorithms=['HS256']) # Only HS256
Multi-Factor Authentication (MFA)
# Step 1: User login with password
@app.route('/login', methods=['POST'])
def login():
user = authenticate(email, password)
if not user:
return 'Invalid credentials', 401
# Generate MFA challenge
mfa_token = secrets.token_urlsafe(32)
cache.set(f'mfa:{mfa_token}', user.id, expire=300) # 5 min
# Send OTP
otp = ''.join(str(random.randint(0, 9)) for _ in range(6))
send_sms(user.phone, f'Your OTP: {otp}')
cache.set(f'otp:{mfa_token}', otp, expire=300)
return {'mfa_required': True, 'mfa_token': mfa_token}
# Step 2: User submit OTP
@app.route('/mfa-verify', methods=['POST'])
def mfa_verify():
mfa_token = request.json['mfa_token']
otp = request.json['otp']
user_id = cache.get(f'mfa:{mfa_token}')
correct_otp = cache.get(f'otp:{mfa_token}')
if not user_id or otp != correct_otp:
return 'Invalid OTP', 401
# Create session
session_id = secrets.token_urlsafe(32)
Session.create(session_id, user_id)
# Clean up
cache.delete(f'mfa:{mfa_token}')
cache.delete(f'otp:{mfa_token}')
return {'session': session_id}
Brute Force Defense
# ❌ VULNERABLE - No rate limiting
@app.route('/login', methods=['POST'])
def login():
user = User.find_by_email(request.form['email'])
if not user or not user.verify_password(request.form['password']):
return 'Invalid', 401
return {'session': create_session(user)}
# ✅ SECURE - Rate limiting + account lockout
from flask_limiter import Limiter
limiter = Limiter(app, key_func=lambda: request.remote_addr)
@app.route('/login', methods=['POST'])
@limiter.limit('5 per 15 minutes')
def login():
email = request.form['email']
user = User.find_by_email(email)
if user and user.locked_until and datetime.utcnow() < user.locked_until:
return 'Account locked', 429
if not user or not user.verify_password(request.form['password']):
# Increment failed attempts
if user:
user.failed_login_attempts += 1
if user.failed_login_attempts >= 5:
user.locked_until = datetime.utcnow() + timedelta(minutes=30)
user.save()
return 'Invalid', 401
# Successful login
user.failed_login_attempts = 0
user.locked_until = None
user.save()
return {'session': create_session(user)}
Session Fixation Prevention
# ❌ VULNERABLE - Same session ID after login
def login(username, password):
if verify_credentials(username, password):
return {'session': request.cookies.get('session')} # WRONG!
# ✅ SECURE - Regenerate session
def login(username, password):
if verify_credentials(username, password):
old_session_id = request.cookies.get('session')
# Delete old session
Session.delete(old_session_id)
# Create new session
new_session_id = secrets.token_urlsafe(32)
Session.create(new_session_id, user.id)
return {'session': new_session_id}
Auth Checklist
- [ ] Passwords hashed dengan Argon2id atau bcrypt
- [ ] Session ID cryptographically random (256+ bit)
- [ ] HttpOnly + Secure + SameSite cookie flags
- [ ] Session timeout (idle + absolute max)
- [ ] Rate limiting on login (5 attempts/15min)
- [ ] Account lockout setelah N failed attempts
- [ ] Session regenerated setelah login
- [ ] MFA available (TOTP preferred over SMS)
- [ ] Password reset flow aman (token dengan expiry)
- [ ] Logout invalidates session
- [ ] No credential logs atau error messages yang leak
Kesimpulan
Authentication & Session = expensive to break, cheap to implement correctly. Use proven libraries, follow best practices, test thoroughly.
Modul berikutnya (#09) membahas HTTPS, TLS, & Certificate Security.
Next: #09 - HTTPS, TLS, & Encryption


