Channel Message Security

Channel Messages provide a communication bridge between web content and Unity. Meanwhile, it exposes another layer that the attackers might use. This guide covers security considerations and best practices for production applications.

Security Overview

While the Channel Messaging System provides a flexible communication bridge, implementing proper security measures is your responsibility and it depends on how secure you'd like the system becomes.

This guide recommends the following security practices:

  • Origin Validation: Implement checks to verify message sources and prevent unauthorized communication
  • Input Sanitization: Always validate and sanitize incoming data to protect against injection attacks
  • Message Authentication: Consider using cryptographic signatures for sensitive operations
  • Replay Protection: Implement nonce-based systems when replay attacks are a concern
  • Safe Data Handling: Follow secure coding practices to prevent XSS and other vulnerabilities

It is not a must at all to use the Channel Messaging System. However, if your implementation involves sensitive information such as user personal data or payment details, or if it must meet higher security compliance standards, the default configuration may not provide sufficient protection.

In the following section, we have listed some common practices that can enhance security for your reference.

Basic Security Principles

1. Validate All Input

Never trust data from web content. Always validate and sanitize incoming messages:

webView.OnChannelMessageReceived += (view, message) => {
    // Validate action names
    if (string.IsNullOrEmpty(message.action)) {
        Debug.LogWarning("Received message with empty action");
        return UniWebViewChannelMessageResponse.Error("Invalid action");
    }

    // Whitelist allowed actions
    var allowedActions = new HashSet<string> {
        "saveProgress", "loadGameData", "updateSettings"
    };

    if (!allowedActions.Contains(message.action)) {
        Debug.LogWarning($"Unauthorized action attempted: {message.action}");
        return UniWebViewChannelMessageResponse.Error("Action not allowed");
    }

    // Validate data structure
    if (message.action == "saveProgress") {
        if (!message.TryGetData<SaveProgressData>(out var data)) {
            return UniWebViewChannelMessageResponse.Error("Invalid data format");
        }

        if (data.playerId <= 0 || string.IsNullOrEmpty(data.levelName)) {
            return UniWebViewChannelMessageResponse.Error("Missing required fields");
        }

        // Process validated data...
    }

    return null;
};

2. Implement URL Validation

For sensitive operations, verify that messages come from trusted origins. Also, always use HTTPs to ensure the connection is safe:

public class SecureChannelHandler {
    private readonly HashSet<string> trustedOrigins = new HashSet<string> {
        "https://yourgame.com",
        "https://cdn.yourgame.com",
        "https://api.yourgame.com"
    };

    public UniWebViewChannelMessageResponse HandleMessage(UniWebView webView, UniWebViewChannelMessage message) {
        // Get current URL
        string currentUrl = webView.Url;

        if (!IsOriginTrusted(currentUrl)) {
            Debug.LogWarning($"Message from untrusted origin: {currentUrl}");
            return UniWebViewChannelMessageResponse.Error("Untrusted origin");
        }

        // Handle trusted message...
        return ProcessTrustedMessage(message);
    }

    private bool IsOriginTrusted(string url) {
        if (string.IsNullOrEmpty(url)) return false;

        try {
            var uri = new System.Uri(url);
            var origin = $"{uri.Scheme}://{uri.Host}";
            return trustedOrigins.Contains(origin);
        } catch {
            return false;
        }
    }
}

3. Secure Sensitive Data

Never expose sensitive information unnecessarily:

webView.OnChannelMessageReceived += (view, message) => {
    if (message.action == "getUserInfo") {
        var userInfo = GetUserInfo();

        // Return only safe, public information
        var safeUserInfo = new {
            displayName = userInfo.displayName,
            level = userInfo.level,
            publicAchievements = userInfo.publicAchievements
            // DON'T include: passwords, tokens, private data
        };

        return UniWebViewChannelMessageResponse.Success(safeUserInfo);
    }

    return null;
};

Advanced Security Features

Message Authentication with Digital Signatures

For high-security applications, implement cryptographic message verification. Using a private key on your server to sign the message, and verify it in Unity side with the public key.

Here is some basic concept and pseudo code about how to perform it.

using System.Security.Cryptography;
using System.Text;

public class SignedMessageHandler {
    private RSA publicKey;

    public void Initialize() {
        // Load your public key (private key stays on your server)
        publicKey = LoadPublicKey();
    }

    public UniWebViewChannelMessageResponse HandleSignedMessage(UniWebView webView, UniWebViewChannelMessage message) {
        if (message.action == "secureTransaction") {
            if (!message.TryGetData<SignedMessage>(out var signedData)) {
                return UniWebViewChannelMessageResponse.Error("Invalid message format");
            }

            // Verify signature
            if (!VerifySignature(signedData.payload, signedData.signature)) {
                Debug.LogWarning("Message signature verification failed");
                return UniWebViewChannelMessageResponse.Error("Invalid signature");
            }

            // Process verified message
            return ProcessSecureTransaction(signedData.payload);
        }

        return null;
    }

    private bool VerifySignature(string payload, string signature) {
        try {
            byte[] payloadBytes = Encoding.UTF8.GetBytes(payload);
            byte[] signatureBytes = System.Convert.FromBase64String(signature);

            return publicKey.VerifyData(payloadBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        } catch {
            return false;
        }
    }
}

[System.Serializable]
public class SignedMessage {
    public string payload;    // The actual message data
    public string signature;  // Base64-encoded signature
    public long timestamp;    // Message timestamp
    public string nonce;      // Unique nonce to prevent replay
}

Nonce-Based Replay Protection

Prevent replay attacks by tracking used nonces:

public class ReplayProtectionHandler {
    private readonly HashSet<string> usedNonces = new HashSet<string>();
    private readonly Queue<(string nonce, DateTime timestamp)> nonceHistory = new Queue<(string, DateTime)>();
    private readonly TimeSpan nonceLifetime = TimeSpan.FromMinutes(10);

    public UniWebViewChannelMessageResponse HandleMessage(UniWebView webView, UniWebViewChannelMessage message) {
        if (message.TryGetData<NonceMessage>(out var nonceData)) {
            // Clean expired nonces
            CleanExpiredNonces();

            // Check for replay
            if (usedNonces.Contains(nonceData.nonce)) {
                Debug.LogWarning($"Replay attack detected: nonce {nonceData.nonce} already used");
                return UniWebViewChannelMessageResponse.Error("Message replay detected");
            }

            // Validate timestamp (prevent very old messages)
            var messageTime = DateTimeOffset.FromUnixTimeMilliseconds(message.timestamp).DateTime;
            if (DateTime.UtcNow - messageTime > nonceLifetime) {
                Debug.LogWarning("Message too old, rejecting");
                return UniWebViewChannelMessageResponse.Error("Message expired");
            }

            // Record nonce
            usedNonces.Add(nonceData.nonce);
            nonceHistory.Enqueue((nonceData.nonce, DateTime.UtcNow));

            // Process unique message
            return ProcessUniqueMessage(nonceData);
        }

        return null;
    }

    private void CleanExpiredNonces() {
        var cutoffTime = DateTime.UtcNow - nonceLifetime;

        while (nonceHistory.Count > 0 && nonceHistory.Peek().timestamp < cutoffTime) {
            var expired = nonceHistory.Dequeue();
            usedNonces.Remove(expired.nonce);
        }
    }
}

[System.Serializable]
public class NonceMessage {
    public string nonce;      // Unique identifier (UUID recommended)
    public string action;     // The actual action
    public object data;       // Message payload
}

JavaScript Security Best Practices

1. Generate Secure Nonces

// Generate cryptographically secure nonces
function generateNonce() {
  const array = new Uint8Array(16);
  crypto.getRandomValues(array);
  return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
    ""
  );
}

// Use nonces in sensitive messages
function sendSecureMessage(action, data) {
  const message = {
    nonce: generateNonce(),
    timestamp: Date.now(),
    action: action,
    data: data,
  };

  return window.uniwebview.request("secureAction", message);
}

2. Input Validation

// Validate data before sending
function sendUserData(userData) {
  // Client-side validation (note: always validate server-side too)
  if (!userData.username || userData.username.length < 3) {
    throw new Error("Username must be at least 3 characters");
  }

  if (!isValidEmail(userData.email)) {
    throw new Error("Invalid email format");
  }

  // Sanitize input
  const sanitizedData = {
    username: sanitizeString(userData.username),
    email: sanitizeString(userData.email),
    preferences: sanitizeObject(userData.preferences),
  };

  return window.uniwebview.send("updateUser", sanitizedData);
}

function sanitizeString(str) {
  return str.replace(/[<>\"'&]/g, "");
}

3. Handle Errors Securely

// Don't expose sensitive information in error handling
window.uniwebview
  .request("sensitiveOperation", data)
  .then((result) => {
    processResult(result);
  })
  .catch((error) => {
    // Log detailed errors for debugging (remove in production)
    console.error("Operation failed:", error);

    // Show user-friendly error messages
    showUserError("Operation could not be completed. Please try again.");
  });

Production Security Checklist

Before Deployment

  • [ ] Input Validation: All message data is validated and sanitized
  • [ ] Action Whitelist: Only explicitly allowed actions are processed
  • [ ] Origin Verification: Messages from untrusted origins are rejected
  • [ ] Error Handling: Sensitive information is not leaked in error messages
  • [ ] Logging: Security events are logged for monitoring
  • [ ] HTTPS Only: All web content is served over HTTPS
  • [ ] CSP Headers: Content Security Policy headers are configured
  • [ ] Rate Limiting: Implement rate limiting for message processing

For High-Security Applications

  • [ ] Message Signing: Cryptographic signatures verify message authenticity
  • [ ] Replay Protection: Nonce-based system prevents message replay
  • [ ] Key Management: Secure key storage and rotation procedures
  • [ ] Audit Trail: Comprehensive logging of all security-relevant events
  • [ ] Penetration Testing: Security testing by qualified professionals

Common Security Pitfalls

1. Trusting Client-Side Validation

// BAD: Only client-side validation
// JavaScript: if (amount > 0) window.uniwebview.send('purchase', {amount});

// GOOD: Always validate server-side
webView.OnChannelMessageReceived += (view, message) => {
    if (message.action == "purchase") {
        var purchase = message.GetData<PurchaseData>();

        // Always validate on Unity side
        if (purchase.amount <= 0 || purchase.amount > 999.99m) {
            return UniWebViewChannelMessageResponse.Error("Invalid amount");
        }

        // Process valid purchase...
    }
    return null;
};

2. Exposing Internal APIs

// BAD: Exposing internal system functions
if (message.action == "executeSQL") {
    // Never allow direct database access from web content!
}

// GOOD: Provide safe, specific APIs
if (message.action == "getHighScores") {
    var scores = GetTopScores(limit: 10); // Safe, limited operation
    return UniWebViewChannelMessageResponse.Success(scores);
}

3. Information Leakage in Errors

// BAD: Detailed error messages
catch (System.Exception e) {
    return UniWebViewChannelMessageResponse.Error(e.ToString()); // Exposes stack traces!
}

// GOOD: Safe error messages
catch (System.Exception e) {
    Debug.LogError($"Channel message error: {e}"); // Log internally
    return UniWebViewChannelMessageResponse.Error("Operation failed"); // Safe public message
}

Remember: Security is a multi-layered approach. Implement appropriate measures based on your application's security requirements and threat model.