Authentication Flow
One of the benefits of the Senza platform is that each cloud connector device has a unique identifier, this helps you to implement device-based authentication that is completely transparent to the user. They won't need to bother entering a username and password, because simply having a device gives them the access they need. And you don't need to be concerned with password sharing, because every device can be bound to a specific user.
Authentication Flow
At a high level, the interaction between the client and the server works as follows:
- The client gets a client assertion from the Auth object and requests an auth token.
- The auth server validates the client assertion, decodes the device ID, and returns an access token.
- The client uses the access token when calling your backend API endpoints.
- When your backend server receives an API request, it looks up the device ID using the access token.
The rest of this page describes the flow in more detail.
Tutorials
If you prefer to learn by example, you can follow these tutorials which cover the complete process:
- Device Authentication
- QR Code Authentication
- Hello sample code
- Device Authentication video tutorial
Client Assertion
The first step is to call the Auth object's getClientAssertion()
method. It will return an object as below with the encrypted host platform assertion:
{
"hostplatform_assertion": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUtUjBCWjZRT1V2WHlIRUtoVWJWRTM4ZWdyanVYZ3pzUXByekNtV2h0dzAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL29hdXRoLWNvbmZpZy5zdHJlYW1pbmcuc3luYW1lZGlhLmNvbS9hdXRobiIsImF1ZCI6Imh0dHBzOi8vc2ctZ29sZGVuYmVycnkudnNzY2xvdWQudHY6OTQ0My9vYXV0aDIvdG9rZW4iLCJpYXQiOjE3MTY1NzYxMTQsImp0aSI6Ijk5NGRlZDk1LTc2NzgtNDA0ZS05MGM3LWY1ZjYxZWEyNzljOCIsInN1YiI6InVybjpzeW5hbWVkaWE6b2F1dGg6aWRlbnRpZmllcjpoeXBlcnNjYWxlOjdlNmQzN2MzMGQyMWFmMDQiLCJleHAiOjE3MTY2NjI1MTR9.iXE1MKVCPHvzfySw4xNuDZoHR__T98zgzbigVRKxiRcDRx4kylEdEYtXXk7km_1Vk4GyHSLikZPVXlIB-yt3pOR0AfABP3e0Fk_Dugbi2lfp0pD_in013gRayv8xjAdtMEClBf5HJ1ituqJ5wk83W-TqZLJwyZsFUYtiTfWEhvSWwLDnhYFrT7cRD5O4oMmZJQGZc9Ek3I1kGbnKmVvW8zZHq4-KzYXbg-XeP_v-e9G6IM2cMcT9qAqXpNHZKtTMZTxJtB2Ga0vAIq53c2desdPEiW7my9ssmLWKh0reBRzD06xmE2OVjwNt6TEJZ7jA_FG_gpLjYfx6YBFfJBEddQ"
}
You can then use the client assertion to request an access token from your auth server. The request should be in the following format, with the Content-Type
header as shown below and the client assertion included in the body.
async function getAccessToken(assertion) {
let response = await fetch("/auth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" },
body: "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=" + assertion
});
let json = await response.json();
return json.access_token;
}
The response will be a JSON object with an access_token
property containing the access token.
Authentication Simulator
You can test this part of the flow with the authentication simulator at https://7s97xn5ffd.execute-api.eu-west-1.amazonaws.com/production/auth/token. Here is an example of how to make a request on the command line. It will return a response with the string DUMMY ACCESS TOKEN
as the access token.
curl --location --request POST https://7s97xn5ffd.execute-api.eu-west-1.amazonaws.com/production/auth/token
--header 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer'
--data-urlencode 'assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6ImUtUjBCWjZRT1V2WHlIRUtoVWJWRTM4ZWdyanVYZ3pzUXByekNtV2h0dzAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL29hdXRoLWNvbmZpZy5oeXBlcnNjYWxlLmNvbGRzbm93Lm5ldC9hdXRobiIsImF1ZCI6Imh0dHBzOi8vc2ctZ29sZGVuYmVycnkudnNzY2xvdWQudHY6OTQ0My9vYXV0aDIvdG9rZW4iLCJpYXQiOjE2NTcxNzc1MjAsImp0aSI6IjIyNDYzMDQzLTIwZTAtNGQ3ZC1iODVhLTM0NTY4NTQxNDgzNCIsInN1YiI6InVybjpzeW5hbWVkaWE6b2F1dGg6aWRlbnRpZmllcjpoeXBlcnNjYWxlOlNZTkFIT01FLmFtb3R6LjExODkwYzVmLWExNDgtNDgyNi1hN2QzLTczNzJkYWM5ODUwZiIsImV4cCI6MTY1NzI2MzkyMH0.jcg5NnKGeT1poxYPJLu6f5Z-XqmzetGSGzBRaFLxav0VyK--hoh8ws1C3u4lN0qQkTzipAjcLBOkuUHQSNjPU8pl8-Wg-Eof3OjOmwkirakYlH9hPut_5ri8NTH-FMecfJq7OTO388ug0wsZB1pQ2ZMNN1xCXb0cTfV6u1eYQryTzwzrAXeF3h56GBGjd4MLq2D5QfIFFn8ktxmUuCm2dMofFduL2-vz_ay4LtGRXrLbddcyu9V3mFSv0ACSSZJBSVpfJ8mxBiGuysp8SbLqq3bCJiXBYWoWfmfdOPYkTsM9o2wjB3dUIS4OxRnfhTgU6a7B_t_svLRUZbW68l1j9g'
Auth Token Endpoint
Your authorization server should implement an /auth/token
endpoint that parses the client assertion into a JWT and returns an access token valid for the current device.
You can use a package like jwt-decode to decode the JWT, which will give you an object like this:
{
iss: 'https://oauth-config.streaming.synamedia.com/authn',
aud: 'https://sg-goldenberry.vsscloud.tv:9443/oauth2/token',
iat: 1716576114,
jti: '994ded95-7678-404e-90c7-f5f61ea279c8',
sub: 'urn:synamedia:oauth:identifier:hyperscale:7e6d37c30d21af04',
exp: 1716662514
}
Notice that the device ID is the last component in the sub
property.
You can validate that the client assertion is legitimate by checking that the aud
property is the same as the Auth Audience string entered in the Device Authentication section of the Tenant Configuration page.
See the JWT documentation for more information about the other properties.
Here is an example function that decodes and validates the JWT. Then it gets the device ID, creates an access token for that device ID, and returns the access token. This is a simplified example; see the implementation in the Hello sample code and the Device Authentication tutorial for a more complete version with security and error handling.
const decodeJwt = require("jwt-decode");
let tokenDevices = {};
app.post("/auth/token", async (req, res) => {
const clientAssertion = req.body.assertion;
const payload = decodeJwt(clientAssertion);
validateClientAssertion(payload);
const token = await generateAccessToken(payload);
res.status(200).json({"access_token":token, "token_type": "bearer", "expires_in": "XXX"});
});
function validateClientAssertion(payload) {
if (payload.aud != config.audience) {
throw new Error(`Invalid audience! Expected ${config.audience}, received ${payload.aud}`);
}
}
async function generateAccessToken(payload) {
let deviceId = getDeviceId(payload);
let accessToken = newAcccessToken();
tokenDevices[accessToken] = deviceId;
return accessToken;
}
function getDeviceId(payload) {
let subjects = payload.sub.split(":");
return subjects[subjects.length - 1];
}
Access Token
Once the client has obtained an access token from the _auth _server, it can use it in the Authorization
header while making requests to your API endpoints as follows:
async function hello() {
let response = await fetch("/hello", {
headers: { "Authorization": "Bearer " + accessToken }
});
let json = await response.json();
return json;
}
When your server receives a request, it can validate the request by checking the access token and looking up the device ID. Then it can use the device ID to access the user's personal information.
app.get('/hello', function (req, res) {
let deviceId = validateAccessToken(req);
let userInfo = getUserInfo(deviceId);
res.json(userInfo);
});
function validateAccessToken(req) {
let authorization = req.headers.authorization;
let accessToken = authorization.substring("Bearer ".length);
let deviceId = tokenDevices[accessToken];
return deviceId;
}
This is a highly simplified example, so see the links above for a more complete implementation.
User Database
You will want to maintain a database that associates device IDs with user accounts in your system, so that you can return data for the given user once you know which device the request is coming from. How you maintain this database depends on your operational process.
- Ideally, you can associate devices with users as part of the distribution process. When a user turns on their device, your app makes a request for an access token that gives the app permission to access the user's data.
- If you are not able to do that, one approach would be to display a QR code that lets the user authenticate using a mobile device. Once they have signed in successfully, you can create a record associating the device to the user in your database. See the QR Code Authentication tutorial for an example.
Conclusion
The examples above demonstrate the canonical flow for securely authenticating the client with the server and using an access token to make requests. When the server receives a request with that access token, it can be sure that it is coming from a particular device. Then you can return personal info for the owner of that device.
Updated about 2 months ago