Introduction
In this guide, we will learn how to Authenticate and Authorize a C2C hardware device on a Frame.io Project.
What will I need?
If you haven’t read the Implementing C2C: Setting Up guide, give it a quick glance before moving on!
Additionally, you should have received a client_secret
from our team that will be used to identify your integration. If you have not received a client_secret
, please take a look at this introduction to the C2C ecosystem and reach out to our team.
If you received a client_id
instead of a client_secret
, then we have set you up as a C2C Application, rather than a hardware device, and you will either want to follow the C2C Application guide or reach out to our team to be issued a client_secret
instead.
Walking through the hardware auth flow
Let’s make sure we have a high-level understanding of the expected user experience for the Authorization Flow we want to implement. Check out the following resources to see this flow from the user’s point of view:
- Support Article for how to add new Hardware Devices.
- Training Video on how to authorize a Teredek Cube.
The hardware authorization flow is designed to lift as many details as possible off the implementer, and therefore the device UI. With this flow, you do not need to worry about:
- Redirecting to a web browser.
- Handling Frame.io user login / authentication
- Listing / selecting the account and project to connect to.
- Any UI elements beyond basic information displays.
Device identification (client_id)
When connecting to Camera to Cloud, each physical hardware device will need to identify itself uniquely.
For hardware devices, we call this the client_id
of the device. When setting up your implementation, you should think about how you want to identify a specific device. You could use a serial number of the hardware device, a UUID, or some uniquely identifying string.
Be careful not to leak personally identifying information. The user’s e-mail, for instance, is not a valid value to use as a client_id
.
Likewise, make sure you own the unique identifier. Do not use the device’s MAC Address, for instance. The MAC address is not owned by your software, and might also be considered personally identifying information.
If you’re not sure what value you would like to use, we can talk through this choice together and make sure a suitable value is chosen that makes integration as easy as possible.
Step 1: Requesting a device code
Let's start implementing. The first thing we need to is request a device code to give the user for a device. We do that by calling the /v2/auth/device/code
endpoint:
curl -X POST https://api.frame.io/v2/auth/device/code \
--header "x-client-version: 2.0.0" \
--form 'client_id=[client_id]' \
--form 'client_secret=[client_secret]' \
--form 'scope=asset_create offline' \
| python -m json.tool
Docs for /v2/auth/device/code
can be found here
We are using form data here rather than JSON data. The C2C authentication endpoints only accept form data. Once you are authenticated, other endpoints will accept JSON payload, but the authentication endpoints will return an error if JSON payloads are sent.
Let's take a closer look at the payload parameters:
client_id
: A unique identifier for the physical hardware device. This value needs to be guaranteed to be unique for the device. This could be a serial number or a randomly generated UUID.
client_secret
: This will be issued to you by Frame.io support and identifies the your device model. This value should be kept secret from the user, and should be encrypted at rest.
scope
: The permissions we are requesting, with spaces used as delimiters. Hardware devices can only request the following two scopes:
asset_create
: Allows the device to create and upload assets.offline
: Allows the device to refresh its own authorization using a refresh token. Authorization tokens expire after 8 hours, so without this scope a user would need to re-authorize their device every 8 hours.
In practice, devices will almost always want to request both scopes.
When we make the request, we will get a response similar to the following:
{
"device_code": "[device_code]",
"expires_in": 120,
"interval": 5,
"name": "MyDevice-[client_id]",
"user_code": "573131"
}
Let's break down this response:
device_code
: The device code should be hidden from the user, and is used to identify this authorization request when polling to see if the user has entered their code successfully.
expires_in
: The number of seconds until this code expires.
interval
: How long the we should wait between polling requests to see if the user has entered the code.
name
: The name of the device we are trying to connect.
user_code
: The six-digit code the user will enter into Frame.io to pair the device to a project.
Now we can display the user code and expiration time to the user to pair their device!
Putting the steps together
Now that we know the two calls we need to make, let's put them together into some python-like pseudocode. Remember, it's possible for our device code to expire, so we need to handle that possibility when setting up our logic:
def authorize_with_frame():
"""
Handles authorizing our device with Frame.io.
"""
# Our client ID can be a serial number, UUID, or some other unique string.
client_id = THIS_DEVICE.get_serial_number()
# Make the call to Frame.io to get our device codes.
pairing_codes = c2c.get_device_codes(client_id)
# We need to keep track of how long we have been polling for
polling_started = datetime.now()
# Now we are going to poll for authorization until the user enters the code.
while True:
# Re-write this ourput each time we poll: Note: This message will only update
# once per `interval` (5 seconds), so if a smooth countdown is desired, that
# will need a different implementation
print(
f"\rPAIRING CODE: {pairing_codes.user_code}"
f", EXPIRES IN: {pairing_codes.expired_in - (datetime.now() - polling_started)} seconds"
)
# Wait for `interval` before polling each time.
time.sleep(pairing_codes.interval)
try
# Make a call to Frame.io to see if the user has entered the code and authorized
# the device.
authorization, error = c2c.poll_for_authorization(
client_id, pairing_codes.device_code
)
except FrameioError as error:
if error.message == "authorization_pending":
# If the authorization is pending, try again.
continue
elif error.message == "expired_token":
# If the pairing codes have expired, then we need to generate new ones.
pairing_codes = c2c.get_device_codes(client_id)
elif error != None:
# If we get any other error, we should raise it. (advanced error handling will
# be covered in another tutorial)
raise error
return authorization
The errors guide is a great resource for a deeper dive on handling errors.
The last thing we should do is fetch the information about the project we have connected to from Frame.io and display it to the user for an extra layer of confirmation that the device was paired to the intended project. We'll show that in a later tutorial.
Troubleshooting
If you find yourself here then something has gone wrong! What would integrating with a third party be without some sort of error? This section lists a set of common issues and will walk you through the steps most likely to solve them. Take a look through the following list and see if anything matches your problem. The errors guide is also great resource for looking up API errors.
If you don’t find a solution here, we would love to hear the issue you ran into so we can add it here.
I don’t see a 'Connect Device' button: If you go to the C2C Management
panel and don't see a Connect Device
button, then one fo two things is happening:
- C2C is not enabled for your account: if the screen is blank and there is a message about C2C not being available for your account, the account manager needs to enable it for your project in the account settings!
- You are not a device manger: if the screen is blank and there is a message about not having permissions, then the account manager either needs to change the permissions for who is allowed to connect C2C devices, or add you into a role that has those permissions.
- You already have a device connected: After the first device is connected, the big blue
Add New Device
goes away, and instead you need to go to the three-dot menu in the upper-right hand corner of theC2C Connections
panel.
Invalid Client error: invalid_client
is returned when the information you are providing to us about the device does not match anything we have on record. This most likely means that your client_secret
is incorrect.
Bad Request error: bad_request
is returned when the request data is malformed in some way. Double check that you have not misspelled a field name or forgotten to add a required field.
I get a Slow Down
error when generating pairing codes: If your networking library is using TCP connection pooling (where the library re-uses a TCP connection across multiple requests), then any additional requests made to the get new pairing codes after the initial ones will return a Slow Down
error. This is a known bug that occurs when a TCP connection is reused to generate more than one pairing code. This error will be returned no matter how long it has been since the first request.
For now, pairing code requests should be made on their own TCP connection. This may mean spinning up a new HTTP client (or similar abstraction) to make pairing code requests.