Authentication and Authorization
Introduction
Authentication and authorization are fundamental to application security. Authentication verifies who you are (identity), while authorization determines what you can do (permissions). Getting these wrong can lead to unauthorized access, data breaches, and compromised user accounts.
This reading covers the complete spectrum of authentication and authorization: from password security and hashing to modern approaches like JSON Web Tokens (JWT) and OAuth. You'll learn not just how these systems work, but how to implement them securely and avoid common pitfalls.
Learning Objectives
By the end of this reading, you will be able to:
- Distinguish between authentication and authorization
- Implement secure password storage using proper hashing
- Build session-based authentication systems
- Use token-based authentication with JWT
- Understand OAuth 2.0 and OpenID Connect basics
- Implement role-based and attribute-based access control
- Recognize and prevent common authentication vulnerabilities
- Apply security best practices to authentication systems
Authentication vs Authorization
These terms are often confused but represent distinct concepts:
Authentication (AuthN)
Who are you?
Authentication verifies the identity of a user or system. It answers the question "Are you really who you claim to be?"
Common methods:
- Something you know (password, PIN)
- Something you have (phone, hardware token)
- Something you are (fingerprint, face)
- Somewhere you are (geolocation)
Authorization (AuthZ)
What can you do?
Authorization determines what resources a user can access and what actions they can perform. It answers the question "Are you allowed to do this?"
Common models:
- Role-Based Access Control (RBAC)
- Attribute-Based Access Control (ABAC)
- Access Control Lists (ACL)
Example Scenario
# Authentication: Verify user identity
def login(username, password):
user = authenticate(username, password)
if user:
return create_session(user)
return None
# Authorization: Check permissions
def delete_post(user, post_id):
post = get_post(post_id)
# Check if user is authorized to delete this post
if user.id == post.author_id or user.role == 'admin':
delete(post)
return True
raise PermissionDenied("You don't have permission to delete this post")
Password Security
Passwords remain the most common authentication method. Securing them properly is critical.
Password Storage: Never Store Plaintext
# ABSOLUTELY NEVER DO THIS
def create_user_terrible(username, password):
db.execute(
"INSERT INTO users (username, password) VALUES (?, ?)",
(username, password) # STORING PLAINTEXT PASSWORD!
)
# When database is breached, all passwords are immediately compromised
Password Hashing with Salt
import hashlib
import os
import hmac
class PasswordHasher:
"""Secure password hashing implementation"""
def __init__(self, iterations=100000):
self.iterations = iterations
def hash_password(self, password):
"""
Hash password using PBKDF2-SHA256
Returns: Dictionary with salt and hash
"""
# Generate random salt (32 bytes = 256 bits)
salt = os.urandom(32)
# Use PBKDF2 (Password-Based Key Derivation Function 2)
# Many iterations make brute-force attacks slower
key = hashlib.pbkdf2_hmac(
'sha256', # Hash algorithm
password.encode(), # Password as bytes
salt, # Random salt
self.iterations # Number of iterations
)
return {
'salt': salt.hex(),
'hash': key.hex(),
'iterations': self.iterations
}
def verify_password(self, password, stored_salt, stored_hash, iterations):
"""Verify password against stored hash"""
# Hash the provided password with same salt and iterations
key = hashlib.pbkdf2_hmac(
'sha256',
password.encode(),
bytes.fromhex(stored_salt),
iterations
)
# Constant-time comparison prevents timing attacks
return hmac.compare_digest(key.hex(), stored_hash)
# Usage example
hasher = PasswordHasher()
# When user registers
password = "MySecurePassword123!"
hashed = hasher.hash_password(password)
print(f"Salt: {hashed['salt'][:32]}...")
print(f"Hash: {hashed['hash'][:32]}...")
# Store in database
db.execute("""
INSERT INTO users (username, password_salt, password_hash, hash_iterations)
VALUES (?, ?, ?, ?)
""", ('alice', hashed['salt'], hashed['hash'], hashed['iterations']))
# When user logs in
is_valid = hasher.verify_password(
'MySecurePassword123!',
hashed['salt'],
hashed['hash'],
hashed['iterations']
)
print(f"Password valid: {is_valid}")
Modern Password Hashing: bcrypt and Argon2
import bcrypt
# bcrypt is designed for passwords and automatically handles salting
class BcryptPasswordHasher:
def __init__(self, rounds=12):
"""
rounds: Cost factor (higher = slower = more secure)
Each increment doubles the time
12 is a good default (2^12 iterations)
"""
self.rounds = rounds
def hash_password(self, password):
"""Hash password using bcrypt"""
# bcrypt handles salt generation internally
salt = bcrypt.gensalt(rounds=self.rounds)
hashed = bcrypt.hashpw(password.encode(), salt)
# Return as string for storage
return hashed.decode('utf-8')
def verify_password(self, password, hashed):
"""Verify password"""
return bcrypt.checkpw(
password.encode(),
hashed.encode()
)
# Usage
bcrypt_hasher = BcryptPasswordHasher()
# Hash password
password = "MySecurePassword123!"
hashed = bcrypt_hasher.hash_password(password)
print(f"Bcrypt hash: {hashed}")
# Verify password
print(f"Valid: {bcrypt_hasher.verify_password(password, hashed)}")
print(f"Invalid: {bcrypt_hasher.verify_password('wrong', hashed)}")
# Argon2: Winner of Password Hashing Competition, most secure option
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
class SecurePasswordHasher:
def __init__(self):
# Default parameters are secure
self.hasher = PasswordHasher()
def hash_password(self, password):
"""Hash password using Argon2id"""
return self.hasher.hash(password)
def verify_password(self, password, hashed):
"""Verify password"""
try:
self.hasher.verify(hashed, password)
return True
except VerifyMismatchError:
return False
# Usage
argon2_hasher = SecurePasswordHasher()
hashed = argon2_hasher.hash_password("MySecurePassword123!")
print(f"Argon2 hash: {hashed}")
Password Policy Implementation
import re
class PasswordPolicy:
"""Enforce password complexity requirements"""
def __init__(self):
self.min_length = 12
self.require_uppercase = True
self.require_lowercase = True
self.require_digit = True
self.require_special = True
self.common_passwords = self.load_common_passwords()
def load_common_passwords(self):
"""Load list of commonly used passwords"""
# In production, load from file like "10k-most-common.txt"
return {
'password', '123456', 'password123', 'admin',
'qwerty', 'letmein', 'welcome', 'monkey123'
}
def validate(self, password):
"""
Validate password against policy
Returns: (is_valid, error_messages)
"""
errors = []
# Check length
if len(password) < self.min_length:
errors.append(f"Password must be at least {self.min_length} characters")
# Check character requirements
if self.require_uppercase and not re.search(r'[A-Z]', password):
errors.append("Password must contain uppercase letter")
if self.require_lowercase and not re.search(r'[a-z]', password):
errors.append("Password must contain lowercase letter")
if self.require_digit and not re.search(r'\d', password):
errors.append("Password must contain digit")
if self.require_special and not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
errors.append("Password must contain special character")
# Check against common passwords
if password.lower() in self.common_passwords:
errors.append("Password is too common")
return len(errors) == 0, errors
# Usage
policy = PasswordPolicy()
passwords = [
"weak",
"StillWeak123",
"BetterP@ssw0rd!",
"password123"
]
for pwd in passwords:
is_valid, errors = policy.validate(pwd)
print(f"\nPassword: {pwd}")
print(f"Valid: {is_valid}")
if errors:
for error in errors:
print(f" - {error}")
Session-Based Authentication
Traditional web authentication using server-side sessions.
How Sessions Work
- User logs in with credentials
- Server creates session and stores session data
- Server sends session ID to client (usually in cookie)
- Client includes session ID in subsequent requests
- Server looks up session data by session ID
Implementation
import secrets
from datetime import datetime, timedelta
import json
class SessionManager:
"""Server-side session management"""
def __init__(self):
# In production, use Redis or database
self.sessions = {}
self.session_timeout = timedelta(hours=2)
def create_session(self, user_id, user_data=None):
"""Create new session for user"""
# Generate cryptographically secure session ID
session_id = secrets.token_urlsafe(32)
# Store session data
self.sessions[session_id] = {
'user_id': user_id,
'user_data': user_data or {},
'created_at': datetime.now(),
'last_activity': datetime.now(),
'ip_address': self.get_client_ip(),
'user_agent': self.get_user_agent()
}
return session_id
def get_session(self, session_id):
"""Retrieve session data"""
session = self.sessions.get(session_id)
if not session:
return None
# Check if session has expired
if datetime.now() - session['last_activity'] > self.session_timeout:
self.destroy_session(session_id)
return None
# Update last activity time
session['last_activity'] = datetime.now()
return session
def destroy_session(self, session_id):
"""Destroy session (logout)"""
if session_id in self.sessions:
del self.sessions[session_id]
def get_client_ip(self):
"""Get client IP address"""
# Implementation depends on framework
return "192.168.1.100"
def get_user_agent(self):
"""Get client user agent"""
# Implementation depends on framework
return "Mozilla/5.0..."
# Flask example
from flask import Flask, request, make_response
app = Flask(__name__)
session_manager = SessionManager()
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
# Authenticate user
user = authenticate_user(username, password)
if not user:
return {'error': 'Invalid credentials'}, 401
# Create session
session_id = session_manager.create_session(
user_id=user.id,
user_data={'username': user.username, 'role': user.role}
)
# Set session cookie
response = make_response({'success': True})
response.set_cookie(
'session_id',
session_id,
httponly=True, # Prevents JavaScript access
secure=True, # Only sent over HTTPS
samesite='Strict' # CSRF protection
)
return response
@app.route('/logout', methods=['POST'])
def logout():
session_id = request.cookies.get('session_id')
if session_id:
session_manager.destroy_session(session_id)
response = make_response({'success': True})
response.set_cookie('session_id', '', expires=0)
return response
@app.route('/api/protected')
def protected_route():
# Get session ID from cookie
session_id = request.cookies.get('session_id')
# Validate session
session = session_manager.get_session(session_id)
if not session:
return {'error': 'Unauthorized'}, 401
# Session is valid, process request
return {
'message': 'Access granted',
'user': session['user_data']['username']
}
Session Security Best Practices
class SecureSessionManager(SessionManager):
"""Enhanced session manager with security features"""
def create_session(self, user_id, user_data=None):
"""Create session with enhanced security"""
session_id = super().create_session(user_id, user_data)
# Store IP address and user agent for validation
session = self.sessions[session_id]
session['ip_address'] = self.get_client_ip()
session['user_agent'] = self.get_user_agent()
return session_id
def get_session(self, session_id):
"""Get session with security checks"""
session = super().get_session(session_id)
if not session:
return None
# Check if IP address changed (optional, can break with mobile users)
# if session['ip_address'] != self.get_client_ip():
# self.destroy_session(session_id)
# return None
# Check if user agent changed (sign of session hijacking)
if session['user_agent'] != self.get_user_agent():
self.destroy_session(session_id)
return None
return session
def rotate_session_id(self, old_session_id):
"""
Rotate session ID to prevent session fixation attacks
Call this after privilege escalation (e.g., after login)
"""
session = self.sessions.get(old_session_id)
if not session:
return None
# Create new session ID
new_session_id = secrets.token_urlsafe(32)
# Copy session data to new ID
self.sessions[new_session_id] = session
# Delete old session
del self.sessions[old_session_id]
return new_session_id
Token-Based Authentication
Modern approach using tokens instead of server-side sessions. Popular for APIs and single-page applications.
JSON Web Tokens (JWT)
JWTs are self-contained tokens that encode user information and claims.
Structure: header.payload.signature
- Header: Token type and algorithm
- Payload: Claims (user data, expiration, etc.)
- Signature: Cryptographic signature to verify integrity
import jwt
from datetime import datetime, timedelta
class JWTAuthManager:
"""JWT-based authentication"""
def __init__(self, secret_key):
self.secret_key = secret_key
self.algorithm = 'HS256'
self.token_expiry = timedelta(hours=1)
self.refresh_expiry = timedelta(days=7)
def create_access_token(self, user_id, user_data=None):
"""Create access token"""
payload = {
'user_id': user_id,
'user_data': user_data or {},
'exp': datetime.utcnow() + self.token_expiry,
'iat': datetime.utcnow(), # Issued at
'type': 'access'
}
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
return token
def create_refresh_token(self, user_id):
"""Create refresh token (longer-lived, for getting new access tokens)"""
payload = {
'user_id': user_id,
'exp': datetime.utcnow() + self.refresh_expiry,
'iat': datetime.utcnow(),
'type': 'refresh'
}
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
return token
def verify_token(self, token, token_type='access'):
"""Verify and decode token"""
try:
payload = jwt.decode(
token,
self.secret_key,
algorithms=[self.algorithm]
)
# Verify token type
if payload.get('type') != token_type:
return None
return payload
except jwt.ExpiredSignatureError:
# Token has expired
return None
except jwt.InvalidTokenError:
# Token is invalid
return None
def refresh_access_token(self, refresh_token):
"""Generate new access token from refresh token"""
payload = self.verify_token(refresh_token, token_type='refresh')
if not payload:
return None
# Create new access token
return self.create_access_token(payload['user_id'])
# Usage example
jwt_manager = JWTAuthManager(secret_key='your-secret-key-here')
# Login: Create tokens
user_id = 42
access_token = jwt_manager.create_access_token(
user_id=user_id,
user_data={'username': 'alice', 'role': 'user'}
)
refresh_token = jwt_manager.create_refresh_token(user_id)
print(f"Access Token: {access_token}")
print(f"Refresh Token: {refresh_token}")
# Verify token
payload = jwt_manager.verify_token(access_token)
print(f"\nDecoded payload: {payload}")
# Refresh access token when it expires
new_access_token = jwt_manager.refresh_access_token(refresh_token)
print(f"\nNew Access Token: {new_access_token}")
Using JWT in Web APIs
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
jwt_manager = JWTAuthManager(secret_key='your-secret-key')
def require_jwt(f):
"""Decorator to protect routes with JWT"""
@wraps(f)
def decorated_function(*args, **kwargs):
# Get token from Authorization header
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({'error': 'No authorization header'}), 401
# Expected format: "Bearer <token>"
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != 'bearer':
return jsonify({'error': 'Invalid authorization header'}), 401
token = parts[1]
# Verify token
payload = jwt_manager.verify_token(token)
if not payload:
return jsonify({'error': 'Invalid or expired token'}), 401
# Add user info to request context
request.user_id = payload['user_id']
request.user_data = payload.get('user_data', {})
return f(*args, **kwargs)
return decorated_function
@app.route('/api/login', methods=['POST'])
def api_login():
username = request.json.get('username')
password = request.json.get('password')
# Authenticate user
user = authenticate_user(username, password)
if not user:
return jsonify({'error': 'Invalid credentials'}), 401
# Create tokens
access_token = jwt_manager.create_access_token(
user_id=user.id,
user_data={'username': user.username, 'role': user.role}
)
refresh_token = jwt_manager.create_refresh_token(user.id)
return jsonify({
'access_token': access_token,
'refresh_token': refresh_token,
'token_type': 'Bearer'
})
@app.route('/api/refresh', methods=['POST'])
def api_refresh():
refresh_token = request.json.get('refresh_token')
new_access_token = jwt_manager.refresh_access_token(refresh_token)
if not new_access_token:
return jsonify({'error': 'Invalid refresh token'}), 401
return jsonify({'access_token': new_access_token})
@app.route('/api/protected')
@require_jwt
def protected_api():
# User info available from decorator
return jsonify({
'message': f'Hello {request.user_data["username"]}',
'user_id': request.user_id
})
JWT vs Sessions
| Feature | JWT | Sessions |
|---|---|---|
| Storage | Client-side | Server-side |
| Scalability | Excellent (stateless) | Requires session store |
| Revocation | Difficult (need blacklist) | Easy (delete session) |
| Size | Larger (sent with each request) | Small (just session ID) |
| Security | Vulnerable if secret leaked | More control on server |
| Use Case | APIs, microservices | Traditional web apps |
OAuth 2.0 Basics
OAuth is a protocol for authorization, allowing third-party apps to access user resources without sharing passwords.
OAuth Roles
- Resource Owner: User who owns the data
- Client: Application requesting access
- Authorization Server: Issues tokens
- Resource Server: Hosts protected resources
Authorization Code Flow
Most secure OAuth flow for web applications:
import requests
from urllib.parse import urlencode
class OAuthClient:
"""OAuth 2.0 Client Implementation"""
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
# OAuth provider endpoints
self.auth_url = 'https://provider.com/oauth/authorize'
self.token_url = 'https://provider.com/oauth/token'
self.api_base = 'https://api.provider.com'
def get_authorization_url(self, state):
"""
Step 1: Generate authorization URL
User is redirected here to grant permission
"""
params = {
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'response_type': 'code',
'scope': 'read:user read:email',
'state': state # CSRF protection
}
return f"{self.auth_url}?{urlencode(params)}"
def exchange_code_for_token(self, code):
"""
Step 2: Exchange authorization code for access token
Called after user is redirected back with code
"""
data = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'code': code,
'redirect_uri': self.redirect_uri,
'grant_type': 'authorization_code'
}
response = requests.post(self.token_url, data=data)
response.raise_for_status()
return response.json()
# Returns: {
# 'access_token': '...',
# 'token_type': 'Bearer',
# 'expires_in': 3600,
# 'refresh_token': '...',
# 'scope': 'read:user read:email'
# }
def get_user_info(self, access_token):
"""
Step 3: Use access token to get user info
"""
headers = {
'Authorization': f'Bearer {access_token}'
}
response = requests.get(
f'{self.api_base}/user',
headers=headers
)
response.raise_for_status()
return response.json()
# Flask example of OAuth flow
from flask import Flask, redirect, request, session
import secrets
app = Flask(__name__)
app.secret_key = 'your-secret-key'
oauth_client = OAuthClient(
client_id='your-client-id',
client_secret='your-client-secret',
redirect_uri='http://localhost:5000/callback'
)
@app.route('/login/oauth')
def oauth_login():
# Generate state token for CSRF protection
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
# Redirect user to OAuth provider
auth_url = oauth_client.get_authorization_url(state)
return redirect(auth_url)
@app.route('/callback')
def oauth_callback():
# Verify state to prevent CSRF
state = request.args.get('state')
if state != session.get('oauth_state'):
return 'Invalid state parameter', 400
# Get authorization code
code = request.args.get('code')
if not code:
return 'No code provided', 400
# Exchange code for token
token_data = oauth_client.exchange_code_for_token(code)
access_token = token_data['access_token']
# Get user info
user_info = oauth_client.get_user_info(access_token)
# Create session for user
session['user_id'] = user_info['id']
session['username'] = user_info['username']
return redirect('/dashboard')
Role-Based Access Control (RBAC)
Assign permissions to roles, then assign roles to users.
from enum import Enum
from typing import Set
class Permission(Enum):
"""Define available permissions"""
READ_POST = 'read:post'
CREATE_POST = 'create:post'
UPDATE_POST = 'update:post'
DELETE_POST = 'delete:post'
READ_USER = 'read:user'
UPDATE_USER = 'update:user'
DELETE_USER = 'delete:user'
ADMIN_PANEL = 'admin:panel'
class Role:
"""Role with assigned permissions"""
def __init__(self, name: str, permissions: Set[Permission]):
self.name = name
self.permissions = permissions
def has_permission(self, permission: Permission) -> bool:
return permission in self.permissions
# Define roles
ROLES = {
'user': Role('user', {
Permission.READ_POST,
Permission.CREATE_POST,
Permission.UPDATE_POST, # Own posts only
Permission.READ_USER,
}),
'moderator': Role('moderator', {
Permission.READ_POST,
Permission.CREATE_POST,
Permission.UPDATE_POST, # Any post
Permission.DELETE_POST, # Any post
Permission.READ_USER,
Permission.UPDATE_USER,
}),
'admin': Role('admin', {
# All permissions
*list(Permission)
})
}
class User:
def __init__(self, user_id, username, role_name):
self.id = user_id
self.username = username
self.role = ROLES[role_name]
def has_permission(self, permission: Permission) -> bool:
return self.role.has_permission(permission)
# Authorization decorator
from functools import wraps
from flask import jsonify, request
def require_permission(permission: Permission):
"""Decorator to require specific permission"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Get current user (from session/JWT)
user = get_current_user(request)
if not user:
return jsonify({'error': 'Unauthorized'}), 401
if not user.has_permission(permission):
return jsonify({'error': 'Forbidden'}), 403
return f(*args, **kwargs)
return decorated_function
return decorator
# Usage
@app.route('/api/posts', methods=['POST'])
@require_permission(Permission.CREATE_POST)
def create_post():
# User has CREATE_POST permission
return jsonify({'message': 'Post created'})
@app.route('/api/posts/<int:post_id>', methods=['DELETE'])
@require_permission(Permission.DELETE_POST)
def delete_post(post_id):
# User has DELETE_POST permission
# Additional check: is user owner or moderator?
user = get_current_user(request)
post = get_post(post_id)
if post.author_id != user.id and user.role.name != 'moderator':
return jsonify({'error': 'Can only delete your own posts'}), 403
# Delete post
return jsonify({'message': 'Post deleted'})
Multi-Factor Authentication (MFA)
Adding additional authentication factors beyond passwords.
Time-Based One-Time Password (TOTP)
import pyotp
import qrcode
from io import BytesIO
class TOTPManager:
"""Manage TOTP-based 2FA"""
def generate_secret(self, user_email):
"""Generate TOTP secret for user"""
# Create random secret
secret = pyotp.random_base32()
# Create provisioning URI for QR code
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(
name=user_email,
issuer_name='MyApp'
)
return secret, uri
def generate_qr_code(self, uri):
"""Generate QR code image"""
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# Convert to bytes
buffer = BytesIO()
img.save(buffer, format='PNG')
buffer.seek(0)
return buffer.getvalue()
def verify_totp(self, secret, code):
"""Verify TOTP code"""
totp = pyotp.TOTP(secret)
return totp.verify(code, valid_window=1)
# Example flow
totp_manager = TOTPManager()
# When user enables 2FA
user_email = "alice@example.com"
secret, uri = totp_manager.generate_secret(user_email)
# Store secret in database (encrypted!)
# db.execute("UPDATE users SET totp_secret = ? WHERE email = ?", (secret, user_email))
# Show QR code to user to scan with authenticator app
qr_image = totp_manager.generate_qr_code(uri)
# When user logs in, after password verification
user_code = input("Enter 6-digit code from authenticator: ")
is_valid = totp_manager.verify_totp(secret, user_code)
if is_valid:
print("2FA verification successful")
else:
print("Invalid code")
Exercises
Basic Exercises
Password Hashing: Implement a user registration and login system using bcrypt for password hashing.
Session Management: Build a simple session manager that:
- Creates sessions with random IDs
- Stores user data
- Expires sessions after 30 minutes of inactivity
JWT Claims: Create a JWT token with custom claims (user role, organization) and verify those claims are present when decoding.
Intermediate Exercises
Complete Auth System: Build a complete authentication system with:
- User registration with password validation
- Login with session creation
- Logout
- Protected routes
- Password reset flow (email with token)
RBAC Implementation: Implement role-based access control with:
- At least 3 roles (user, moderator, admin)
- At least 5 different permissions
- Decorator to protect routes by permission
- Admin interface to manage user roles
Token Refresh Flow: Implement JWT token refresh mechanism:
- Short-lived access tokens (15 minutes)
- Long-lived refresh tokens (7 days)
- Endpoint to refresh access token
- Automatic token refresh in client
Advanced Exercises
OAuth Provider: Build a simple OAuth 2.0 authorization server that:
- Implements authorization code flow
- Issues access and refresh tokens
- Validates tokens
- Provides user info endpoint
Attribute-Based Access Control: Implement ABAC where authorization depends on:
- User attributes (department, level)
- Resource attributes (owner, visibility)
- Environmental attributes (time, location)
Security Hardening: Take an existing auth system and add:
- Brute force protection (rate limiting, account lockout)
- Session fixation protection
- CSRF protection
- Account enumeration prevention
- Suspicious activity detection
Summary
Authentication and authorization are complex but critical security domains:
Password Security: Never store plaintext passwords. Use modern hashing algorithms like bcrypt or Argon2 with proper salting. Enforce strong password policies.
Sessions: Traditional session-based auth stores state on the server. Secure cookies with HttpOnly, Secure, and SameSite flags. Implement session timeouts and rotation.
JWT Tokens: Stateless authentication suitable for APIs and distributed systems. Shorter-lived access tokens with longer-lived refresh tokens. Be aware of limitations around token revocation.
OAuth 2.0: Standard protocol for delegated authorization. Allows users to grant third-party access without sharing passwords. Authorization code flow is most secure for web apps.
Access Control: RBAC assigns permissions to roles. Implement principle of least privilege. Always check authorization in addition to authentication.
Multi-Factor Authentication: Adds significant security beyond passwords. TOTP is widely supported and relatively easy to implement.
Key Takeaways
- Authentication (who you are) and authorization (what you can do) are distinct
- Password hashing is mandatory, use bcrypt or Argon2
- Sessions are stateful, JWTs are stateless - choose based on your needs
- Always validate tokens/sessions on every request
- Implement defense in depth: passwords + 2FA, authentication + authorization
- OAuth solves the delegation problem without password sharing
Further Reading
- OWASP Authentication Cheat Sheet
- RFC 6749: OAuth 2.0 Authorization Framework
- RFC 7519: JSON Web Token (JWT)
- "OAuth 2 in Action" by Justin Richer and Antonio Sanso
- NIST Digital Identity Guidelines (SP 800-63)
Next Steps
Continue to 04-vulnerabilities.md to learn about common security vulnerabilities and how to prevent them in your code.