WebRTC Security Best Practices

Security is paramount in real-time communications. WebRTC provides built-in security features, but proper implementation and additional measures are necessary to protect user privacy and prevent attacks. This guide covers WebRTC security best practices for developers.

Built-in WebRTC Security

The WebRTC security stack WebRTC media and data ride a layered stack: the application streams sit on SRTP for media and DTLS-SCTP for data, both keyed by a mandatory DTLS handshake, over an ICE/UDP transport. The signaling channel is separate and must be secured independently with TLS and your own authentication. Application — audio, video, data channels SRTP (media) · DTLS-SCTP (data) — encrypted DTLS handshake — authenticate peers, derive keys ICE + UDP/TCP — connectivity (STUN/TURN) Signaling channelsecure it yourself:WSS (TLS) + app authentication not covered by DTLS/SRTP
Figure 1 — Media and data are encrypted for you (green layers). The signaling channel on the right is not — securing it with TLS and real authentication is the developer's job.

WebRTC includes strong security by default:

  • Mandatory Encryption: All media and data streams are encrypted using DTLS (Datagram Transport Layer Security) for key exchange and SRTP (Secure Real-Time Transport Protocol) for media.
  • Secure Contexts: Modern browsers only allow WebRTC in secure contexts (HTTPS pages), preventing man-in-the-middle attacks.
  • Permission Models: Users must explicitly grant camera and microphone access, protecting against unauthorized surveillance.
  • Perfect Forward Secrecy: Each session uses unique encryption keys that can't be derived from previous sessions.

Authentication and Authorization

Implement robust authentication for your WebRTC application:

Signaling Authentication

Secure the signaling channel with token-based authentication:

// Server generates JWT token
const jwt = require('jsonwebtoken');

function generateToken(userId) {
  return jwt.sign(
    { userId, exp: Math.floor(Date.now() / 1000) + 3600 },
    process.env.JWT_SECRET
  );
}

// Client includes token in signaling
const ws = new WebSocket(`wss://signal.example.com?token=${token}`);

TURN Server Authentication

Use time-limited credentials for TURN servers:

function generateTurnCredentials(username, secret) {
  const timestamp = Math.floor(Date.now() / 1000) + 86400; // 24 hours
  const turnUsername = `${timestamp}:${username}`;
  const hmac = crypto.createHmac('sha1', secret);
  hmac.update(turnUsername);
  const credential = hmac.digest('base64');

  return { username: turnUsername, credential };
}

Network Security

Protect against network-level attacks:

Isolate TURN Traffic

Deploy TURN servers in isolated network segments:

  • Separate TURN servers from application servers.
  • Use dedicated IP ranges.
  • Implement firewall rules restricting access.

Rate Limiting

Prevent resource exhaustion:

const rateLimit = require('express-rate-limit');

const signalingLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many signaling requests'
});

app.use('/signaling', signalingLimiter);

DDoS Protection

Implement DDoS mitigation:

  • Use CDN services like Cloudflare.
  • Deploy load balancers with DDoS protection.
  • Implement connection throttling.
  • Monitor traffic patterns.

Media Security

Ensure media streams remain private:

End-to-End Encryption

For sensitive communications, implement additional encryption:

// Insertable Streams API for additional encryption
const sender = pc.addTrack(mediaStreamTrack);
const senderStreams = sender.createEncodedStreams();

senderStreams.readable
  .pipeThrough(new TransformStream({
    transform: encryptFrame
  }))
  .pipeTo(senderStreams.writable);

function encryptFrame(chunk, controller) {
  // Additional encryption logic
  const encrypted = customEncrypt(chunk.data);
  chunk.data = encrypted;
  controller.enqueue(chunk);
}

Screen Sharing Protection

Protect screen sharing from information disclosure:

async function getScreenShare() {
  const stream = await navigator.mediaDevices.getDisplayMedia({
    video: {
      cursor: 'never' // Don't show cursor
    },
    audio: false,
    surfaceSwitching: 'exclude' // Prevent window switching
  });

  // Monitor for window changes
  stream.getVideoTracks()[0].addEventListener('ended', () => {
    console.log('Screen sharing ended');
    cleanup();
  });

  return stream;
}

Privacy Protection

Respect user privacy:

IP Address Leakage Prevention

WebRTC can leak IP addresses even when using VPNs. Mitigate this:

const config = {
  iceServers: [...],
  iceTransportPolicy: 'relay' // Force TURN for maximum privacy
};

const pc = new RTCPeerConnection(config);

Minimal Data Collection

Only collect necessary data:

  • Don't log media content.
  • Minimize metadata collection.
  • Implement data retention policies.
  • Provide clear privacy policies.

Consent Management

Ensure proper user consent:

async function requestMediaPermissions() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: true
    });

    // Show clear indication that media is active
    showMediaActiveIndicator();

    return stream;
  } catch (error) {
    if (error.name === 'NotAllowedError') {
      showPermissionDeniedMessage();
    }
    throw error;
  }
}

Signaling Channel Security

Secure the signaling channel against attacks:

Message Validation

Validate all signaling messages:

function validateSignalingMessage(message) {
  const schema = {
    type: ['offer', 'answer', 'ice-candidate', 'bye'],
    timestamp: 'number',
    from: 'string',
    to: 'string'
  };

  // Validate message structure
  if (!validateSchema(message, schema)) {
    throw new Error('Invalid message format');
  }

  // Validate timestamp to prevent replay attacks
  const messageAge = Date.now() - message.timestamp;
  if (messageAge > 60000) { // 60 seconds
    throw new Error('Message too old');
  }

  // Validate SDP if present
  if (message.sdp) {
    validateSDP(message.sdp);
  }

  return true;
}

Prevent Injection Attacks

Sanitize all user inputs:

function sanitizeUserInput(input) {
  return input
    .replace(/[<>'"]/g, '') // Remove potential XSS characters
    .trim()
    .substring(0, 100); // Limit length
}

function createRoom(roomName) {
  const sanitized = sanitizeUserInput(roomName);
  if (!/^[a-zA-Z0-9-_]+$/.test(sanitized)) {
    throw new Error('Invalid room name');
  }
  return sanitized;
}

TURN Server Security

Secure your TURN infrastructure:

Credential Rotation

Regularly rotate TURN credentials:

// Implement credential rotation
setInterval(() => {
  const newSecret = generateSecret();
  updateTurnServerSecret(newSecret);
  notifyApplicationServers(newSecret);
}, 7 * 24 * 60 * 60 * 1000); // Weekly rotation

Access Control

Restrict TURN server access:

# /etc/turnserver.conf
# Restrict IP ranges that can use relay
allowed-peer-ip=10.0.0.0-10.255.255.255
allowed-peer-ip=172.16.0.0-172.31.255.255

# Deny potentially dangerous IPs
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=127.0.0.0-127.255.255.255

After tightening these rules, confirm legitimate clients can still allocate a relay using the free ICE server tester.

Usage Monitoring

Monitor TURN usage for anomalies:

function monitorTurnUsage() {
  const metrics = {
    activeAllocations: getTurnAllocations(),
    bandwidth: getTurnBandwidth(),
    failedAuth: getFailedAuthCount()
  };

  // Alert on suspicious patterns
  if (metrics.failedAuth > THRESHOLD) {
    alertSecurityTeam('Possible TURN brute force attack');
  }

  if (metrics.bandwidth > BANDWIDTH_LIMIT) {
    alertSecurityTeam('Unusual TURN bandwidth usage');
  }
}

Compliance Considerations

Ensure compliance with relevant regulations:

GDPR Compliance

For European users:

  • Obtain explicit consent for data processing.
  • Implement data portability.
  • Provide deletion capabilities.
  • Document data processing activities.
  • Appoint a DPO if required.

HIPAA Compliance

For healthcare applications:

  • Use Business Associate Agreements.
  • Implement audit logging.
  • Encrypt all data in transit and at rest.
  • Run regular security assessments.
  • Maintain incident response procedures.

Recording Consent

When recording sessions:

async function startRecording(participants) {
  // Obtain consent from all participants
  const consents = await Promise.all(
    participants.map(p => requestRecordingConsent(p))
  );

  if (!consents.every(c => c === true)) {
    throw new Error('Not all participants consented to recording');
  }

  // Show clear recording indicator
  showRecordingIndicator();

  // Start recording with audit trail
  logRecordingStart(participants, consents);
}

Security Monitoring

Implement comprehensive monitoring:

Logging

Log security-relevant events:

function logSecurityEvent(event) {
  const logEntry = {
    timestamp: Date.now(),
    type: event.type,
    userId: event.userId,
    ip: event.ip,
    details: event.details,
    severity: event.severity
  };

  // Send to security information and event management (SIEM)
  securityLogger.log(logEntry);

  // Alert on high-severity events
  if (event.severity === 'high') {
    alertSecurityTeam(logEntry);
  }
}

Intrusion Detection

Monitor for suspicious activity:

function detectAnomalies(userActivity) {
  const patterns = {
    rapidConnections: countConnections(userActivity, 60000) > 10,
    multipleLocations: detectLocationChanges(userActivity),
    unusualTiming: detectOffHoursAccess(userActivity),
    highFailureRate: calculateFailureRate(userActivity) > 0.5
  };

  if (Object.values(patterns).some(p => p)) {
    flagSuspiciousActivity(userActivity);
  }
}

Incident Response

Prepare for security incidents:

Response Plan

  1. Detection: Identify the incident.
  2. Containment: Isolate affected systems.
  3. Investigation: Determine scope and cause.
  4. Remediation: Fix vulnerabilities.
  5. Recovery: Restore normal operations.
  6. Post-Incident: Review and improve.

Emergency Procedures

async function handleSecurityIncident(incident) {
  // Immediate containment
  await isolateAffectedServers(incident.affectedServers);

  // Notify stakeholders
  await notifySecurityTeam(incident);
  await notifyAffectedUsers(incident);

  // Begin investigation
  const forensics = await collectForensicData(incident);

  // Implement fixes
  await deploySecurityPatches();

  // Monitor for recurrence
  await enhanceMonitoring(incident.type);

  // Document incident
  await createIncidentReport(incident, forensics);
}

Security Checklist

Use this checklist for security audits:

  • HTTPS/WSS for all communications
  • Authentication on all endpoints
  • Time-limited TURN credentials
  • Rate limiting implemented
  • Input validation and sanitization
  • Security headers configured
  • Logging and monitoring active
  • Incident response plan documented
  • Regular security assessments
  • Compliance requirements met
  • User consent mechanisms
  • Data encryption at rest and in transit
  • Access control properly configured
  • Regular security training for the team

Conclusion

WebRTC security requires a multi-layered approach combining the protocol's built-in security features with application-level protections, network security, and operational practices. By implementing these best practices, you can build WebRTC applications that protect user privacy, prevent attacks, and maintain trust.

Remember that security is not a one-time task but an ongoing process. Regularly review and update your security measures, stay informed about new vulnerabilities, and maintain vigilance against emerging threats.

Check Your Own Servers

Locked-down TURN credentials and access rules are only safe if they still work. After hardening, run your turn:/turns: servers through the tester to confirm authentication succeeds and no relay is left open to anonymous use.

Open the Free ICE Server Tester →