Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write clean, Pythonic code โจ
๐ฏ Introduction
Welcome to this critical tutorial on input validation in Python! ๐ In this guide, weโll explore how to protect your applications from malicious input and ensure data integrity.
Youโll discover how proper input validation can be the difference between a secure application and a security breach! Whether youโre building web applications ๐, APIs ๐, or command-line tools ๐ป, understanding input validation is essential for writing secure, robust code.
By the end of this tutorial, youโll feel confident implementing bulletproof input validation in your Python projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding Input Validation
๐ค What is Input Validation?
Input validation is like a bouncer at a club ๐บ - it checks everyone at the door and only lets in the good ones! Think of it as a security checkpoint โ that examines all incoming data before it enters your application.
In Python terms, input validation is the process of ensuring that user-provided data meets specific criteria before processing it. This means you can:
- โจ Prevent security vulnerabilities like SQL injection
- ๐ Ensure data integrity and consistency
- ๐ก๏ธ Protect against application crashes and unexpected behavior
๐ก Why Use Input Validation?
Hereโs why input validation is non-negotiable:
- Security First ๐: Prevent injection attacks and malicious code execution
- Data Quality ๐: Ensure only valid data enters your system
- User Experience ๐: Provide clear feedback for incorrect input
- System Stability ๐๏ธ: Prevent crashes from unexpected data
Real-world example: Imagine a banking application ๐ฆ. Without input validation, someone could enter negative amounts or SQL commands instead of numbers - catastrophic! ๐ฅ
๐ง Basic Syntax and Usage
๐ Simple Example
Letโs start with basic validation techniques:
# ๐ Hello, secure Python!
def validate_age(age_input):
"""๐ Validate age input"""
try:
# ๐ Convert to integer
age = int(age_input)
# โ
Check valid range
if 0 <= age <= 150:
return age
else:
raise ValueError("Age must be between 0 and 150")
except ValueError as e:
# โ Invalid input
raise ValueError(f"Invalid age: {str(e)}")
# ๐ฎ Test it out!
try:
user_age = validate_age("25")
print(f"โ
Valid age: {user_age}")
except ValueError as e:
print(f"โ Error: {e}")
๐ก Explanation: Notice how we validate both the type (must be convertible to int) and the range (0-150). Always validate both format and business rules!
๐ฏ Common Validation Patterns
Here are patterns youโll use daily:
import re
from datetime import datetime
# ๐๏ธ Pattern 1: Email validation
def validate_email(email):
"""๐ง Validate email format"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(pattern, email):
return email.lower() # ๐จ Normalize to lowercase
raise ValueError("Invalid email format")
# ๐จ Pattern 2: Phone number validation
def validate_phone(phone):
"""๐ฑ Validate phone number"""
# Remove spaces and dashes
cleaned = re.sub(r'[\s\-\(\)]', '', phone)
# Check if it's digits only and correct length
if cleaned.isdigit() and 10 <= len(cleaned) <= 15:
return cleaned
raise ValueError("Invalid phone number")
# ๐ Pattern 3: Date validation
def validate_date(date_string, format='%Y-%m-%d'):
"""๐
Validate date string"""
try:
return datetime.strptime(date_string, format)
except ValueError:
raise ValueError(f"Invalid date format. Expected: {format}")
๐ก Practical Examples
๐ Example 1: E-commerce Input Validation
Letโs build a secure product order system:
import re
from decimal import Decimal
from typing import Dict, List
class OrderValidator:
"""๐๏ธ Validates e-commerce order data"""
def __init__(self):
self.errors: List[str] = []
def validate_product_name(self, name: str) -> str:
"""๐ฆ Validate product name"""
# ๐ก๏ธ Remove whitespace
name = name.strip()
# โ
Check length
if not name:
raise ValueError("Product name cannot be empty")
if len(name) > 100:
raise ValueError("Product name too long (max 100 chars)")
# ๐ Check for invalid characters
if re.search(r'[<>\"\'&]', name):
raise ValueError("Product name contains invalid characters")
return name
def validate_quantity(self, quantity: str) -> int:
"""๐ข Validate quantity"""
try:
qty = int(quantity)
if qty <= 0:
raise ValueError("Quantity must be positive")
if qty > 1000:
raise ValueError("Quantity too large (max 1000)")
return qty
except ValueError:
raise ValueError("Invalid quantity format")
def validate_price(self, price: str) -> Decimal:
"""๐ฐ Validate price"""
try:
# ๐ฏ Use Decimal for money!
price_decimal = Decimal(str(price))
if price_decimal <= 0:
raise ValueError("Price must be positive")
if price_decimal > 1000000:
raise ValueError("Price too high")
# ๐ก Round to 2 decimal places
return price_decimal.quantize(Decimal('0.01'))
except:
raise ValueError("Invalid price format")
def validate_credit_card(self, card_number: str) -> str:
"""๐ณ Validate credit card number (basic)"""
# ๐งน Remove spaces and dashes
card = re.sub(r'[\s\-]', '', card_number)
# โ
Check if all digits
if not card.isdigit():
raise ValueError("Card number must contain only digits")
# ๐ Check length (simplified)
if len(card) not in [15, 16]: # Amex or others
raise ValueError("Invalid card number length")
# ๐ Luhn algorithm check
if not self._luhn_check(card):
raise ValueError("Invalid card number")
# ๐ญ Mask for security (show only last 4)
return f"****-****-****-{card[-4:]}"
def _luhn_check(self, card_number: str) -> bool:
"""๐ Luhn algorithm for card validation"""
def digits_of(n):
return [int(d) for d in str(n)]
digits = digits_of(card_number)
odd_digits = digits[-1::-2]
even_digits = digits[-2::-2]
checksum = sum(odd_digits)
for d in even_digits:
checksum += sum(digits_of(d*2))
return checksum % 10 == 0
def validate_order(self, order_data: Dict) -> Dict:
"""๐ Validate complete order"""
validated = {}
self.errors = []
# ๐ฏ Validate each field
try:
validated['product'] = self.validate_product_name(
order_data.get('product', '')
)
except ValueError as e:
self.errors.append(f"Product: {e}")
try:
validated['quantity'] = self.validate_quantity(
order_data.get('quantity', '')
)
except ValueError as e:
self.errors.append(f"Quantity: {e}")
try:
validated['price'] = self.validate_price(
order_data.get('price', '')
)
except ValueError as e:
self.errors.append(f"Price: {e}")
if self.errors:
raise ValueError(f"Validation failed: {'; '.join(self.errors)}")
return validated
# ๐ฎ Let's use it!
validator = OrderValidator()
# โ
Valid order
try:
order = validator.validate_order({
'product': 'Python Book ๐',
'quantity': '2',
'price': '29.99'
})
print("โ
Order validated:", order)
except ValueError as e:
print(f"โ Validation error: {e}")
# โ Invalid order
try:
order = validator.validate_order({
'product': '<script>alert("hack")</script>',
'quantity': '-5',
'price': 'free'
})
except ValueError as e:
print(f"โ Validation error: {e}")
๐ฏ Try it yourself: Add validation for shipping address and email!
๐ฎ Example 2: User Registration System
Letโs make a secure user registration validator:
import re
import hashlib
from typing import Dict
class UserRegistrationValidator:
"""๐ค Secure user registration validation"""
# ๐ซ Common weak passwords
WEAK_PASSWORDS = [
'password', '123456', 'password123', 'admin',
'letmein', 'welcome', 'monkey', '1234567890'
]
def validate_username(self, username: str) -> str:
"""๐ท๏ธ Validate username"""
username = username.strip().lower()
# ๐ Length check
if len(username) < 3:
raise ValueError("Username too short (min 3 chars)")
if len(username) > 20:
raise ValueError("Username too long (max 20 chars)")
# ๐ค Character check
if not re.match(r'^[a-z0-9_]+$', username):
raise ValueError("Username can only contain letters, numbers, and underscores")
# ๐ซ Reserved names
reserved = ['admin', 'root', 'system', 'guest']
if username in reserved:
raise ValueError("Username is reserved")
return username
def validate_password(self, password: str) -> str:
"""๐ Validate password strength"""
# ๐ Length check
if len(password) < 8:
raise ValueError("Password too short (min 8 chars)")
# ๐ช Strength requirements
checks = {
'uppercase': r'[A-Z]',
'lowercase': r'[a-z]',
'digit': r'\d',
'special': r'[!@#$%^&*(),.?":{}|<>]'
}
missing = []
for check_name, pattern in checks.items():
if not re.search(pattern, password):
missing.append(check_name)
if missing:
raise ValueError(f"Password must contain: {', '.join(missing)}")
# ๐ซ Check common passwords
if password.lower() in self.WEAK_PASSWORDS:
raise ValueError("Password is too common")
# โ
Return hashed password (never store plain text!)
return self._hash_password(password)
def _hash_password(self, password: str) -> str:
"""๐ Hash password securely"""
# ๐ฒ In production, use bcrypt or argon2!
return hashlib.sha256(password.encode()).hexdigest()
def validate_age(self, age: str) -> int:
"""๐ Validate user age"""
try:
age_int = int(age)
if age_int < 13:
raise ValueError("Must be 13 or older to register")
if age_int > 120:
raise ValueError("Invalid age")
return age_int
except ValueError:
raise ValueError("Invalid age format")
def validate_security_question(self, question: str, answer: str) -> Dict[str, str]:
"""๐ Validate security Q&A"""
# ๐งน Clean inputs
question = question.strip()
answer = answer.strip()
if len(question) < 10:
raise ValueError("Security question too short")
if len(answer) < 3:
raise ValueError("Security answer too short")
# ๐ก๏ธ Check for sensitive info in answer
sensitive_patterns = [
r'\b\d{3}-\d{2}-\d{4}\b', # SSN
r'\b\d{16}\b', # Credit card
r'password', # Literal password
]
for pattern in sensitive_patterns:
if re.search(pattern, answer, re.IGNORECASE):
raise ValueError("Security answer contains sensitive information")
return {
'question': question,
'answer': self._hash_password(answer) # Hash the answer too!
}
# ๐ฎ Test the validator
validator = UserRegistrationValidator()
# โ
Good registration
try:
user_data = {
'username': validator.validate_username('python_dev'),
'password': validator.validate_password('Secure#Pass123'),
'age': validator.validate_age('25'),
'security': validator.validate_security_question(
"What's your pet's name?",
"Fluffy"
)
}
print("โ
Registration valid! ๐")
except ValueError as e:
print(f"โ Registration error: {e}")
# โ Bad registration attempts
bad_attempts = [
{'username': 'a'}, # Too short
{'password': '12345'}, # Too weak
{'username': '<script>alert("xss")</script>'}, # Invalid chars
]
for attempt in bad_attempts:
try:
if 'username' in attempt:
validator.validate_username(attempt['username'])
if 'password' in attempt:
validator.validate_password(attempt['password'])
except ValueError as e:
print(f"โ Caught: {e}")
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Custom Validation Decorators
When youโre ready to level up, try validation decorators:
from functools import wraps
from typing import Callable, Any
def validate_input(**validators):
"""๐ฏ Decorator for input validation"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
# ๐ Get function arguments
import inspect
sig = inspect.signature(func)
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
# โ
Validate each argument
for param_name, validator in validators.items():
if param_name in bound.arguments:
try:
validated = validator(bound.arguments[param_name])
bound.arguments[param_name] = validated
except ValueError as e:
raise ValueError(f"{param_name}: {e}")
return func(**bound.arguments)
return wrapper
return decorator
# ๐จ Create reusable validators
def positive_int(value: Any) -> int:
"""๐ข Validate positive integer"""
num = int(value)
if num <= 0:
raise ValueError("Must be positive")
return num
def non_empty_string(value: Any) -> str:
"""๐ Validate non-empty string"""
text = str(value).strip()
if not text:
raise ValueError("Cannot be empty")
return text
# ๐ Use the decorator!
@validate_input(
name=non_empty_string,
age=positive_int
)
def create_user(name: str, age: int) -> Dict:
"""๐ค Create user with validation"""
return {
'name': name,
'age': age,
'created': True
}
# ๐ฎ Test it
try:
user = create_user("Alice", "25")
print("โ
User created:", user)
user = create_user("", "-5") # Will fail!
except ValueError as e:
print(f"โ Validation failed: {e}")
๐๏ธ Advanced Topic 2: Schema-Based Validation
For complex data structures, use schema validation:
from typing import Dict, Any, List
import json
class Schema:
"""๐๏ธ Schema-based validation"""
def __init__(self, schema: Dict[str, Any]):
self.schema = schema
def validate(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""โ
Validate data against schema"""
validated = {}
errors = []
for field, rules in self.schema.items():
# ๐ Check required fields
if rules.get('required', False) and field not in data:
errors.append(f"{field}: Required field missing")
continue
if field in data:
value = data[field]
# ๐ฏ Type validation
expected_type = rules.get('type')
if expected_type and not isinstance(value, expected_type):
errors.append(f"{field}: Expected {expected_type.__name__}")
continue
# ๐ Length validation
if 'min_length' in rules and len(value) < rules['min_length']:
errors.append(f"{field}: Too short (min {rules['min_length']})")
if 'max_length' in rules and len(value) > rules['max_length']:
errors.append(f"{field}: Too long (max {rules['max_length']})")
# ๐ข Range validation
if 'min_value' in rules and value < rules['min_value']:
errors.append(f"{field}: Below minimum ({rules['min_value']})")
if 'max_value' in rules and value > rules['max_value']:
errors.append(f"{field}: Above maximum ({rules['max_value']})")
# ๐จ Pattern validation
if 'pattern' in rules:
import re
if not re.match(rules['pattern'], str(value)):
errors.append(f"{field}: Invalid format")
# ๐ Enum validation
if 'enum' in rules and value not in rules['enum']:
errors.append(f"{field}: Must be one of {rules['enum']}")
# โ
Custom validator
if 'validator' in rules:
try:
value = rules['validator'](value)
except ValueError as e:
errors.append(f"{field}: {e}")
continue
validated[field] = value
if errors:
raise ValueError(f"Validation errors: {'; '.join(errors)}")
return validated
# ๐ฎ Define a complex schema
user_schema = Schema({
'username': {
'type': str,
'required': True,
'min_length': 3,
'max_length': 20,
'pattern': r'^[a-zA-Z0-9_]+$'
},
'email': {
'type': str,
'required': True,
'pattern': r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
},
'age': {
'type': int,
'required': True,
'min_value': 0,
'max_value': 150
},
'role': {
'type': str,
'enum': ['user', 'admin', 'moderator'],
'required': False
},
'bio': {
'type': str,
'max_length': 500,
'required': False
}
})
# ๐ Validate complex data
try:
valid_user = user_schema.validate({
'username': 'python_master',
'email': '[email protected]',
'age': 28,
'role': 'admin',
'bio': 'I love Python! ๐'
})
print("โ
Valid user data:", valid_user)
except ValueError as e:
print(f"โ Schema validation failed: {e}")
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Trusting Client-Side Validation
# โ Wrong way - relying only on frontend validation
def process_payment(amount):
# ๐ฅ No server-side validation!
charge_credit_card(amount)
# โ
Correct way - always validate server-side!
def process_payment(amount):
# ๐ก๏ธ Validate on server
try:
amount_decimal = Decimal(str(amount))
if amount_decimal <= 0 or amount_decimal > 10000:
raise ValueError("Invalid amount")
# โ
Safe to process
charge_credit_card(amount_decimal)
except (ValueError, TypeError):
raise ValueError("Invalid payment amount")
๐คฏ Pitfall 2: SQL Injection Through Poor Validation
# โ Dangerous - string concatenation with user input
def get_user(username):
query = f"SELECT * FROM users WHERE username = '{username}'"
# ๐ฅ SQL injection vulnerability!
return execute_query(query)
# โ
Safe - parameterized queries + validation
def get_user(username):
# ๐ก๏ธ Validate first
if not re.match(r'^[a-zA-Z0-9_]+$', username):
raise ValueError("Invalid username")
# โ
Then use parameterized query
query = "SELECT * FROM users WHERE username = ?"
return execute_query(query, (username,))
๐ฃ Pitfall 3: Incomplete Sanitization
# โ Incomplete - only checks for <script>
def sanitize_html(text):
return text.replace('<script>', '').replace('</script>', '')
# โ
Complete - proper HTML escaping
import html
def sanitize_html(text):
# ๐ก๏ธ Escape all HTML entities
return html.escape(text)
# ๐ฏ Or use a proper library
from markupsafe import Markup, escape
def safe_display(user_input):
# โจ Automatically escapes dangerous content
return Markup('<p>{}</p>').format(escape(user_input))
๐ ๏ธ Best Practices
- ๐ฏ Validate Early and Often: Check input at every entry point
- ๐ Whitelist Over Blacklist: Define whatโs allowed, not whatโs forbidden
- ๐ก๏ธ Defense in Depth: Multiple layers of validation
- ๐จ Clear Error Messages: Help users fix their input
- โจ Sanitize for Context: Different rules for HTML, SQL, URLs, etc.
- ๐ Never Trust User Input: Always assume input is malicious
- ๐ Log Validation Failures: Monitor for attack patterns
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Secure API Input Validator
Create a comprehensive API input validation system:
๐ Requirements:
- โ Validate JSON request bodies
- ๐ท๏ธ Support nested object validation
- ๐ค Rate limiting per user
- ๐ Timestamp validation
- ๐จ Custom error responses
- ๐ API key validation
- ๐ Validation metrics logging
๐ Bonus Points:
- Add request size limits
- Implement field-level encryption markers
- Create reusable validation middleware
๐ก Solution
๐ Click to see solution
import json
import time
import hashlib
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
from functools import wraps
class APIValidator:
"""๐ Comprehensive API input validation"""
def __init__(self):
self.rate_limits: Dict[str, list] = {}
self.api_keys = {
'test_key_123': 'test_user',
'prod_key_456': 'prod_user'
}
self.validation_metrics = {
'total_requests': 0,
'failed_validations': 0,
'rate_limit_hits': 0
}
def validate_api_key(self, api_key: str) -> str:
"""๐ Validate API key"""
if not api_key:
raise ValueError("API key required")
user = self.api_keys.get(api_key)
if not user:
self.validation_metrics['failed_validations'] += 1
raise ValueError("Invalid API key")
return user
def check_rate_limit(self, user: str, limit: int = 10, window: int = 60) -> None:
"""โฑ๏ธ Check rate limiting"""
now = time.time()
# ๐งน Clean old entries
if user in self.rate_limits:
self.rate_limits[user] = [
t for t in self.rate_limits[user]
if now - t < window
]
else:
self.rate_limits[user] = []
# ๐ฆ Check limit
if len(self.rate_limits[user]) >= limit:
self.validation_metrics['rate_limit_hits'] += 1
raise ValueError(f"Rate limit exceeded ({limit} requests per {window}s)")
self.rate_limits[user].append(now)
def validate_timestamp(self, timestamp: str, max_age_minutes: int = 5) -> datetime:
"""๐ Validate timestamp freshness"""
try:
# ๐
Parse ISO format
ts = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
# โฐ Check if too old
age = datetime.now() - ts
if age > timedelta(minutes=max_age_minutes):
raise ValueError("Request timestamp too old")
# ๐ฎ Check if in future
if ts > datetime.now() + timedelta(minutes=1):
raise ValueError("Request timestamp in future")
return ts
except:
raise ValueError("Invalid timestamp format")
def validate_request_size(self, data: Any, max_size_kb: int = 1024) -> None:
"""๐ Validate request size"""
size = len(json.dumps(data).encode('utf-8'))
if size > max_size_kb * 1024:
raise ValueError(f"Request too large ({size} bytes)")
def validate_nested_object(self, data: Dict, schema: Dict) -> Dict:
"""๐๏ธ Validate nested objects"""
validated = {}
for field, rules in schema.items():
if field not in data and rules.get('required', False):
raise ValueError(f"Missing required field: {field}")
if field in data:
value = data[field]
# ๐ฏ Nested object validation
if 'object_schema' in rules:
validated[field] = self.validate_nested_object(
value, rules['object_schema']
)
elif 'array_schema' in rules:
validated[field] = [
self.validate_nested_object(item, rules['array_schema'])
for item in value
]
else:
# ๐ Simple field validation
validated[field] = self._validate_field(field, value, rules)
return validated
def _validate_field(self, name: str, value: Any, rules: Dict) -> Any:
"""๐ Validate individual field"""
# Type check
if 'type' in rules and not isinstance(value, rules['type']):
raise ValueError(f"{name}: Invalid type")
# String validations
if isinstance(value, str):
if 'max_length' in rules and len(value) > rules['max_length']:
raise ValueError(f"{name}: Too long")
if 'pattern' in rules:
import re
if not re.match(rules['pattern'], value):
raise ValueError(f"{name}: Invalid format")
return value
def validate_api_request(self, request: Dict) -> Dict:
"""๐ Complete API request validation"""
self.validation_metrics['total_requests'] += 1
try:
# ๐ Validate API key
user = self.validate_api_key(request.get('api_key', ''))
# โฑ๏ธ Check rate limit
self.check_rate_limit(user)
# ๐ Validate timestamp
self.validate_timestamp(request.get('timestamp', ''))
# ๐ Check request size
self.validate_request_size(request)
# ๐๏ธ Validate request body
body_schema = {
'action': {
'type': str,
'required': True,
'pattern': r'^[a-z_]+$'
},
'data': {
'type': dict,
'required': True,
'object_schema': {
'user_id': {
'type': str,
'required': True,
'pattern': r'^[a-zA-Z0-9-]+$'
},
'amount': {
'type': (int, float),
'required': False
}
}
}
}
validated_body = self.validate_nested_object(
request.get('body', {}),
body_schema
)
return {
'user': user,
'body': validated_body,
'validated_at': datetime.now().isoformat()
}
except ValueError as e:
self.validation_metrics['failed_validations'] += 1
raise
# ๐ฎ Test the API validator
validator = APIValidator()
# โ
Valid request
valid_request = {
'api_key': 'test_key_123',
'timestamp': datetime.now().isoformat(),
'body': {
'action': 'create_order',
'data': {
'user_id': 'user-123',
'amount': 99.99
}
}
}
try:
result = validator.validate_api_request(valid_request)
print("โ
Request validated:", result)
except ValueError as e:
print(f"โ Validation failed: {e}")
# ๐ Check metrics
print("\n๐ Validation Metrics:")
for metric, value in validator.validation_metrics.items():
print(f" {metric}: {value}")
# ๐ก๏ธ Middleware decorator
def validate_api(validator: APIValidator):
"""๐ฏ API validation decorator"""
def decorator(func):
@wraps(func)
def wrapper(request: Dict):
try:
validated = validator.validate_api_request(request)
return func(validated)
except ValueError as e:
return {
'error': str(e),
'status': 'validation_failed'
}
return wrapper
return decorator
# ๐ Use the decorator
@validate_api(validator)
def process_order(validated_request: Dict) -> Dict:
"""๐ฐ Process validated order"""
return {
'status': 'success',
'order_id': 'ORD-' + str(time.time()),
'user': validated_request['user']
}
# Test the decorated function
result = process_order(valid_request)
print("\n๐ฏ Decorated function result:", result)
๐ Key Takeaways
Youโve learned essential security skills! Hereโs what you can now do:
- โ Implement comprehensive input validation with confidence ๐ช
- โ Prevent common security vulnerabilities like injection attacks ๐ก๏ธ
- โ Build robust validation systems for any application ๐ฏ
- โ Create reusable validation components like a pro ๐
- โ Secure your Python applications against malicious input! ๐
Remember: Input validation is your first line of defense. Never trust user input, always validate! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered input validation security!
Hereโs what to do next:
- ๐ป Practice with the exercises above
- ๐๏ธ Implement validation in your existing projects
- ๐ Learn about output encoding and CSRF protection
- ๐ Share your secure coding practices with others!
Remember: Security is not a feature, itโs a requirement. Keep your applications safe, and most importantly, keep learning! ๐
Happy secure coding! ๐๐โจ