Setting Up Twilio Video Integration
This use case describes how we created a video conferencing integration for platformOS using Twilio.
To create the integration, first we needed to connect platformOS and Twilio. There are many tools available to connect to Twilio and get the API Keys to generate the JSON Web Token (JWT) needed for Twilio's video services. As we would need to interact with the Twilio API on user events later in our module and the best and most efficient way to do this would be directly from platformOS, we decided to learn the ropes and use all platformOS had to offer from the start.
Problem/situation
First, we needed to get and store all of the API Keys necessary to authorize the connection. We could get some of them from the platformOS Partner Portal after adding the Twilio integration as detailed in the topic Managing Integrations. To use other API resources, we created a form and listened to pOS logs in our CLI. Then we created a JWT (JSON Web Token) that would allow the connection to take place.
Challenges
Who likes a challenge? I love a good challenge but am not so fond of problems. Luckily with platformOS, challenges are made easier and problems are few:
-
Retrieve the Twilio Secret
platformOS provides us with Twilio's Twilio Account SID called
sid
and the API Key SID calledauth_token
. But we must get the API Key Secret ourselves. -
Storing the API Keys
Once we have all the API keys, we need a place to store them securely so that we can access them later when making the connection to Twilio's video services.
-
Preparing and encoding a JWT
We needed to create a JWT encoded token that matches Twilio's Access Token format to make and persist the connection to a Twilio video
room
.
Solution
Everything we needed to succeed was provided by platformOS. Our process consisted of the following five steps:
Step 1: Fetch the secret
with an API call notification
As discussed above, there are many tools to interact with an API: Curl, node.js, C#, PHP, Ruby, Python, Java and more. Below is a simple use case using Curl.
Create API Key Secret with Curl
Replace ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX with your Twilio sid
and your auth_token
from the platformOS Partner Portal: select the Instance your are working on, click Update configuration and then Edit to view the Twilio API Keys.
curl -X POST https://api.twilio.com/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Keys.json \
-u ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:your_auth_token
We could get the API Secret using many tools but we decided to get it using platformOS. When creating a module, we wanted to do things like create rooms, recordings, compositions and more from inside the context of the module itself.
To do this, we needed to connect directly to Twilio from platformOS.
Create API Key Secret with platformOS - GraphQL GUI
Here, we created an api_call_notification
and then used the pos-cli gui serve <environment>
CLI command to send and receive the response.
-
Create an API Call Notification
To create our API Key Secret we send an API call with a request type of
POST
to the URL and with the headers outlined in the Twilio Identity and Access Management documentation.app/api_calls/twilio_api_secret_create.liquid
--- format: http request_headers: |- {%- assign authBasic = context.constants.TWILIO_API_ACCOUNT_SID | append: ":" | append: context.constants.TWILIO_API_KEY_SID | base64_encode -%} { "Content-Type": "application/json", "Authorization": "Basic {{ authBasic }}" } request_type: POST to: https://api.twilio.com/2010-04-01/Accounts/{{ context.constants.TWILIO_API_ACCOUNT_SID }}/Keys.json ---
-
Send and receive the response
To send the API call notification and receive the response we used the
pos-cli gui serve <environment>
command. We saw the response returned with our API Key Secret.mutation createSecret { api_call_send(template: { name: "twilio_api_secret_create" }) { response { body } } }
Fetch API Key resource with platformOS - Form configuration
Here, we were not creating another API Key(Secret) — we were just receiving a representation of the API Key resource that was created. This resource itself is not that helpful, but the process illustrates the workflow when connecting to API resources with a form configuration.
-
Create a page
First, we needed a page to hold a form.
app/views/pages/twilio-api.html.liquid
<h3>Get Keys</h3> {% include_form 'twilio_api_call' %}
-
Create a virtual record
To be able to submit a form you need to have a
table
connected to it. In this case we do not need any data, so we can create what we call avirtual_record
like below. This allows for a form with no data or properties to be submitted.app/schema/virtual_record.yml
name: virtual_record
-
Create a form
The simplest of forms, a button with a resource of our
virtual_record
schema and anapi_call_notification
that will be triggered on submit.--- name: twilio_api_call resource: virtual_record api_call_notifications: - twilio_api_secret_create --- {% form %} <button>API Call</button> {% endform %}
-
Create an API Call Notification
Using the API resource was now just a matter of formatting the notification to meet the requirements of the API and resource we were calling. Twilio Identity and Access Management documentation
app/api_calls/twilio_api_secret_create.liquid
--- name: twilio_api_secret_create format: http request_headers: |- {%- assign authBasic = context.constants.TWILIO_ACCOUNT_SID | append: ":" | append: context.constants.TWILIO_ACCOUNT_AUTH_TOKEN | base64_encode -%} { "Content-Type": "application/json", "Authorization": "Basic {{ authBasic }}" } request_type: GET to: |- https://api.twilio.com/2010-04-01/Accounts/{{ context.constants.TWILIO_ACCOUNT_SID }}/Keys/{{ context.constants.TWILIO_API_KEY }}.json callback: |- {%- log response.body, type: 'info' -%} ---
Note
Logs should be removed from production instances to reduce noise. If absolutely necessary then be sure to add a relevent prefix in the log
type
eg.type: "myModule - info"
. This will help you find the logs you are looking for. -
View the response
We clicked the button that submits the form that makes an API call notification but what happened to our API Call Response? No worries, we just needed to go to the CLI and use the
pos-cli logs <environment>
command to get the response returned as typeinfo
that we declared in the callback above.Note
For security reasons, the Secret field is ONLY returned when the API Key is first created – never when fetching the resource.
Step 2: Store the API Keys with custom context.constants
We created custom context.constants
for each of the keys we need to store and access securely. To do this we used the GraphQL GUI with the pos-cli gui serve
command.
Set Constants
mutation SetConstant {
constant_set(name: "TWILIO_API_KEY_SID", value: "123456789") {
name
value
}
}
Repeat for:
TWILIO_API_KEY_SID
TWILIO_API_ACCOUNT_SID
TWILIO_API_SECRET
Get Constants
We used a simple piece of Liquid markup to access our constants from the context
object.
{{ context.constants.TWILIO_API_KEY_SID }}
Note
Constants need to be explicitly called {{ context.constants }}
, as they are hidden from {{ context }}
. More info: context.constants
Step 3: Using platformOS Liquid filters to prepare, encode and render the JWT
-
Setup the URL
First, we needed a place to put our code and a URL to expose the JWT for authentication. We did this in pages and because we wanted it to have the JSON format, we named the file
token.json.liquid
.We added
slug
to create the URL — in this case the token. We could have also addedformat
here, but it was not necessary because we used .json in our file name.app/pages/token.json.liquid
--- slug: token format: json ---
-
Prepare the payload
Many things are standard for the JSON Web Token (JWT), but many have their own properties as does the Twilio JWT.
Assign Liquid timestamp variables
We needed to be able to tell Twilio the earliest possible time the token can be used and the time when the token should expire. We did this using Liquid date filters:
date
for date format,now
for time now andadd_to_time
to add the length of time before the token expires.Assign identity and room variables
- Identity Grant -
identity
is mandatory. It is a unique identifier which indicates the holder of the Access Token can access Programmable Video services. - Room Grant -
room
is optional and used to grant use of a specific Room name or SID, which indicates the holder of the Access Token may only connect to the indicated Room.
- Identity Grant -
Payload
The list below describes all the parameters we needed to fill to create the Token. Where we used parse_json
to create a Hash from these that will be used when encoding the JWT token:
jti
is a unique identifier for the token. Your application can choose this identifier. The default helper library implementation includes the Sid of the API Key SID used to generate the token, and a unique random string.iss
is the issuer - the API Key SID whose secret signs the token.sub
is the Twilio Account SID of the account to which access is scoped.nbf
is the timestamp on which the token was generated.exp
is the timestamp on which the token will expire. Tokens have a maximum age of 24 hours.grants
is the list of granted permissions the token has. Client SDK (Chat, Video) grant values will vary from SDK to SDK.
{% liquid
assign now_timestamp = 'now' | date: '%s'
assign exp_timestamp = 'now' | add_to_time: 1 | date: '%s'
assign identity = 'unique-Id-Name'
assign room = ''
%}
{%- parse_json payload -%}
{
"jti": "{{ context.constants.TWILIO_API_KEY_SID }}-{{ now_timestamp }}",
"iss": "{{ context.constants.TWILIO_API_KEY_SID }}",
"sub": "{{ context.constants.TWILIO_ACCOUNT_SID }}",
"nbf": {{ now_timestamp }},
"exp": {{ exp_timestamp }},
"grants": {
"identity": "{{ identity }}",
"video": {
"room": "{{ room }}"
}
}
}
{%- endparse_json -%}
Step 4: Encode the JSON Web Token
Now we had the variable and payload ready. We could encode payload to a JSON Web Token using the jwt_encode Liquid filter. To do this we needed to know the Algorithm to use for encryption (in this case HS256
), the Secret constant we created earlier context.constants.TWILIO_API_SECRET
and a Custom Header of {"cty": "twilio-fpa;v=1"}
where we again used parse_json
to create a Hash from our JSON.
{% parse_json headers %}
{
"cty": "twilio-fpa;v=1"
}
{% endparse_json %}
{{ payload | jwt_encode: 'HS256', context.constants.TWILIO_API_SECRET, headers }}
Step 5: JSON authentication
As a last step, we needed to configure the JSON used for authentication that will include our Twilio JWT token.
For this, Twilio requires identity
and token
in JSON format.
{
"identity": "{{ identity }}",
"token": "{{ payload | jwt_encode: 'HS256', context.constants.TWILIO_API_SECRET, headers }}"
}
Results
In platformOS, we had all the tools we needed to connect to Twilio's API. No need for man in the middle or third-party tools and scripts that add to the weight of our application and increase the list of topics to learn about. We were able to create a lightweight, server-side rendered and fully customizable integration. Everything we needed, nothing we did not.
Read more
- Twilio's Programmable video
- Twilio - API Key Resources
- Twilio - User Identity & Access Tokens
- platformOS - Form Configuration
- platformOS - CLI
- JWT
Author information
Daniel Telfer
Frontend Developer, Digital Fuel