Security at Daily

At this point in 2020, we’ve all heard horror stories of unwanted guests dropping into video chats. We don’t wish that on anyone. We want everyone to feel confident that every call will be trespasser free, every time.

The WebRTC standard underpins our video chat APIs, requiring encryption across all communication channels. Audio, video, and screen sharing transmit over Secure Real-time Transport Protocol (SRTP) and data via Datagram Transport Layer Security (DTLS). Only encrypted connections can access our web servers, which run on the AWS cloud and use Amazon or letsencrypt.org tooling to generate TLS certificates that rotate every 90 days.

You can read more details about all the things that we do to make every call secure at daily.co/security.

While every Daily call is built on these standards, we know that some users need to implement additional controls to secure their calls.

This post explains three of those extra measures: adding safeguards to public room links, creating private rooms with tokens, and setting advanced host permissions.

Add safeguards to public rooms

With a public room, anybody with the room URL can join the call. Public rooms come in handy for:

  • Broadcasts and livestreams, when all are welcome to view the presentation [0].
  • Prototyping a call interface quickly, if you’re testing out custom UI changes.
  • Quick syncs with colleagues, where you don’t necessarily want to deal with meeting tokens (more on those later).

To minimize the likelihood that somebody could find or guess the URL, you can generate room links randomly with the Daily API, and use each link only once.

Generate unique, single-use room URLs with the Daily API

To create a randomized room URL via the Daily API, leave the name room configuration property empty.

Without a name, Daily generates a random 20 character alphanumeric string from a 36 character set. That equates to 36^20 possibilities as to what the room name could wind up being, making it nearly impossible for somebody to guess the URL.

This curl command sends a POST request to our /rooms endpoint without a room name:

curl --request POST \
     --url https://api.daily.co/v1/rooms \
     --header 'authorization: Bearer INSERT_YOUR_TOKEN_HERE' \
     --header 'content-type: application/json' 

If successful, you should see a response in your terminal including a randomly generated room name, for example:

{
    "id":"41fc9a57-00ed-401d-a1fb-0422fab07ac5",
    "name":"TyhAfU45KJMMnwP5S5sI",
    "api_created":true,
    "privacy":"public",
    "url":"https://your-domain.daily.co/TyhAfU45KJMMnwP5S5sI",
    "created_at":"2020-07-27T17:40:14.967Z",
    "config":{}
}

Notice how the room name is part of the URL. Test the link in your browser to be sure it works.

While this example request doesn’t specify room configuration, you can also set properties like exp to set the room to expire at some point in the not too distant future, making it even more secure. You could also set max_participants to limit the number of participants who can join a room in the first place.

If you prefer, you can also create rooms with randomly generated names from the Daily dashboard. Click the “Rooms” menu option in the left side navigation bar, then the “Create room” button in the top right corner. You’ll be taken to a “Create a new room” screen that notes that if you leave a room name blank, Daily will generate a random one for you:

Screenshot of Daily dashboard with instructions about randomly generated room names.
Leave a room name out when you’re creating rooms from the dashboard to let Daily generate a room name for you.

If you create a new room for every video call, you minimize the amount of time any room name is active, and, therefore, the amount of time a potential malevolent actor has to guess the URL. While a room URL is extremely difficult to guess, especially if Daily generates a random string for the room name, using links only once adds an extra layer of security.

Create private rooms with tokens

While there’s a time and place for public rooms, sometimes private rooms will be called for, like when:

  • Intuitive room names are needed, and you want to add a safeguard against someone guessing the room URL.
  • Ticketed events: participants need to pay to access the room.
  • Reusable rooms: the URL is the same, but there will be different guests each time.

Any time highly sensitive information will be discussed, we recommend a private room.

To create a private room, send a POST request to the /rooms Daily API endpoint, and set the privacy room property to "private":

curl --request POST \
     --url https://api.daily.co/v1/rooms \
     --header 'authorization: Bearer INSERT_YOUR_TOKEN_HERE' \
     --header 'content-type: application/json' \
     --data '{"name": "private-room-test", "privacy":"private"}' 

Hold onto the URL that you get back in the response. It should look something like:

https://your-domain.daily.co/private-room-test

Just like public rooms, you can add additional configuration properties, like the max_participants allowed to enter a room, for extra security. And, you can also create private rooms from the dashboard.

Unlike public rooms, however, where all a participant needs to join is a URL, a participant needs a meeting token to immediately be able to join a private room [1].

Meeting tokens

Overview

A meeting token controls who can access a room, and how they can participate once they get there. Daily meeting tokens are JSON Web Tokens (JWTs) that contain specific information about a participant, like the room_name they can join, their own user_name, whether they’ll be ejected after a certain amount of time, and many other options.

Create meeting tokens with a POST request to the Daily meeting-tokens API endpoint. Let’s use the private room created above, with room_name set to private-room-test:

curl --request POST \
     --url https://api.daily.co/v1/meeting-tokens \
     --header 'authorization: Bearer INSERT_YOUR_TOKEN_HERE' \
     --header 'content-type: application/json' \
     --data '{"properties": {"room_name":"private-room-test"}}' 

You should get a token as a response back:

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyIjoicHJpdmF0ZS1yb29tLXRlc3QiLCJkIjoiYTMwMjUxMTctYmViZS00ZTU1LWJlMjgtMmEyODFjNWI3YWIwIiwiaWF0IjoxNTk2MTM1MDc0fQ.6jzRSPuo1VlJiAnyewuWL67Egkcd1Y-N5zM8IHM_yHw"}

We can test this token quickly in the browser. But, first, let’s visit that room URL we created without using the token. Open a fresh private browsing window [2]. Paste in the URL as is, without a token. You should be greeted with a screen that tells you that you aren’t allowed to join the meeting.

Screenshot of call screen that says “You are not allowed to join this meeting.”
Participants without tokens will not be able to join the call.

Close that window. Now, append the token to the end of the URL, for example:

https://your-domain.daily.co/private-room-test?t=INSERT_TOKEN_HERE

Now, repeat the process of opening a new private browser window, but this time paste in the URL with the token. You should now be in a meeting:

Screenshot of Daily prebuilt UI meeting room
But your smiling face instead of mine should be in the corner, of course.

Advanced token settings

In the first example, we created a token for basic authentication. There might be occasions when you want to set advanced properties on a token, like how long token access lasts. You can do that through either timestamps or time limits on tokens.

Option 1: Set timestamps on tokens

If you reuse the room URL, you might want to ensure that participants can’t enter before a certain time, when other meetings might still be going on. To do that, you set the nbf token property to either the timestamp in UTC if you’re creating the room from the dashboard, or, if you’re creating the token with a POST request to the meeting-tokens API endpoint, you’ll use a Unix timestamp [3].

For example, let’s say you don’t want a participant to join a room before Friday, August 7, 2020 at 5pm UTC. Your curl command could look like:

curl --request POST \
     --url https://api.daily.co/v1/meeting-tokens \
     --header 'authorization: Bearer INSERT_YOUR_API_KEY_HERE' \
     --header 'content-type: application/json' \
     --data '{"properties":{"room_name":"private-room-test","nbf":1596819600}}'

You’ll receive a token in response that prevents the participant from joining the room until that time. You can similarly set the exp property to make sure the token expires after the meeting is over, so a person can’t rejoin the room. This is especially useful if you plan to have other meetings in the same room later.

If you’re using exp, you can also take advantage of eject_at_token_exp. Set the latter’s boolean property to true, and as soon as a participant’s token expires they will be booted from a call.

Option 2: Set time limits

Token time limits are helpful if you want to be sure there’s only a certain amount of time a participant should be able to access a room. For reused rooms, this ensures rooms are vacated before another meeting starts. Time limits don’t necessarily have to be used to immediately eject participants when their time is up, but that does make for a pretty funny three second demo:

Gif: participant ejected ten seconds after being in a meeting
Am I waving hello or goodbye? Who’s to say?

I configured this token to eject someone three seconds after they join by setting the eject_after_elapsed property to 3. To test that out yourself, your curl command might look like:

curl --request POST \
     --url https://api.daily.co/v1/meeting-tokens \
     --header 'authorization: Bearer INSERT_YOUR_API_KEY_HERE' \
     --header 'content-type: application/json' \
     --data '{"properties":{"room_name":"private-room-test","eject_after_elapsed":3}}'

I then joined the room URL as the owner of the room, so in that tab I wouldn't get kicked out. I joined in another tab with the eject_after_elapsed token appended to the URL. The gif above shows what I saw as a meeting owner, but here’s what I saw in my other tab as the ejected participant:

Screenshot of a Dailly call alerting a participant that the “Meeting has ended.”
Ejected participants see a screen that says the meeting has ended.

Now that we’ve covered how to set properties on tokens, we’re ready to build sophisticated meeting controls for hosts.

Set advanced host permissions

In some situations, meeting hosts need the ability to eject participants from a meeting at any time, making sure those attendees can’t join again. These types of strict access controls could be needed during:

  • Conferences or any ticketed, tiered, event where participants pay to access only a part of the experience.  
  • Classroom sessions, when repeated student disruptions could throw an entire lesson off course.  
  • Games, where ejecting participants is part of the fun!

The ability to eject participants takes a bit more time to set up, but it’s useful in any situation when a host requires advanced control over a meeting. To enable it, we need to do three things:

1) Create a token for the meeting owner. This ensures the host has permission to eject participants. This curl command probably looks familiar by now; just notice the different properties:

curl --request POST \
     --url https://api.daily.co/v1/meeting-tokens \
     --header 'authorization: Bearer INSERT_YOUR_API_KEY_HERE' \
     --header 'content-type: application/json' \
     --data '{"properties":{"is_owner": true, "room_name":"private-room-test"}}'

2) Create tokens for attendees that set eject_at_token_exp to false, and set those tokens to expire shortly after the meeting begins. That way, an attendee will remain in the meeting even after the token expires. But, when the host ejects them, their token will have expired and they won’t be able to rejoin the call:

curl --request POST \
     --url https://api.daily.co/v1/meeting-tokens \
     --header 'authorization: Bearer INSERT_YOUR_API_KEY_HERE' \
     --header 'content-type: application/json' \
     --data '{"properties":{"is_owner": true, "room_name":"private-room-test"}}'

3) In your application, give the host the ability, through a button or other interface, to call the updateParticipant method, passing in session_id and eject:true as parameters to eject a particular participant.

In our demo, we add a button next to each participant’s name that the host can click to kick out a participant:

<html>
  <head>
    <title>Simple eject sample code</title>
    <script src="https://unpkg.com/@daily-co/daily-js"></script>
  </head>
  <body onload="startCall()">
    <!-- Other elements here -->
    <iframe id="call-frame" allow="camera; microphone; autoplay"></iframe>
    <!-- Other elements here -->
    <div id="participants-list"></div>
    <!-- Other elements here -->
  </body>
  <script>
    async function updateEvent() {
      // Gets all call participants 
      let participants = callFrame.participants(); 
      // Our demo includes code here that changes styling based on the number of participants
      // ... 
      // Get the list of participants
      let wrapper = document.getElementById('participants-list');
      Object.keys(participants).forEach((p)) => {
        if (p === 'local') {
          return; // The local participant does not need to see their own name 
        }
        let participant = participants[p]; 
        // Add each participant's name, and a button to eject them, to the list 
        wrapper.innerHTML += `
            <div class="ui-participant-guest">
            <p>${participant.user_name || 'Guest'}</p>
              <img src="icon-eject.svg" alt="Kick user out of meeting"
                    onclick="callFrame.updateParticipant('${p}',{eject:true})" />
            </div>`;
        
      }); 
     // Our demo includes other code here that updates UI based on screenshare, etc. 
     // ... 
    }
    async function startCall() {
      // Other code here connects to your room and creates the callFrame...
      // ...
      callFrame
        .on('participant-joined', updateEvent)
      // You'll probably want lots of other event listeners; those go here 
    }
  </script> 
</html> 

And, here’s that demo in action, featuring me ejecting my Fear, Uncertainty, and Doubt from a call:

Gif: Host ejects participants from a Daily call.
With advanced token permissions and user interface tools, hosts can eject participants from calls.

Of course, this is only one of the ways this advanced host permission could be implemented. We’d love to hear how you build yours.

What's next?

If you’re interested in learning even more about meeting tokens and how they work, read Phil’s post on room access control.

As always, please reach out to us with any questions and comments.


[0] Read more about setting up 1:many meetings or live streaming with Daily.
[1] If you add members to your Daily account, it’s also possible for other members of your Daily team to enter a private room if that private room has the privacy property set to "org". If you’re using the prebuilt Daily UI, another option for guests outside your organization to access a private meeting is to set the room’s enable_knocking property to true. A meeting owner will need to be logged in the room to let anybody who knocks join.
[2] If you happen to be logged into your Daily account, your browser might think you’re the meeting “owner” and let you log into the room anyway.
[3] There are a few different tools for converting time to a Unix timestamp, like this one.

Oh, and by the way, if you found this photo from our blog directory, you might’ve noticed the image with the locks. We have @gaspanik on Unsplash to thank for that.