Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on Type Safety for Security! ๐ In this guide, weโll explore how TypeScriptโs powerful type system can help prevent security vulnerabilities in your applications.
Youโll discover how proper typing can protect against common security issues like injection attacks, unauthorized access, and data validation errors. Whether youโre building web applications ๐, APIs ๐ฅ๏ธ, or handling sensitive data ๐, understanding type safety for security is essential for writing robust, secure code.
By the end of this tutorial, youโll feel confident using TypeScript as your first line of defense against security vulnerabilities! Letโs dive in! ๐โโ๏ธ
๐ Understanding Type Safety for Security
๐ค What is Type Safety for Security?
Type safety for security is like having a security guard ๐ฎ at every door of your application. Think of it as a protective shield ๐ก๏ธ that validates and controls what data can enter and flow through your system.
In TypeScript terms, it means using the type system to enforce security constraints at compile-time. This means you can:
- โจ Prevent injection attacks before they happen
- ๐ Validate user input at the type level
- ๐ก๏ธ Control access to sensitive data through types
๐ก Why Use Type Safety for Security?
Hereโs why developers love using types for security:
- Compile-Time Protection ๐: Catch security issues before runtime
- Input Validation ๐ป: Enforce data constraints automatically
- Clear Security Boundaries ๐: Types document security requirements
- Reduced Attack Surface ๐ง: Minimize opportunities for exploitation
Real-world example: Imagine building a banking app ๐ฆ. With type safety, you can ensure that account numbers are always validated, amounts are positive numbers, and user roles are properly enforced.
๐ง Basic Syntax and Usage
๐ Simple Example
Letโs start with a friendly example:
// ๐ Hello, secure TypeScript!
type SafeString = string & { __brand: 'SafeString' };
// ๐จ Creating a validation function
const validateInput = (input: string): SafeString => {
// ๐ก๏ธ Remove dangerous characters
const sanitized = input.replace(/[<>'"]/g, '');
return sanitized as SafeString;
};
// ๐ Secure user type
interface SecureUser {
id: string; // ๐ค User identifier
username: SafeString; // ๐ฏ Validated username
role: 'user' | 'admin'; // ๐ Restricted roles
}
๐ก Explanation: Notice how we use branded types to ensure strings are validated! The SafeString
type guarantees that the string has been sanitized.
๐ฏ Common Security Patterns
Here are patterns youโll use for security:
// ๐๏ธ Pattern 1: SQL Query Protection
type SQLQuery = string & { __brand: 'SQLQuery' };
const createSafeQuery = (query: string, params: any[]): SQLQuery => {
// ๐ก๏ธ Use parameterized queries
const safeQuery = query.replace(/\?/g, () => {
const param = params.shift();
return typeof param === 'string'
? `'${param.replace(/'/g, "''")}'`
: param;
});
return safeQuery as SQLQuery;
};
// ๐จ Pattern 2: Sensitive Data Protection
type SensitiveData<T> = {
value: T;
accessLevel: 'public' | 'private' | 'restricted';
};
// ๐ Pattern 3: Input Validation Types
type Email = string & { __brand: 'Email' };
type Password = string & { __brand: 'Password' };
const validateEmail = (input: string): Email | null => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(input) ? input as Email : null;
};
๐ก Practical Examples
๐ Example 1: Secure E-Commerce API
Letโs build something real:
// ๐๏ธ Define secure product types
interface SecureProduct {
id: string;
name: SafeString;
price: number;
category: 'electronics' | 'clothing' | 'food';
}
// ๐ณ Secure payment information
type CreditCardNumber = string & { __brand: 'CreditCardNumber' };
type CVV = string & { __brand: 'CVV' };
interface SecurePayment {
cardNumber: CreditCardNumber;
cvv: CVV;
amount: number;
}
// ๐ Secure shopping cart with validation
class SecureShoppingCart {
private items: SecureProduct[] = [];
private userId: string;
constructor(userId: string) {
// ๐ Validate user ID format
if (!/^[a-zA-Z0-9]{8,}$/.test(userId)) {
throw new Error('Invalid user ID format');
}
this.userId = userId;
}
// โ Add item with validation
addItem(product: SecureProduct): void {
// ๐ก๏ธ Validate price is positive
if (product.price <= 0) {
throw new Error('Invalid product price');
}
this.items.push(product);
console.log(`โ
Added ${product.name} to secure cart!`);
}
// ๐ฐ Process payment securely
processPayment(payment: SecurePayment): boolean {
// ๐ Validate payment amount matches cart total
const total = this.getTotal();
if (Math.abs(payment.amount - total) > 0.01) {
console.log('โ Payment amount mismatch!');
return false;
}
console.log('โ
Payment processed securely!');
return true;
}
// ๐ Get total with validation
private getTotal(): number {
return this.items.reduce((sum, item) => {
// ๐ก๏ธ Extra validation during calculation
if (item.price < 0) {
throw new Error('Corrupted price detected!');
}
return sum + item.price;
}, 0);
}
}
// ๐ฎ Let's use it securely!
const cart = new SecureShoppingCart('user12345678');
const safeProductName = validateInput('TypeScript Book');
cart.addItem({
id: '1',
name: safeProductName,
price: 29.99,
category: 'electronics'
});
๐ฏ Try it yourself: Add role-based access control to restrict certain operations!
๐ฎ Example 2: Secure User Authentication System
Letโs make authentication safe:
// ๐ Secure authentication types
type HashedPassword = string & { __brand: 'HashedPassword' };
type SessionToken = string & { __brand: 'SessionToken' };
type UserId = string & { __brand: 'UserId' };
// ๐ค User with security constraints
interface SecureUserAccount {
id: UserId;
email: Email;
passwordHash: HashedPassword;
roles: readonly ('user' | 'admin' | 'moderator')[];
lastLogin?: Date;
failedAttempts: number;
}
// ๐ก๏ธ Authentication service
class SecureAuthService {
private users = new Map<Email, SecureUserAccount>();
private sessions = new Map<SessionToken, UserId>();
private readonly MAX_ATTEMPTS = 3;
// ๐ Hash password (simulation)
private hashPassword(password: Password): HashedPassword {
// ๐ฏ In real app, use bcrypt or similar
return `hashed_${password}` as HashedPassword;
}
// ๐ Register with validation
async register(
email: Email,
password: Password,
roles: readonly ('user' | 'admin' | 'moderator')[] = ['user']
): Promise<UserId> {
// ๐ก๏ธ Check if user exists
if (this.users.has(email)) {
throw new Error('User already exists');
}
// ๐ Validate password strength
if (password.length < 8) {
throw new Error('Password too weak');
}
const userId = `user_${Date.now()}` as UserId;
const user: SecureUserAccount = {
id: userId,
email,
passwordHash: this.hashPassword(password),
roles,
failedAttempts: 0
};
this.users.set(email, user);
console.log('โ
User registered securely!');
return userId;
}
// ๐ Login with security checks
async login(email: Email, password: Password): Promise<SessionToken> {
const user = this.users.get(email);
if (!user) {
// ๐ซ Don't reveal if user exists
throw new Error('Invalid credentials');
}
// ๐ก๏ธ Check for account lockout
if (user.failedAttempts >= this.MAX_ATTEMPTS) {
throw new Error('Account locked due to failed attempts');
}
// ๐ Verify password
const hashedInput = this.hashPassword(password);
if (hashedInput !== user.passwordHash) {
user.failedAttempts++;
throw new Error('Invalid credentials');
}
// โ
Reset failed attempts and create session
user.failedAttempts = 0;
user.lastLogin = new Date();
const token = `session_${Date.now()}_${Math.random()}` as SessionToken;
this.sessions.set(token, user.id);
console.log('๐ Login successful!');
return token;
}
// ๐ Authorize action based on roles
authorize(
token: SessionToken,
requiredRole: 'user' | 'admin' | 'moderator'
): boolean {
const userId = this.sessions.get(token);
if (!userId) {
console.log('โ Invalid session');
return false;
}
const user = Array.from(this.users.values())
.find(u => u.id === userId);
if (!user) {
console.log('โ User not found');
return false;
}
const hasRole = user.roles.includes(requiredRole);
console.log(hasRole ? 'โ
Authorized!' : 'โ Insufficient permissions');
return hasRole;
}
}
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Template Literal Types for API Security
When youโre ready to level up, try this advanced pattern:
// ๐ฏ API endpoint security with template literals
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type SecureEndpoint = `/api/v1/${string}`;
// ๐ Role-based endpoint access
type EndpointPermissions = {
[K in SecureEndpoint]: {
methods: HTTPMethod[];
roles: ('user' | 'admin')[];
};
};
// ๐ก๏ธ Type-safe API router
class SecureAPIRouter {
private permissions: EndpointPermissions = {
'/api/v1/users': {
methods: ['GET', 'POST'],
roles: ['admin']
},
'/api/v1/profile': {
methods: ['GET', 'PUT'],
roles: ['user', 'admin']
}
} as EndpointPermissions;
// ๐ Check endpoint access
canAccess(
endpoint: SecureEndpoint,
method: HTTPMethod,
userRole: 'user' | 'admin'
): boolean {
const permission = this.permissions[endpoint];
return permission.methods.includes(method) &&
permission.roles.includes(userRole);
}
}
๐๏ธ Advanced Topic 2: Phantom Types for Data Classification
For the brave developers:
// ๐ Phantom types for data classification
interface Classified<T, Level> {
data: T;
_level?: Level;
}
// ๐ Security levels
type Public = { readonly brand: unique symbol };
type Confidential = { readonly brand: unique symbol };
type Secret = { readonly brand: unique symbol };
// ๐ก๏ธ Type-safe data handling
class DataClassifier {
// ๐ Only public data can be logged
logData<T>(data: Classified<T, Public>): void {
console.log('๐ข Public data:', data.data);
}
// ๐ Confidential data requires authentication
processConfidential<T>(
data: Classified<T, Confidential>,
authenticated: boolean
): T | null {
if (!authenticated) {
console.log('โ Authentication required!');
return null;
}
return data.data;
}
// ๐ Secret data requires special handling
handleSecret<T>(
data: Classified<T, Secret>,
handler: (data: T) => void
): void {
console.log('๐ Processing secret data...');
handler(data.data);
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Type Assertions Bypassing Security
// โ Wrong way - bypassing security checks!
const unsafeEmail = 'not-an-email' as Email;
const unsafeQuery = 'DROP TABLE users;' as SQLQuery;
// โ
Correct way - always validate!
const safeEmail = validateEmail('[email protected]');
if (!safeEmail) {
console.log('โ ๏ธ Invalid email format!');
}
// โ
Use validation functions
const safeQuery = createSafeQuery(
'SELECT * FROM users WHERE id = ?',
['user123']
);
๐คฏ Pitfall 2: Exposing Sensitive Type Information
// โ Dangerous - exposing internal structure!
type UserWithPassword = {
id: string;
email: string;
password: string; // ๐ฅ Never expose passwords!
};
// โ
Safe - separate public and private data!
type PublicUser = {
id: string;
email: string;
};
type PrivateUser = PublicUser & {
passwordHash: HashedPassword;
securityQuestions: string[];
};
// ๐ก๏ธ Safe conversion function
const toPublicUser = (user: PrivateUser): PublicUser => ({
id: user.id,
email: user.email
});
๐ ๏ธ Best Practices
- ๐ฏ Never Trust User Input: Always validate and sanitize!
- ๐ Use Branded Types: Create distinct types for validated data
- ๐ก๏ธ Principle of Least Privilege: Grant minimal necessary access
- ๐จ Separate Public/Private Types: Donโt expose sensitive fields
- โจ Fail Securely: Handle errors without revealing system details
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Secure API Key System
Create a type-safe API key management system:
๐ Requirements:
- โ API keys with different permission levels
- ๐ท๏ธ Rate limiting based on key type
- ๐ค Key rotation and expiration
- ๐ Usage tracking and analytics
- ๐จ Each key type needs different emoji!
๐ Bonus Points:
- Add IP whitelisting
- Implement key families (parent/child keys)
- Create audit logging system
๐ก Solution
๐ Click to see solution
// ๐ฏ Our type-safe API key system!
type APIKey = string & { __brand: 'APIKey' };
type KeyID = string & { __brand: 'KeyID' };
interface SecureAPIKey {
id: KeyID;
key: APIKey;
type: 'basic' | 'premium' | 'enterprise';
permissions: readonly string[];
rateLimit: number;
expiresAt: Date;
emoji: string;
ipWhitelist?: readonly string[];
}
class APIKeyManager {
private keys = new Map<APIKey, SecureAPIKey>();
private usage = new Map<KeyID, number>();
// ๐ Generate secure key
generateKey(
type: SecureAPIKey['type'],
permissions: readonly string[]
): SecureAPIKey {
const keyId = `key_${Date.now()}` as KeyID;
const apiKey = `${type}_${Math.random().toString(36).substr(2)}` as APIKey;
const keyConfig: SecureAPIKey = {
id: keyId,
key: apiKey,
type,
permissions,
rateLimit: this.getRateLimit(type),
expiresAt: this.getExpiration(type),
emoji: this.getEmoji(type)
};
this.keys.set(apiKey, keyConfig);
this.usage.set(keyId, 0);
console.log(`โ
Generated ${keyConfig.emoji} ${type} API key!`);
return keyConfig;
}
// ๐ก๏ธ Validate key with security checks
validateKey(
key: APIKey,
permission: string,
ipAddress?: string
): boolean {
const keyConfig = this.keys.get(key);
if (!keyConfig) {
console.log('โ Invalid API key');
return false;
}
// ๐
Check expiration
if (new Date() > keyConfig.expiresAt) {
console.log('โฐ API key expired');
return false;
}
// ๐ Check permissions
if (!keyConfig.permissions.includes(permission)) {
console.log('๐ซ Insufficient permissions');
return false;
}
// ๐ Check IP whitelist
if (keyConfig.ipWhitelist && ipAddress) {
if (!keyConfig.ipWhitelist.includes(ipAddress)) {
console.log('๐ซ IP not whitelisted');
return false;
}
}
// ๐ Check rate limit
const currentUsage = this.usage.get(keyConfig.id) || 0;
if (currentUsage >= keyConfig.rateLimit) {
console.log('๐ฆ Rate limit exceeded');
return false;
}
// โ
Update usage
this.usage.set(keyConfig.id, currentUsage + 1);
console.log(`โ
${keyConfig.emoji} Key validated!`);
return true;
}
// ๐ Rotate key
rotateKey(oldKey: APIKey): SecureAPIKey | null {
const oldConfig = this.keys.get(oldKey);
if (!oldConfig) return null;
const newKey = this.generateKey(oldConfig.type, oldConfig.permissions);
this.keys.delete(oldKey);
console.log('๐ Key rotated successfully!');
return newKey;
}
// ๐ Get usage stats
getUsageStats(keyId: KeyID): number {
return this.usage.get(keyId) || 0;
}
private getRateLimit(type: SecureAPIKey['type']): number {
const limits = {
basic: 100,
premium: 1000,
enterprise: 10000
};
return limits[type];
}
private getExpiration(type: SecureAPIKey['type']): Date {
const days = {
basic: 30,
premium: 90,
enterprise: 365
};
const expiry = new Date();
expiry.setDate(expiry.getDate() + days[type]);
return expiry;
}
private getEmoji(type: SecureAPIKey['type']): string {
const emojis = {
basic: '๐',
premium: '๐๏ธ',
enterprise: '๐'
};
return emojis[type];
}
}
// ๐ฎ Test it out!
const keyManager = new APIKeyManager();
const apiKey = keyManager.generateKey('premium', ['read', 'write']);
keyManager.validateKey(apiKey.key, 'read', '192.168.1.1');
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create secure types with validation ๐ช
- โ Prevent injection attacks using type safety ๐ก๏ธ
- โ Implement access control at the type level ๐ฏ
- โ Validate user input before it enters your system ๐
- โ Build secure applications with TypeScript! ๐
Remember: TypeScriptโs type system is your security ally! Use it to build a fortress around your application. ๐ฐ
๐ค Next Steps
Congratulations! ๐ Youโve mastered type safety for security!
Hereโs what to do next:
- ๐ป Practice with the API key exercise above
- ๐๏ธ Audit your existing code for security improvements
- ๐ Move on to our next tutorial: Input Validation Strategies and Patterns
- ๐ Share your secure coding journey with others!
Remember: Every security expert started by learning the basics. Keep coding securely, keep learning, and most importantly, stay vigilant! ๐
Happy secure coding! ๐๐โจ