Overview
Security is not optional. Understanding security fundamentals helps you build systems that protect user data and resist attacks.
Authentication vs Authorization
Authentication (AuthN) Who are you?
Verifies identity
Login, passwords, MFA
“Prove you are who you claim”
Authorization (AuthZ) What can you do?
Verifies permissions
Roles, policies, ACLs
“Are you allowed to do this?”
Authentication Methods
Password-Based (Traditional)
import bcrypt
# Storing passwords
def hash_password ( password : str ) -> bytes :
salt = bcrypt.gensalt( rounds = 12 )
return bcrypt.hashpw(password.encode(), salt)
# Verifying passwords
def verify_password ( password : str , hashed : bytes ) -> bool :
return bcrypt.checkpw(password.encode(), hashed)
JWT (JSON Web Tokens)
┌─────────────────────────────────────────────────────┐
│ JWT Token │
├─────────────────┬─────────────────┬─────────────────┤
│ Header │ Payload │ Signature │
│ {"alg":"HS256"}│ {"sub":"12345"} │ HMACSHA256( │
│ │ {"exp":16234...}│ header.payload│
│ │ │ , secret) │
└─────────────────┴─────────────────┴─────────────────┘
↓ ↓ ↓
Base64URL Base64URL Base64URL
↓ ↓ ↓
eyJhbGci.... eyJzdWIi.... SflKxwRJS...
import jwt
from datetime import datetime, timedelta
# Creating JWT
def create_token ( user_id : str , secret : str ) -> str :
payload = {
"sub" : user_id,
"iat" : datetime.utcnow(),
"exp" : datetime.utcnow() + timedelta( hours = 24 )
}
return jwt.encode(payload, secret, algorithm = "HS256" )
# Verifying JWT
def verify_token ( token : str , secret : str ) -> dict :
try :
return jwt.decode(token, secret, algorithms = [ "HS256" ])
except jwt.ExpiredSignatureError:
raise Exception ( "Token expired" )
except jwt.InvalidTokenError:
raise Exception ( "Invalid token" )
OAuth 2.0 Flow
┌──────────┐ ┌──────────────┐
│ User │ │Authorization │
│ │ │ Server │
└────┬─────┘ └──────┬───────┘
│ │
│ 1. User clicks "Login with Google" │
│──────────────────────────────────────────►│
│ │
│ 2. Redirect to authorization page │
│◄──────────────────────────────────────────│
│ │
│ 3. User grants permission │
│──────────────────────────────────────────►│
│ │
│ 4. Redirect with authorization code │
│◄──────────────────────────────────────────│
│ │
┌────┴─────┐ 5. Exchange code for tokens ┌──────┴───────┐
│ App │──────────────────────────────►│Auth Server │
│ │◄──────────────────────────────│ │
└──────────┘ 6. Access + Refresh tokens └──────────────┘
OWASP Top 10 (2021)
1. Broken Access Control
# ❌ Vulnerable: No authorization check
@app.get ( "/users/ {user_id} /data" )
def get_user_data ( user_id : int ):
return db.get_user_data(user_id) # Anyone can access any user's data!
# ✅ Secure: Check authorization
@app.get ( "/users/ {user_id} /data" )
def get_user_data ( user_id : int , current_user : User = Depends(get_current_user)):
if current_user.id != user_id and not current_user.is_admin:
raise HTTPException( 403 , "Access denied" )
return db.get_user_data(user_id)
2. Cryptographic Failures
# ❌ Bad: Weak hashing
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# ✅ Good: Proper password hashing
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt( 12 ))
3. Injection
# ❌ SQL Injection vulnerable
query = f "SELECT * FROM users WHERE id = { user_id } "
# ✅ Parameterized query
query = "SELECT * FROM users WHERE id = ?"
cursor.execute(query, (user_id,))
4. Insecure Design
# ❌ Bad: Security question for password reset
def reset_password ( email , mothers_maiden_name ):
# Easily guessable/discoverable answers
pass
# ✅ Good: Token-based password reset
def reset_password ( email ):
token = generate_secure_token()
send_reset_email(email, token)
store_token_with_expiry(email, token, expiry = 30_minutes )
5. Security Misconfiguration
# ❌ Bad: Debug mode in production
app.run( debug = True )
# ❌ Bad: Default credentials
DATABASE_PASSWORD = "admin123"
# ✅ Good: Secure configuration
app.run( debug = os.getenv( "FLASK_DEBUG" , False ))
DATABASE_PASSWORD = os.getenv( "DATABASE_PASSWORD" )
6. Vulnerable Components
# Regularly audit dependencies
npm audit
pip-audit
snyk test
# Keep dependencies updated
npm update
pip install --upgrade package_name
7. Authentication Failures
# ❌ Bad: No rate limiting
@app.post ( "/login" )
def login ( credentials ):
return authenticate(credentials)
# ✅ Good: Rate limiting + account lockout
from slowapi import Limiter
limiter = Limiter( key_func = get_remote_address)
@app.post ( "/login" )
@limiter.limit ( "5/minute" )
def login ( credentials ):
if get_failed_attempts(credentials.email) > 5 :
raise HTTPException( 429 , "Account locked" )
return authenticate(credentials)
Encryption
Symmetric vs Asymmetric
Type Key Speed Use Case Symmetric (AES)Same key Fast Data at rest, bulk encryption Asymmetric (RSA)Public/Private pair Slow Key exchange, digital signatures
HTTPS/TLS
┌──────────┐ ┌──────────┐
│ Client │ │ Server │
└────┬─────┘ └────┬─────┘
│ 1. ClientHello (supported ciphers) │
│─────────────────────────────────────►│
│ │
│ 2. ServerHello + Certificate │
│◄─────────────────────────────────────│
│ │
│ 3. Key Exchange (encrypted) │
│─────────────────────────────────────►│
│ │
│ 4. Secure connection established │
│◄────────────────────────────────────►│
│ (All traffic encrypted) │
└──────────────────────────────────────┘
CORS (Cross-Origin Resource Sharing)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# ❌ Bad: Allow all origins
app.add_middleware(
CORSMiddleware,
allow_origins = [ "*" ], # Dangerous!
allow_methods = [ "*" ],
allow_headers = [ "*" ],
)
# ✅ Good: Specific origins
app.add_middleware(
CORSMiddleware,
allow_origins = [ "https://myapp.com" , "https://admin.myapp.com" ],
allow_credentials = True ,
allow_methods = [ "GET" , "POST" , "PUT" , "DELETE" ],
allow_headers = [ "Authorization" , "Content-Type" ],
)
Content Security Policy (CSP)
# HTTP Header to prevent XSS
Content - Security - Policy:
default - src 'self' ;
script - src 'self' https: // trusted - cdn.com ;
style - src 'self' 'unsafe-inline' ;
img - src 'self' data: https: ;
connect - src 'self' https: // api.myapp.com ;
frame - ancestors 'none' ;
API Security Best Practices
Rate Limiting
from fastapi import FastAPI, Request
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter( key_func = get_remote_address)
app = FastAPI()
@app.get ( "/api/search" )
@limiter.limit ( "10/minute" ) # 10 requests per minute per IP
async def search ( request : Request, query : str ):
return { "results" : perform_search(query)}
@app.post ( "/api/login" )
@limiter.limit ( "5/minute" ) # Stricter for auth endpoints
async def login ( request : Request, credentials : Credentials):
return authenticate(credentials)
API Key Authentication
from fastapi import Security, HTTPException
from fastapi.security import APIKeyHeader
api_key_header = APIKeyHeader( name = "X-API-Key" )
async def verify_api_key ( api_key : str = Security(api_key_header)):
# Hash the API key before comparing (store hashed in DB)
hashed_key = hashlib.sha256(api_key.encode()).hexdigest()
if not await db.verify_api_key(hashed_key):
raise HTTPException( status_code = 403 , detail = "Invalid API key" )
return api_key
@app.get ( "/api/data" )
async def get_data ( api_key : str = Security(verify_api_key)):
return { "data" : "protected" }
from fastapi import FastAPI
from fastapi.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
class SecurityHeadersMiddleware ( BaseHTTPMiddleware ):
async def dispatch ( self , request , call_next ):
response = await call_next(request)
# Prevent clickjacking
response.headers[ "X-Frame-Options" ] = "DENY"
# Prevent MIME sniffing
response.headers[ "X-Content-Type-Options" ] = "nosniff"
# Enable XSS filter
response.headers[ "X-XSS-Protection" ] = "1; mode=block"
# Strict Transport Security (force HTTPS)
response.headers[ "Strict-Transport-Security" ] = "max-age=31536000; includeSubDomains"
# Referrer policy
response.headers[ "Referrer-Policy" ] = "strict-origin-when-cross-origin"
# Permissions policy
response.headers[ "Permissions-Policy" ] = "geolocation=(), microphone=()"
return response
Secrets Management
Environment Variables (Basic)
import os
from pydantic_settings import BaseSettings
class Settings ( BaseSettings ):
database_url: str
jwt_secret: str
api_key: str
class Config :
env_file = ".env"
env_file_encoding = "utf-8"
# Never commit .env to version control!
# .gitignore should include .env
HashiCorp Vault (Production)
import hvac
client = hvac.Client( url = 'https://vault.example.com' )
client.auth.kubernetes.login( role = 'my-app' , jwt = service_account_token)
# Read secrets
secret = client.secrets.kv.v2.read_secret_version( path = 'myapp/config' )
db_password = secret[ 'data' ][ 'data' ][ 'password' ]
# Dynamic database credentials
creds = client.secrets.database.generate_credentials( name = 'myapp-db' )
# Credentials auto-expire and rotate
Penetration Testing Checklist
Security Checklist
Hash passwords with bcrypt/Argon2 (cost factor ≥12)
Implement MFA for sensitive operations
Use secure session management (HttpOnly, Secure, SameSite cookies)
Rate limit login attempts
Implement secure password reset (time-limited tokens)
Consider passwordless authentication (WebAuthn)
Implement least privilege principle
Check permissions server-side (never trust client)
Use role-based access control (RBAC)
Audit access to sensitive resources
Implement proper multi-tenancy isolation
Encrypt sensitive data at rest (AES-256)
Use HTTPS everywhere (TLS 1.2+)
Never log sensitive data (passwords, tokens, PII)
Implement proper key management and rotation
Use secure random number generation
Implement data retention and deletion policies
Keep systems and dependencies updated
Use WAF (Web Application Firewall)
Implement network segmentation
Enable audit logging
Regular security scanning and penetration testing
Implement DDoS protection
Common Vulnerabilities Quick Reference
Vulnerability Attack Vector Prevention SQL Injection Malicious SQL in input Parameterized queries XSS Malicious scripts in pages Output encoding, CSP CSRF Forged requests CSRF tokens, SameSite cookies SSRF Server-side request to internal Allowlist URLs, validate input XXE Malicious XML entities Disable DTD processing Path Traversal ../ in file pathsValidate and sanitize paths Insecure Deserialization Malicious serialized data Avoid deserializing untrusted data
Security is everyone’s job. Don’t assume “someone else will handle security.” Build it in from the start. Security is not a feature—it’s a requirement.
Interview Tip : Be prepared to discuss real security incidents you’ve handled, how you stay updated on security threats, and your experience with security tools and practices.