Shared Secret Authentication

Sometimes, relying on a central authorization service is unnecessary or unwanted. Some applications need a simpler setup with fewer moving parts. Others have security requirements where no third party can be trusted, not even the signaling service.

The shared secret model offers both simplicity and strong protection. The client and device are provisioned with the same secret, known only to them. They use this secret to sign and verify signaling messages directly between each other. The Nabto WebRTC Signaling Service only forwards these messages and has no ability to forge or validate them.

This creates a lightweight, decentralized system. Only peers that know the shared secret can communicate, ensuring that no external system can impersonate participants or tamper with the connection.

The shared secret is applied using HMAC-SHA256 signatures added to signaling messages. This proves to each side that the other holds the correct secret, providing strong mutual trust without relying on the signaling service.

How the shared secret is shared between the two peers is up to the application, typically part of the client/device pairing. Possible ways this pairing step could be completed:

  • Camera scans a QR code generated by the mobile app
  • Mobile app scans a static QR code printed on the camera
  • Secret shared over Bluetooth during local pairing
  • Secret exchanged on a trusted local network within a short time window (similar to WPS)
  • Secret provisioned via a secure cloud backend after both peers authenticate (for example, app and device each authenticate to the backend, which then issues matching secrets)

Depending on the specific design, the shared secret can be pre-provisioned per device during manufacturing or distribution. The pairing step then securely transfers that unique secret to the client.

While conceptually simple, the shared secret authentication approach comes with some shortcomings: Especially for browser based applications, it is difficult to establish a good user experience as you cannot e.g. scan a QR code or use Bluetooth for sharing the secret. Also, the decentral nature is not always desirable, especially if you have an existing user/device management system. For such scenarios, centralized authorization is a better solution.

Using Shared Secret Authentication in Applications

The Nabto WebRTC SDKs expects the application to provide the shared secret to use for the authentication. The secret is provided as any string, however, the strong protection offered by this model relies on the secret to be generated randomly and with a sufficient length. Nabto recommends using 256 bit random data encoded to a string using hex. Such a string can be generated using openSSL like this:

$ openssl rand -hex 32

Client applications

To use the shared secret approach, specify SHARED_SECRET as security mode when creating the message transport that implements the signaling channel:

import NabtoWebRTC
import NabtoWebRTCUtil

func connect(
    observer: MessageTransportObserver,
    productId: String,
    deviceId: String,
    sharedSecret: String
) async {
    let signalingClient = createSignalingClient(
        SignalingClientOptions(
            productId: productId,
            deviceId: deviceId
        )
    )

    do {
        try await signalingClient.start()
        
        let messageTransport = try await createClientMessageTransport(
            client: signalingClient,
            options: .sharedSecret(sharedSecret: sharedSecret)
        )
        await messageTransport.addObserver(observer)
    } catch {
        print(error)
    }
}

var clientOptions = ...
var sharedSecret = ...
var client = SignalingClientFactory.createSignalingClient(clientOptions);
var messageTransport = ClientMessageTransport.createSharedSecretMessageTransport(client, sharedSecret);

// Example on how to create a Client Message Transport which uses a shared secret.
const productId = ...
const deviceId = ...
const sharedSecret = ...

const signalingClient = createSignalingClient({
  productId: productId,
  deviceId: deviceId,
});
const messageTransport = createClientMessageTransport(signalingClient, {
  securityMode: ClientMessageTransportSecurityMode.SHARED_SECRET,
  sharedSecret: sharedSecret,
});

Also pass the actual shared secret as input. The Nabto WebRTC MessageTransport implementation encodes signaling messages and sign them as per the Nabto WebRTC Signaling protocol specification.

Device (camera) applications

Building on the Device Applications section to start a device using Nabto WebRTC Device SDK, shared secret authentication can be implemented like so:

function handleNewChannel(channel, authorized) {
  this.messageTransport = createDeviceMessageTransport(signalingDevice, channel, { securityMode: DeviceMessageTransportSecurityMode.SHARED_SECRET, sharedSecretCallback: async (keyId) => { return sharedSecret; } });
  this.messageTransport.on("setupdone", async (iceServers) => {
      const pc = new RTCPeerConnection({ iceServers: iceServers });
      new PerfectNegotiation(pc, this.messageTransport);
      new SignalingEventHandler(pc, signalingDevice);
      const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
      stream.getTracks().forEach((track) => {
          pc.addTrack(track);
      })
  })
}

int handleNewChannel(SignalingDevicePtr device,
                     nabto::example::H264TrackHandler trackHandler,
                     SignalingChannelPtr channel, bool authorized) {
  std::string sharedSecret = "the-shared-secret"; // Generated by you.
  nabto::webrtc::util::MessageTransportPtr transport =
      nabto::webrtc::util::MessageTransportFactory::createSharedSecretTransport(
          device, channel,
          [&sharedSecret](const std::string keyId) -> std::string {
            return sharedSecret;
          });

  auto webConn = nabto::example::WebrtcConnection::create(
      device, channel, transport, trackHandler);
}

If instead you use the basis device apps, you pass the shared secret on the commandline as follows (for the RTSP bridge application):

$ webrtc_device_rtsp -d ${deviceId} -p ${productId} -k key.pem --secret ${sharedSecret} \
   --rtsp-url rtsp://127.0.0.1:8554/video

Custom shared secret models

The shared secret authentication model can be extended to support more advanced authorization and deployment scenarios. By supporting multiple secrets and identifying them using key ids (keyId), a single device can securely interact with many clients using different secrets, while keeping the protocol lightweight and decentralized.

Possible extensions include:

  • Per-client secrets: Each client is provisioned with its own secret, allowing individual revocation, tracking and access control.
  • Role-based secrets: Clients with different roles (e.g. admin, user) use different secrets, enabling permission separation.
  • Public/private key signatures: Instead of shared secrets, asymmetric key pairs or certificate chains can be used for stronger identity guarantees.
  • Application-defined schemes: Protocols such as Noise or other domain-specific models can be integrated.

Using keyId for multiple secrets

When using shared secret authentication, the MessageTransport encodes all messages as JWT. If multiple secrets exist, the client can add the JWT Key ID field (kid). The device uses this to determine which secret to use for signature verification.

To support this mechanism, the Nabto WebRTC device SDK invokes a callback into the application whenever a shared secret is needed. The application receives the keyId and must respond with the corresponding secret. This allows the application to implement any desired key lookup or policy logic, including:

  • Looking up secrets from in-memory maps, local databases or secure storage
  • Integrating with key management systems or secure elements
  • Enforcing per-user access control or key revocation policies

The Shared Secret Message Transport handles all cryptographic validation internally once the application has supplied the appropriate secret. This structure allows developers to retain full control over secret management while relying on the SDK to handle message encoding, verification and connection setup.