Security Best Practices β
This comprehensive guide covers security best practices for VueSip applications, helping you protect your VoIP communications at every layerβfrom encrypted transport connections to secure credential storage.
Overview β
Why Security Matters in VoIP
VoIP applications handle sensitive communications and user credentials. Without proper security measures, attackers could intercept calls, steal credentials, or impersonate users. VueSip implements defense-in-depth with multiple security layers working together to protect your application.
VueSip's Multi-Layer Security Architecture:
- π Transport Security (WSS/TLS) - Encrypts SIP signaling messages during transmission to prevent eavesdropping
- π€ Media Encryption (DTLS-SRTP) - End-to-end encryption for actual voice/video data streams
- π Credential Storage - AES-GCM encryption protects sensitive data when stored on the device
- βοΈ Input Validation - Prevents injection attacks and ensures data integrity
- π‘οΈ Authentication - SIP Digest authentication verifies user identity securely
Each layer protects a different aspect of your application, and together they create a robust security posture.
Quick Start: Secure Configuration β
Want to get secure quickly? Here's a minimal security setup that implements all essential protections. Copy this configuration and customize the values for your environment:
import { useSipClient, LocalStorageAdapter, generateEncryptionKey } from 'vuesip'
// β
Production-ready secure SIP client configuration
async function initializeSecureClient() {
const { connect, register } = useSipClient({
// 1. Transport Security - Always use WSS (WebSocket Secure)
uri: 'wss://sip.example.com:7443', // β Encrypted WebSocket connection
// 2. Authentication - Use your SIP credentials
sipUri: 'sip:1000@example.com', // Your SIP identity
password: 'your-secure-password', // Or use HA1 hash (see Authentication section)
// 3. Media Security - DTLS-SRTP configuration with secure TURN
rtcConfiguration: {
// Option A: Use VueSip's convenient stunServers/turnServers helpers
stunServers: [
'stun:stun.l.google.com:19302',
'stun:stun1.l.google.com:19302'
],
turnServers: [
{
urls: ['turns:turn.example.com:5349'], // β TURN over TLS
username: 'turnuser',
credential: 'turnpass',
credentialType: 'password'
}
],
iceTransportPolicy: 'relay' // β Force traffic through TURN (highest security)
}
})
// Connect and register
await connect()
await register()
console.log('Secure SIP client initialized')
}
// Initialize the client
initializeSecureClient().catch(console.error)What this configuration does:
β
Encrypts transport - Uses WSS with TLS to protect SIP signaling β
Encrypts media - DTLS-SRTP automatically protects voice/video streams β
Encrypts storage - AES-GCM with PBKDF2 (100k iterations) protects stored credentials β
Uses secure TURN - Forces media through TURN over TLS (TURNS) β
Maximum security - iceTransportPolicy: 'relay' prevents IP address leaks
Alternative: Standard iceServers format
You can also use the standard WebRTC iceServers format instead of stunServers/turnServers:
rtcConfiguration: {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turns:turn.example.com:5349',
username: 'turnuser',
credential: 'turnpass'
}
],
iceTransportPolicy: 'relay'
}π‘ Tip: For even stronger security, use HA1 hash authentication instead of passwords (see Authentication section).
Table of Contents β
- Transport Security (WSS/TLS)
- Media Encryption (DTLS-SRTP)
- Credential Storage
- Authentication
- Input Validation
- Security Checklist
- Common Security Pitfalls
- Additional Resources
Transport Security (WSS/TLS) β
What This Section Covers
This section explains how to secure the SIP signaling channelβthe connection that carries messages like REGISTER, INVITE, and BYE between your application and the SIP server. Without encryption, these messages are visible to anyone monitoring the network.
Understanding WebSocket Secure (WSS) β
What is WSS? WebSocket Secure (WSS) is the encrypted version of the WebSocket protocol. It uses TLS (Transport Layer Security) to encrypt all data transmitted between your browser and the SIP server, similar to how HTTPS protects web traffic.
Why use WSS? SIP messages contain sensitive information including:
- Authentication credentials (during registration)
- Call details (who's calling whom)
- Session descriptions (IP addresses, media capabilities)
- Account information
Always use WebSocket Secure (WSS) in production environments to prevent this information from being intercepted.
β Recommended - Secure Connection:
import { useSipClient } from 'vuesip'
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443', // π WSS protocol = encrypted connection
sipUri: 'sip:1000@example.com', // Your SIP identity
password: 'your-password' // Sent securely over encrypted channel
})β Not Recommended - Insecure Connection:
// Only use ws:// for local development/testing on localhost
const { connect } = useSipClient({
uri: 'ws://localhost:8088', // β οΈ Unencrypted - anyone on network can see traffic!
sipUri: 'sip:1000@example.com',
password: 'your-password' // β οΈ Password exposed in cleartext!
})π Note: While ws:// (unencrypted) is acceptable for local development on localhost, never use it when connecting to remote servers or in production environments.
Automatic Production Environment Check β
VueSip includes a built-in safety mechanism that automatically detects and warns about insecure connections in production:
// VueSip internal check - happens automatically
if (config.uri.startsWith('ws://') && process.env.NODE_ENV === 'production') {
// You'll see this warning in the console if you accidentally use ws:// in production
console.warn('Using insecure WebSocket (ws://) in production. Use wss:// for secure connections.')
}π‘ Tip: Set NODE_ENV=production in your production builds to enable this check.
TLS Certificate Validation β
What are TLS Certificates? TLS certificates prove your SIP server's identity and enable encrypted connections. They're like a digital passport that verifies "this server is really sip.example.com."
Certificate Requirements:
β Use certificates from trusted Certificate Authorities (CAs)
- Let's Encrypt (free, automated)
- DigiCert, Sectigo, etc. (commercial options)
β Avoid self-signed certificates in production
- Browsers will show security warnings
- Man-in-the-middle attacks become easier
- Only use for internal development/testing
π Certificate Maintenance:
- Monitor certificate expiration dates (most expire after 90 days for Let's Encrypt)
- Set up automated renewal
- Test certificate renewal process in staging first
Transport Security Best Practices β
Follow these guidelines to ensure your SIP signaling remains secure:
- β
Always use WSS in production - Never use
ws://for production deployments where real users connect - β Use standard TLS ports - Port 443 (HTTPS/WSS) or 7443 (dedicated WSS) for easier firewall traversal
- β Validate server certificates - Ensure proper TLS certificate validation is enabled in browsers
- β Monitor connection security - Log connection attempts and alert on repeated failures
Media Encryption (DTLS-SRTP) β
What This Section Covers
While WSS protects SIP signaling messages, actual audio and video streams (the "media") take a different path through the network using WebRTC. This section explains how VueSip secures these media streams so your conversations remain private.
Understanding DTLS-SRTP Encryption β
Why Media Needs Separate Encryption
SIP signaling and media take different paths for performance reasons. SIP messages go through the SIP server, but media streams directly between participants (peer-to-peer) for lower latency. This means we need separate encryption for media.
How DTLS-SRTP Works:
VueSip uses WebRTC's built-in DTLS-SRTP for media encryption. This is a two-part system:
DTLS (Datagram Transport Layer Security) - First, devices securely exchange encryption keys
- Think of this as the "handshake" where both sides agree on how to encrypt
- Works over UDP for real-time performance
SRTP (Secure Real-time Transport Protocol) - Then, media streams are encrypted with those keys
- Encrypts the actual audio/video packets
- Adds integrity checks to detect tampering
Key Security Features:
- π Forward Secrecy - New encryption keys generated for each call (old calls can't be decrypted if keys are later compromised)
- π Mandatory Encryption - WebRTC enforces encryption by default (you can't accidentally disable it)
- π― End-to-End Protection - Media encrypted from your app to the other participant
π‘ Good News: WebRTC handles all the complex cryptography automatically. You just need to configure the connection properly!
RTCPeerConnection Security Configuration β
WebRTC automatically handles media encryption through RTCPeerConnection. VueSip configures this securely by default, but you can customize it:
// MediaManager automatically creates this configuration
// This example shows what VueSip does internally
const rtcConfiguration = {
iceServers: [
{
// STUN server helps find your public IP address for peer-to-peer connections
urls: ['stun:stun.l.google.com:19302'],
},
{
// TURN server relays traffic when direct connections aren't possible
urls: ['turn:turn.example.com:3478'],
username: 'your-username', // Credentials for TURN server
credential: 'your-credential', // Usually time-limited tokens
credentialType: 'password' // Type of credential being used
}
],
iceTransportPolicy: 'all', // 'all' = try direct first, use relay if needed
// 'relay' = force all traffic through TURN (more secure, higher latency)
bundlePolicy: 'balanced', // Bundle audio/video on same connection for efficiency
rtcpMuxPolicy: 'require' // Multiplex RTP and RTCP on same port (required by WebRTC)
}Understanding STUN and TURN Servers β
What Problem Do They Solve?
Most devices sit behind NAT (Network Address Translation) routers that hide their real IP addresses. STUN and TURN servers help WebRTC connections work despite NAT:
STUN (Session Traversal Utilities for NAT) - Helps you discover your public IP address
- Think of it as asking "what's my address?" to make direct connections possible
- Used at the start of each call
- Free public STUN servers available (like Google's)
TURN (Traversal Using Relays around NAT) - Relays media when direct connections fail
- Acts as a relay server when NAT is too restrictive
- Required in ~8-10% of connections (strict corporate firewalls, symmetric NAT)
- Costs bandwidth (you're paying for relayed traffic)
Configuring STUN/TURN Servers:
import { useSipClient } from 'vuesip'
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443',
sipUri: 'sip:1000@example.com',
password: 'your-password',
rtcConfiguration: {
// STUN servers help discover public IP addresses (needed for NAT traversal)
stunServers: [
'stun:stun.l.google.com:19302', // Google's public STUN server
'stun:stun1.l.google.com:19302' // Backup STUN server for redundancy
],
// TURN servers relay media when direct connections fail (needed in restrictive networks)
turnServers: [
{
urls: ['turn:turn.example.com:3478'], // Standard TURN (UDP/TCP)
username: 'turn-user', // Usually time-limited credentials
credential: 'turn-password', // Generated by your TURN server
credentialType: 'password'
},
{
urls: ['turns:turn.example.com:5349'], // π TURN over TLS (more secure)
username: 'turn-user',
credential: 'turn-password',
credentialType: 'password'
}
],
// Control how connections are established
iceTransportPolicy: 'all' // 'all' = try direct, fallback to relay
// 'relay' = always use TURN (maximum privacy, higher latency)
}
})β οΈ Security Note: TURN servers can see your media traffic (since they relay it). Use TURNS (TURN over TLS) to encrypt the relay connection itself.
ICE Transport Policy: Balancing Security and Performance β
Understanding the Tradeoff
The ICE transport policy controls how WebRTC establishes connections:
| Policy | Security Level | Performance | How It Works | Best For |
|---|---|---|---|---|
all (default) | Good | Best | Tries direct peer-to-peer first, uses TURN relay only if needed | Normal operations, most users |
relay | Maximum | Lower latency | Forces all traffic through your TURN server, no direct connections | High-security environments, enterprise |
When to Use relay (Maximum Security):
// High-security configuration: All media goes through your TURN server
rtcConfiguration: {
iceTransportPolicy: 'relay', // β οΈ Forces TURN relay for ALL connections
turnServers: [
{
urls: ['turns:turn.example.com:5349'], // Use TLS-encrypted TURN
username: 'turn-user',
credential: 'turn-password',
credentialType: 'password'
}
]
}β
Use relay when:
- Handling sensitive calls (legal, medical, financial)
- Corporate policy requires traffic monitoring
- Must prevent IP address disclosure
- Need to route all traffic through your infrastructure
β Avoid relay when:
- Latency is critical (gaming, live translation)
- TURN bandwidth costs are prohibitive
- Most users have good direct connectivity
π‘ Tip: In relay mode, you must have working TURN servers or calls will fail!
Media Encryption Best Practices β
Follow these guidelines to ensure maximum media security:
- π Use TURN over TLS (TURNS) - Encrypts the relay connection itself with
turns://URLs - β° Secure TURN credentials - Use time-limited credentials (expire after 24 hours) to limit exposure
- π Monitor ICE connection states - Log failures to detect network issues or attacks
- π‘οΈ Prefer relay in sensitive environments - Use
iceTransportPolicy: 'relay'for maximum privacy
Credential Storage β
What This Section Covers
When users choose "Remember me," credentials must be stored on their device. This section explains how to store sensitive data securely using encryption, so credentials remain protected even if the device is compromised.
Understanding Encryption at Rest β
The Problem: Browser storage (localStorage, IndexedDB) stores data in plain text. Anyone with access to the device can read these files and steal credentials.
The Solution: VueSip provides built-in encryption using the Web Crypto API, a browser standard for cryptographic operations. All sensitive data is encrypted before storage and decrypted only when needed.
Encryption Specifications:
VueSip uses industry-standard encryption with secure defaults:
Algorithm: AES-GCM (Advanced Encryption Standard - Galois/Counter Mode)
- AES-GCM provides both encryption and integrity checking
- 256-bit key length (strongest AES variant)
- Widely used, hardware-accelerated in most CPUs
Key Derivation: PBKDF2 (Password-Based Key Derivation Function 2) with SHA-256
- Converts user passwords into encryption keys
- Slow by design to prevent brute-force attacks
Iterations: 100,000 (configurable, minimum recommended)
- More iterations = slower but more secure
- Slows down attackers trying to guess passwords
Salt: Random 16-byte salt per encryption operation
- Ensures identical passwords produce different keys
- Prevents rainbow table attacks
IV: Random 12-byte initialization vector per encryption
- Ensures identical data produces different ciphertext
- Prevents pattern analysis
π Security Guarantee: Even if an attacker copies your localStorage, they cannot decrypt credentials without the encryption key.
Using Encrypted Storage β
Basic Encrypted Storage Setup:
import { LocalStorageAdapter, generateEncryptionKey } from 'vuesip'
// Create an encrypted storage adapter with password-based encryption
// The encryption password should be derived from user credentials or stored securely
const encryptionPassword = 'user-master-password' // In production: derive from user password
const encryptedStorage = new LocalStorageAdapter(
{
prefix: 'vuesip', // Namespace for storage keys
version: '1', // Version for migration support
encryption: {
enabled: true, // Enable AES-GCM encryption
iterations: 100000 // PBKDF2 iterations (minimum recommended: 100,000)
}
},
encryptionPassword // Password used for key derivation
)
// Use the encrypted storage to persist application data securely
await encryptedStorage.set('userPreferences', {
theme: 'dark',
notifications: true,
displayName: 'John Doe'
})
// Retrieve encrypted data
const result = await encryptedStorage.get('userPreferences')
if (result.success) {
console.log('Retrieved:', result.data) // Automatically decrypted
}β οΈ Security Warning: The encryption password is critical! If lost, encrypted data cannot be recovered. If compromised, all encrypted data can be decrypted.
Generating Secure Encryption Keys β
Never hardcode encryption keys in your source code! Here are secure ways to generate keys:
Option 1: Generate Random Key (For Current Session Only)
import { generateEncryptionKey } from 'vuesip'
// Generate a cryptographically secure random key
const encryptionKey = generateEncryptionKey(32) // 32 bytes = 256 bits
// β
GOOD: Store in memory (clears when app closes)
sessionStorage.setItem('encKey', encryptionKey) // Available only in current tab
// β BAD: Never store encryption keys in plain text!
// localStorage.setItem('encKey', encryptionKey) // β οΈ Defeats the purpose of encryption!π‘ When to use: Session-based logins where users re-authenticate each time
Option 2: Derive from User Password (For Persistent Sessions)
import { hashPassword } from 'vuesip'
// User enters their master password
const userPassword = 'user-master-password'
// Derive encryption key using PBKDF2
// This takes the password through 100,000+ iterations of hashing
const encryptionKey = await hashPassword(userPassword)
// Use this key to encrypt/decrypt stored credentials
// The key never leaves the device and isn't storedπ‘ When to use: "Remember me" functionality where users want persistent login
β οΈ Important: The user must enter their password each time the app loads to regenerate the encryption key. This is a security feature, not a bug!
Choosing the Right Storage Adapter β
Different storage adapters offer different tradeoffs between security, persistence, and convenience:
| Adapter | Persistence | Scope | Security Level | Use Case |
|---|---|---|---|---|
| SessionStorage | Until tab closes | Current tab only | High | Temporary sessions, kiosks |
| LocalStorage | Permanent | All tabs in origin | Medium (with encryption) | Remember credentials |
| IndexedDB | Permanent | All tabs in origin | Medium (with encryption) | Large datasets, offline mode |
| Memory (default) | None | Current component | Highest | Maximum security, no persistence |
Example 1: Session Storage (Security-First Approach)
import { SessionStorageAdapter } from 'vuesip'
// Data automatically deleted when browser tab closes
// Best for: Public computers, shared devices, maximum security
const sessionStorage = new SessionStorageAdapter({
prefix: 'vuesip',
version: '1',
encryption: {
enabled: true,
iterations: 100000
}
}, 'session-password')
// Use session storage for temporary data
await sessionStorage.set('callHistory', recentCalls)β Best for: Medical offices, public kiosks, shared workstations - data is automatically cleared when tab closes
Example 2: Encrypted IndexedDB (Convenience with Security)
import { IndexedDBAdapter, generateEncryptionKey } from 'vuesip'
// Create encrypted IndexedDB storage
// Best for: Long-term storage, offline capabilities, larger datasets
const indexedDBStorage = new IndexedDBAdapter(
{
prefix: 'vuesip',
version: '1',
databaseName: 'vuesip-db',
encryption: {
enabled: true,
iterations: 100000
}
},
'user-encryption-password'
)
// Use for larger datasets with encryption
await indexedDBStorage.set('contacts', contactsList)
await indexedDBStorage.set('callRecords', callHistory)β Best for: Personal devices, users who want persistent storage with encryption
Credential Storage Best Practices β
Follow these guidelines to protect stored credentials:
- π Never store plaintext passwords - Always use encryption for any persistent storage
- π Use HA1 hash when possible - Store HA1 digests instead of passwords (see Authentication section)
- β° Minimize credential lifetime - Clear credentials on explicit logout
- ποΈ Use SessionStorage for sensitive apps - No persistence after tab close = maximum security
- π Secure encryption keys - Never hardcode keys in source code, never commit to git
- π Implement key rotation - Periodically prompt users to update their master password
- π« Never log encryption keys - Exclude keys from error reports and analytics
π‘ Pro Tip: For maximum security, combine encrypted storage with short-lived sessions. Require re-authentication every 24 hours even with "remember me."
Authentication β
What This Section Covers
Before making calls, your application must authenticate with the SIP server to prove the user's identity. This section explains SIP Digest Authentication and how to use it securely, including advanced techniques like HA1 hashing to avoid storing plaintext passwords.
Understanding SIP Digest Authentication β
What is Digest Authentication?
SIP Digest Authentication (based on HTTP Digest) is a challenge-response mechanism that proves you know the password without actually sending it over the network.
How It Works (Simplified):
- Client: "I want to register as user 1000"
- Server: "Prove it. Here's a challenge (nonce): abc123"
- Client: Calculates
response = MD5(password + nonce + other data)and sends response - Server: Calculates the same thing and compares. If they match, authentication succeeds!
Why It's Secure:
- Password never transmitted (only a hash)
- Nonce prevents replay attacks (old responses won't work)
- Different hash for each authentication attempt
Standard Password Authentication:
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443',
sipUri: 'sip:1000@example.com',
// VueSip automatically handles digest authentication
password: 'your-password', // Password used to calculate digest response
// Optional parameters (usually auto-configured)
authorizationUsername: '1000', // Username for auth (defaults to sipUri user part)
realm: 'asterisk' // Authentication realm (usually provided by server)
})π Note: Even though you provide a "password" parameter, VueSip never sends it directly. It's only used to calculate MD5 digests for authentication challenges.
HA1 Hash Authentication (Advanced) β
What is HA1?
HA1 (Hash A1) is a pre-computed hash used in SIP Digest authentication. Instead of storing the actual password, you can store this hash and use it directly for authentication.
HA1 Formula:
HA1 = MD5(username:realm:password)Why Use HA1?
β Advantages:
- Password never stored - Your application only has the HA1 hash, not the actual password
- Reduced exposure - If your database is compromised, attackers get hashes, not passwords
- Same security - Works identically with SIP Digest authentication
- Compatible - All SIP servers support digest authentication with HA1
β Limitations:
- Realm-specific (different realm = different HA1)
- Not as strong as modern password hashing (bcrypt, argon2)
- Still vulnerable if attacker has realm name
Using HA1 Authentication:
// Step 1: Pre-compute HA1 hash (typically done server-side during registration)
// β οΈ This is just an example - in production, generate HA1 on your backend
const username = '1000'
const realm = 'asterisk'
const password = 'secret123'
const ha1 = MD5(`${username}:${realm}:${password}`) // Example: 'b5f2a7c3d9e...'
// Step 2: Use HA1 in client configuration (instead of password)
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443',
sipUri: 'sip:1000@example.com',
ha1: ha1, // π Use pre-computed HA1 hash
realm: 'asterisk' // β οΈ Must match the realm used to compute HA1
// Note: Don't provide 'password' when using 'ha1'
})Real-World Workflow:
// 1οΈβ£ User Registration (Backend)
// When user creates account, compute and store HA1
async function registerUser(username: string, password: string, realm: string) {
const ha1 = MD5(`${username}:${realm}:${password}`)
// Store HA1 in database (not the password!)
await database.users.create({
username,
realm,
ha1 // β
Only store the hash
})
return ha1
}
// 2οΈβ£ User Login (Frontend)
// Fetch HA1 from your backend API
async function loginUser(username: string) {
// Your backend retrieves the stored HA1
const response = await fetch(`/api/auth/ha1?username=${username}`)
const { ha1, realm } = await response.json()
// Use HA1 to configure SIP client
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443',
sipUri: `sip:${username}@example.com`,
ha1, // Use pre-computed hash
realm // Must match
})
await connect()
}π‘ Best Practice: Compute HA1 on your backend during user registration. Never compute it on the frontend where password could be intercepted.
Authentication Best Practices β
Follow these guidelines for secure authentication:
- π Use HA1 when possible - Avoid storing plaintext passwords in your system
- β Validate server challenges - Ensure 401/407 responses have valid nonce and realm
- π Monitor auth failures - Log failed authentication attempts and alert on repeated failures (potential brute force)
- β±οΈ Implement rate limiting - Limit authentication attempts to prevent brute force attacks (e.g., 5 attempts per 15 minutes)
- π Use strong passwords - Require minimum 12 characters with mixed case, numbers, and symbols
- π Rotate credentials - Encourage periodic password changes
- π« Never log passwords or HA1 - Exclude from logs, error reports, and analytics
β οΈ Security Warning: Even with HA1, use encrypted storage (see Credential Storage section) to protect the hash itself!
Input Validation β
What This Section Covers
User input is the primary attack vector for web applications. This section explains how to validate all inputs using VueSip's built-in validators to prevent injection attacks, crashes, and data corruption.
Why Input Validation Matters:
Without validation, malicious users could:
- Inject malicious SIP headers
- Cause application crashes with malformed data
- Bypass security checks
- Trigger unexpected behavior
VueSip provides comprehensive validators for all input types. Always validate before use!
SIP URI Validation β
What is a SIP URI? A SIP URI (like sip:user@example.com) identifies users and servers in SIP networks, similar to email addresses.
Why Validate? Malformed SIP URIs can cause registration failures, call setup errors, or even security vulnerabilities.
import { validateSipUri } from 'vuesip'
// Validate user input before using it
const userInput = 'sip:1000@example.com'
const result = validateSipUri(userInput)
if (result.valid) {
// β
Safe to use - URI has been validated and normalized
console.log('Valid SIP URI:', result.normalized)
// Output: sip:1000@example.com
await makeCall(result.normalized) // Use normalized version
} else {
// β Invalid - show error to user
console.error('Invalid SIP URI:', result.error)
showErrorMessage(`Invalid SIP address: ${result.error}`)
}Validation Rules:
- β
Must start with
sip:orsips:(secure SIP) - β Must include user part (before @) and domain part (after @)
- β Port must be between 1-65535 if specified
- β Domain automatically normalized to lowercase
- β Special characters properly encoded
Valid Examples:
validateSipUri('sip:1000@example.com') // β
Basic format
validateSipUri('sip:user@example.com:5060') // β
With port
validateSipUri('sips:secure@example.com') // β
Secure SIP
validateSipUri('sip:john.doe@sip.example.com') // β
With subdomainInvalid Examples:
validateSipUri('1000@example.com') // β Missing sip: scheme
validateSipUri('sip:1000') // β Missing domain
validateSipUri('http://example.com') // β Wrong protocolPhone Number Validation β
What is E.164? E.164 is the international standard for phone numbers (like +14155551234). The format ensures phone numbers work globally.
import { validatePhoneNumber } from 'vuesip'
// Validate phone number input
const userInput = '+14155551234'
const result = validatePhoneNumber(userInput)
if (result.valid) {
// β
Valid E.164 phone number
console.log('Valid phone number:', result.normalized)
// Can be used to construct SIP URI: sip:+14155551234@provider.com
const sipUri = `sip:${result.normalized}@pstn.example.com`
await makeCall(sipUri)
} else {
// β Invalid phone number
console.error('Invalid phone number:', result.error)
showErrorMessage('Please enter a valid phone number with country code')
}E.164 Format Rules:
- β
Must start with
+(plus sign indicates international format) - β Followed by country code (1-3 digits, e.g., +1 for US/Canada)
- β Followed by national number
- β Maximum 15 digits total (including country code)
- β Only digits after the +, no spaces or separators
Valid Examples:
validatePhoneNumber('+14155551234') // β
US number
validatePhoneNumber('+442071234567') // β
UK number
validatePhoneNumber('+81312345678') // β
Japan numberInvalid Examples:
validatePhoneNumber('4155551234') // β Missing + and country code
validatePhoneNumber('+1-415-555-1234') // β Contains separators
validatePhoneNumber('(415) 555-1234') // β Not E.164 formatWebSocket URL Validation β
Why Validate? Malicious URLs could connect to untrusted servers or cause crashes.
import { validateWebSocketUrl } from 'vuesip'
// Validate SIP server URL
const userInput = 'wss://sip.example.com:7443'
const result = validateWebSocketUrl(userInput)
if (result.valid) {
// β
Valid WebSocket URL
console.log('Valid WebSocket URL:', result.normalized)
// Safe to use for connection
const { connect } = useSipClient({
uri: result.normalized, // Use validated URL
// ... rest of config
})
} else {
// β Invalid URL
console.error('Invalid WebSocket URL:', result.error)
}Validation Rules:
- β
Must use
ws://orwss://protocol - β Must include valid hostname (domain or IP)
- β Port is optional (defaults: 80 for ws, 443 for wss)
- β Path and query parameters allowed
Valid Examples:
validateWebSocketUrl('wss://sip.example.com') // β
Basic WSS
validateWebSocketUrl('wss://sip.example.com:7443') // β
With port
validateWebSocketUrl('ws://localhost:8088') // β
Local development
validateWebSocketUrl('wss://sip.example.com/path') // β
With pathDTMF Tone Validation β
What are DTMF Tones? Dual-Tone Multi-Frequency tones are the beeps you hear when pressing phone buttons. They're used for IVR systems (press 1 for sales, press 2 for support) and entering PIN codes.
import { validateDtmfTone, validateDtmfSequence } from 'vuesip'
// Validate single tone
const tone = validateDtmfTone('1')
if (tone.valid) {
await sendDTMF(tone.normalized) // Send: '1'
}
// Validate entire sequence (e.g., PIN code or menu navigation)
const sequence = validateDtmfSequence('1234*#')
if (sequence.valid) {
// β
Valid sequence - send all tones
await sendDTMF(sequence.normalized) // Send: '1234*#'
} else {
// β Invalid - contains unsupported characters
showErrorMessage('Invalid DTMF sequence')
}Valid DTMF Tones:
0-9- Standard digit keys*- Star key (often "back" or "cancel")#- Pound/hash key (often "confirm" or "send")A-D- Extended keys (rarely used, mainly in military/specialized systems)
Common Use Cases:
// IVR menu navigation
await sendDTMF('1') // "Press 1 for sales"
// PIN code entry
await sendDTMF('9876') // Enter 4-digit PIN
// Conference controls
await sendDTMF('*6') // Mute/unmute in conference
// Voicemail access
await sendDTMF('*97') // Access voicemail systemConfiguration Validation β
VueSip automatically validates your entire configuration before attempting to connect. This catches errors early and provides clear error messages.
import { validateSipConfig } from 'vuesip'
// Your configuration object
const config = {
uri: 'wss://sip.example.com:7443',
sipUri: 'sip:1000@example.com',
password: 'secret123'
}
// Validate before connecting
const validation = validateSipConfig(config)
if (!validation.valid) {
// β Configuration has errors - fix before connecting
console.error('Configuration errors:', validation.errors)
// Example errors:
// - "uri is required"
// - "sipUri must be a valid SIP URI"
// - "password or ha1 is required"
// Show errors to user or developer
validation.errors.forEach(error => {
showErrorMessage(error)
})
// Don't attempt to connect!
return
}
// β οΈ Warnings don't prevent connection but should be addressed
if (validation.warnings) {
console.warn('Configuration warnings:', validation.warnings)
// Example warnings:
// - "Using ws:// in production (use wss://)"
// - "No TURN servers configured (calls may fail on restrictive networks)"
// Optionally log warnings for monitoring
}
// β
Configuration is valid - safe to connect
await connect()What Gets Validated:
- URI format and protocol
- SIP URI format
- Authentication credentials (password or HA1)
- Port numbers (if specified)
- Storage adapter configuration
- RTC configuration parameters
π‘ Pro Tip: Call validateSipConfig() in development mode to catch configuration errors early!
Input Validation Best Practices β
Follow these guidelines to secure your application:
- β Validate all user inputs - Never trust client-side data, even in your own application
- β Use built-in validators - VueSip's validators are tested and secure - don't write your own
- β Validate before use - Always validate before passing data to VueSip functions
- β Use normalized output - Validators return normalized values (lowercase domains, etc.)
- π§Ή Sanitize before display - When showing user input in UI, escape HTML to prevent XSS
- π Validate on both client and server - Defense in depth - validate twice
- π Handle errors gracefully - Show clear, user-friendly error messages
- π« Never pass raw input - Always use
result.normalizedfrom validators
Complete Validation Example:
// Example: Making a call with full validation
async function makeValidatedCall(userInput: string) {
// Step 1: Validate input
const validation = validateSipUri(userInput)
if (!validation.valid) {
// Step 2: Handle invalid input
showErrorMessage(`Invalid phone number: ${validation.error}`)
return false
}
// Step 3: Use normalized value
try {
await makeCall(validation.normalized)
return true
} catch (error) {
// Step 4: Handle call errors
showErrorMessage('Call failed. Please try again.')
return false
}
}Security Checklist β
Use this comprehensive checklist to audit your VueSip application's security posture. Review this list before deploying to production and periodically thereafter.
π Transport Security β
- [ ] Use WSS in production - Never use
ws://for production deployments - [ ] Verify TLS certificates - Ensure SIP server has valid, non-expired certificate from trusted CA
- [ ] Monitor security warnings - Set up alerts for WSS connection failures
- [ ] Use standard secure ports - Prefer 443 or 7443 for easier firewall traversal
- [ ] Test certificate renewal - Verify automated certificate renewal process
π€ Media Security β
- [ ] Configure STUN servers - At least 2 STUN servers for redundancy
- [ ] Configure TURN servers - Required for ~10% of connections (restrictive NATs)
- [ ] Use TURNS (TLS) - Prefer
turns://overturn://for encrypted relay - [ ] Set ICE transport policy - Choose
relayfor high-security environments - [ ] Monitor ICE failures - Log and alert on connection establishment failures
- [ ] Test TURN authentication - Verify TURN credentials work and auto-refresh
π Credential Security β
- [ ] No plaintext passwords - Never store passwords in source code or unencrypted storage
- [ ] Use encrypted storage - Enable encryption in storage adapters for persistent data
- [ ] Prefer HA1 hashes - Use pre-computed HA1 instead of passwords when possible
- [ ] Secure encryption keys - Generate strong keys, never hardcode or commit to git
- [ ] Implement key management - Document how encryption keys are generated and stored
- [ ] Clear on logout - Explicitly clear credentials when user logs out
- [ ] Time-limited sessions - Re-authenticate periodically even with "remember me"
πΎ Storage Security β
- [ ] Choose appropriate storage - SessionStorage for sensitive, LocalStorage for convenience
- [ ] Encrypt persistent data - Always encrypt LocalStorage and IndexedDB
- [ ] Use minimum 100K iterations - PBKDF2 iterations should be at least 100,000
- [ ] Secure key derivation - Use proper PBKDF2 with salt and high iteration count
- [ ] Never log keys - Exclude encryption keys from all logs and error reports
- [ ] Test data clearing - Verify credentials are actually removed on logout
π‘οΈ Authentication Security β
- [ ] Use SIP Digest authentication - Enabled by default in VueSip
- [ ] Monitor auth failures - Log and alert on repeated authentication failures
- [ ] Strong password policy - Require minimum 12 characters, mixed case, numbers, symbols
- [ ] Implement rate limiting - Limit authentication attempts (e.g., 5 per 15 minutes)
- [ ] Use HA1 when possible - Avoid storing plaintext passwords in your backend
- [ ] Validate auth realms - Ensure realm matches expected value
- [ ] Test auth failure handling - Verify app handles 401/407 responses correctly
βοΈ Input Validation β
- [ ] Validate SIP URIs - Use
validateSipUri()before calling - [ ] Validate phone numbers - Use
validatePhoneNumber()for E.164 format - [ ] Validate WebSocket URLs - Use
validateWebSocketUrl()for server URLs - [ ] Validate DTMF tones - Use
validateDtmfTone()before sending - [ ] Validate configuration - Call
validateSipConfig()before connecting - [ ] Sanitize display - Escape HTML when showing user input
- [ ] Server-side validation - Never rely solely on client-side validation
π Application Security β
- [ ] Implement CSP - Set Content-Security-Policy headers to prevent XSS
- [ ] Use HTTPS everywhere - Serve application over HTTPS, not HTTP
- [ ] Configure CORS properly - Restrict origins that can access your API
- [ ] Keep dependencies updated - Regularly run
npm auditand update packages - [ ] Scan for vulnerabilities - Use automated security scanning tools
- [ ] Implement logging - Log security events (auth failures, connection errors)
- [ ] Monitor in production - Set up alerts for security anomalies
- [ ] Regular security audits - Schedule quarterly security reviews
- [ ] Document security - Maintain security documentation for your team
- [ ] Test security - Include security tests in your test suite
Common Security Pitfalls β
What This Section Covers
Even experienced developers make these mistakes. Learn from common security pitfalls and how to avoid them in your VueSip application.
β Pitfall 1: Using Insecure WebSocket in Production β
The Mistake:
// β BAD - Anyone on the network can intercept this traffic!
const { connect } = useSipClient({
uri: 'ws://sip.example.com:8088', // Unencrypted connection
sipUri: 'sip:1000@example.com',
password: 'secret' // Password visible in network traffic!
})Why It's Dangerous:
- All SIP messages transmitted in plaintext
- Passwords visible to anyone on the network (WiFi, ISP, etc.)
- Call details, participants, and metadata exposed
- Vulnerable to man-in-the-middle attacks
β The Fix: Always Use WSS
// β
GOOD - TLS-encrypted connection
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443', // Encrypted with TLS
sipUri: 'sip:1000@example.com',
password: 'secret' // Protected by TLS encryption
})π‘ Development Exception: ws://localhost is acceptable for local development only.
β Pitfall 2: Hardcoding Credentials in Source Code β
The Mistake:
// β BAD - Credentials visible in git history forever!
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443',
sipUri: 'sip:1000@example.com',
password: 'hardcoded-password-123' // This will be in git commits!
})Why It's Dangerous:
- Credentials committed to version control (git)
- Visible to anyone with repository access
- Exposed in build artifacts
- Impossible to change without updating code
- Violates security compliance (SOC 2, ISO 27001)
β The Fix: Use Environment Variables or User Input
// β
GOOD - Credentials from environment variables
const { connect } = useSipClient({
uri: import.meta.env.VITE_SIP_URI, // From .env file (not committed)
sipUri: userInputSipUri.value, // From user input
password: userInputPassword.value // From user input
})Setting Up Environment Variables:
# .env file (add to .gitignore!)
VITE_SIP_URI=wss://sip.example.com:7443// Access in your code
const sipServerUri = import.meta.env.VITE_SIP_URIβ οΈ Important: Add .env to .gitignore so it's never committed!
β Pitfall 3: Storing Plaintext Passwords in Browser Storage β
The Mistake:
// β BAD - Password readable by anyone with device access!
localStorage.setItem('sipPassword', 'my-password')
// Later...
const password = localStorage.getItem('sipPassword') // Plaintext!Why It's Dangerous:
- localStorage is unencrypted plain text
- Readable by any JavaScript on the same origin
- Visible in browser dev tools
- Persists even after browser restart
- Accessible to malware and browser extensions
β The Fix: Use VueSip's Encrypted Storage
// β
GOOD - AES-GCM encrypted storage
import { LocalStorageAdapter, hashPassword } from 'vuesip'
// Derive encryption password from user credentials (never store the password itself!)
const userPassword = getUserPassword() // From login form
const encryptionPassword = await hashPassword(userPassword)
// Create encrypted storage adapter
const storage = new LocalStorageAdapter(
{
prefix: 'vuesip',
version: '1',
encryption: {
enabled: true, // Enable AES-GCM encryption
iterations: 100000 // Strong PBKDF2 key derivation
}
},
encryptionPassword
)
// Store credentials securely
await storage.set('sipCredentials', {
sipUri: 'sip:1000@example.com',
password: 'user-password' // Encrypted before storage
})
// Later, retrieve credentials (automatically decrypted)
const result = await storage.get('sipCredentials')
if (result.success && result.data) {
const { connect } = useSipClient({
uri: 'wss://sip.example.com:7443',
sipUri: result.data.sipUri,
password: result.data.password
})
}π Result: Credentials encrypted with AES-GCM before storage. Unreadable without encryption key.
β Pitfall 4: Skipping Input Validation β
The Mistake:
// β BAD - Using user input directly without validation!
const sipUri = userInput.value // Could be: "; DROP TABLE users; --"
await makeCall(sipUri) // Potential injection or crash!Why It's Dangerous:
- Malformed input causes crashes
- Potential for injection attacks
- Unexpected behavior
- Poor user experience (unclear error messages)
- Security vulnerabilities
β The Fix: Always Validate User Input
// β
GOOD - Validate before use
import { validateSipUri } from 'vuesip'
const result = validateSipUri(userInput.value)
if (result.valid) {
// Safe to use - input has been validated and normalized
await makeCall(result.normalized)
} else {
// Handle invalid input gracefully
showErrorMessage(`Invalid SIP address: ${result.error}`)
}Benefits:
- Catches errors early with clear messages
- Prevents injection attacks
- Normalizes input (lowercase domains, etc.)
- Better user experience
β Pitfall 5: Logging Sensitive Data β
The Mistake:
// β BAD - Credentials exposed in logs!
console.log('Connecting with password:', password) // Visible in console!
console.log('User config:', config) // May contain secrets!
// Logs sent to analytics
analytics.track('login', {
password: password, // β Exposed to analytics service!
ha1: ha1
})Why It's Dangerous:
- Credentials visible in browser console
- Exposed in log aggregation services (Datadog, Sentry)
- Sent to analytics platforms
- Stored in log files
- Visible to customer support staff
- May violate compliance regulations
β The Fix: Sanitize All Logs
// β
GOOD - Redact sensitive data before logging
const sanitizedConfig = {
...config,
password: '***REDACTED***', // Hide password
ha1: '***REDACTED***', // Hide HA1
encryptionKey: '***REDACTED***' // Hide encryption key
}
console.log('User config:', sanitizedConfig) // Safe to log
// Analytics without sensitive data
analytics.track('login', {
username: config.authorizationUsername, // β
Safe to log
uri: config.uri.replace(/:[^:]+$/, ''), // β
URI without credentials
// Don't include password, ha1, or keys!
})Utility Function for Sanitization:
// Helper function to sanitize configuration objects
function sanitizeConfig(config: any): any {
const sensitive = ['password', 'ha1', 'encryptionKey', 'credential', 'apiKey']
return Object.keys(config).reduce((acc, key) => {
acc[key] = sensitive.includes(key) ? '***REDACTED***' : config[key]
return acc
}, {} as any)
}
// Usage
console.log('Config:', sanitizeConfig(config))Additional Resources β
Deepen Your Understanding
These external resources provide in-depth information about the security technologies used in VueSip.
Web Crypto API β
- MDN - Web Crypto API - Complete API reference for browser cryptography
- Subtle Crypto - Low-level cryptographic operations
WebRTC Security β
- WebRTC Security Architecture (RFC 8827) - Official WebRTC security specification
- DTLS-SRTP (RFC 5764) - Technical specification for media encryption
SIP Security β
- SIP Authentication (RFC 3261) - SIP specification, authentication section
- SIP Security Mechanisms (RFC 3329) - Advanced SIP security features
OWASP Resources β
- OWASP Top Ten - Most critical web security risks
- OWASP Cheat Sheet Series - Practical security guidance
Next Steps β
Continue Your VueSip Journey
Now that you understand how to secure your VueSip application, explore these guides to build powerful VoIP features:
- Getting Started - Set up your first VueSip application with security in mind
- Making Calls - Learn how to make outgoing calls securely
- Device Management - Configure audio/video devices properly
- Call Controls - Advanced call control features and best practices
π‘ Pro Tip: Return to this security guide periodically to review your application's security posture as it grows and evolves!