Skip to main content

Server

We will start our journey by creating a simple backend using ExpressJS. The main goal is to authenticate in VPaaS and make REST API requests to generate meetings and participants with access to those meetings.

You can download the starter code from Step.01-Exercise-Server.

Register the application in VPaaS

The first step is to register our application with the VPaaS service. Once registered, we will obtain a clientId.

First, we will create a key pair consisting of a private key and a public key:

$ openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:4096 -out privateKey.pem
info

Keep the privateKey.pem in a secure location and never share it with anybody. It will be used to authenticate the application against the Pexip VPaaS service.

The minimum key size for VPaaS is 4096 bits.

The following step will extract the public key into a separate file:

$ openssl pkey -in privateKey.pem -pubout > publicKey.pem

Now you will need to contact Pexip and send the publicKey.pem file. Pexip will register the application and, once the application is registered, you will be assigned a clientId.

To authenticate your backend into the VPaaS service you will need both the privateKey.pem and the clientId.

Configuration file

You need to define the configuration file in ./server/config/default.json. The application will read this file and use the parameters for authenticating into VPaaS.

Here is an example of the configuration file in which you will need to modify at least the clientId:

{
"vpaas": {
"apiAddress": "https://crud.pexip.rocks",
"credentials": {
"clientId": "euopeke6qei65m5prjcvnqv2lq",
"privateKeyPath": "./config/credentials/privateKey.pem"
}
},
"server": {
"port": 3000
}
}

The meaning of these parameters is the following:

  • apiAddress: URL address for the VPaaS API.
  • clientId: Id generated after registering the app into VPaaS.
  • privateKeyPath: Path to where the private key is located.
  • port: The port to bind to the server.

Create the JWT and generate an access token

Now we will implement our first function to authenticate into the VPaaS service.

We will open the file ./server/src/index.ts and define a function to generate a JWT (JSON Web Token). This token will be signed with the private key and will contain the clientId, the expiration time and the scope with the permissions that we want to use.

First of all, we need to make some imports:

import express, {type RequestHandler} from 'express';
import {v4 as uuidv4} from 'uuid';
import jwt from 'jsonwebtoken';
import {createApi, withToken} from '@pexip/vpaas-api';

And now we implement the function:

const createJwt = (): string => {
const apiAddress: string = config.get('vpaas.apiAddress');
const authEndpoint = `${apiAddress}/oauth/token`;
const clientId = config.get('vpaas.credentials.clientId');
const privateKey = fs.readFileSync(
config.get('vpaas.credentials.privateKeyPath'),
);

const scope = [
'meeting:create',
'meeting:read',
'meeting:write',
'participant:create',
'participant:read',
'participant:write',
];
const requestId = uuidv4();

const token = jwt.sign(
{
iss: clientId, // Application Client UUID
sub: clientId, // Application Client UUID
aud: authEndpoint,
scope: scope.join(' '),
},
privateKey,
{
algorithm: 'RS384',
expiresIn: '60s',
jwtid: requestId,
},
);

return token;
};

Once a JWT is created, our server will send it to VPaaS to create an access_token. Finally, the access_token will be used to authenticate each request that our server sends to VPaaS.

To simplify the authentication process, we will make use of the @pexip/vpaas-api library. This library is an abstraction layer that will take care of a lot of things for us.

We will use two functions from the @pexip/vpaas-api library: withToken and createApi. The function withToken will take as parameters the function that we have previously created (createJWT) and the VPaaS URL.

const api = withToken(createJwt, config.get('vpaas.apiAddress'))(createApi());

With this piece of code our server will be able to authenticate into VPaaS and use the API through the api object. When the access_token expires, the library will generate a new token automatically.

HTTP endpoints

We have implemented authentication into VPaaS and the api object. Now we will continue editing the file ./server/src/index.ts and implement three HTTP endpoints:

  • Get ApiAddress: This will be used to return the VPaaS URL that is defined in the configuration file.
  • Create Meeting: This endpoint will create an ephemeral meeting in the VPaaS service. . It will be removed automatically once the last participant leaves.
  • Create Participant for a Meeting: This is used to create one-time credentials to allow access to a meeting.

During the rest of this lesson we will explore more deeply what each of these endpoints does.

info

Define all the endpoints after the the middleware definition for CORS app.use(cors({...})). If you don't do this, the cross-site calls to the server will fail.

Before implementing the endpoints we need to import the RequestHandler:

import express, {type RequestHandler} from 'express';

Get the ApiAddress

Our frontend application will connect to two different servers: our server and VPaaS. This means that it needs to know two different URLs.

We have two ways of defining these URLs:

  • Defining the two URLs in a configuration file in the frontend.
  • Defining only our server URL in the frontend and make a request to recover the VPaaS URL.

If we choose the first option, this endpoint is not needed, but we would have to define the VPaaS URL both in the backend and in the frontend.

app.get('/api-address', (async (req, res) => {
res.send(config.get('vpaas.apiAddress'));
}) as RequestHandler);

A call to this endpoint will return the VPaaS URL, e.g.:

https://crud.pexip.rocks

Your VPaaS URL may be different, dependent on the region chosen when you registered your application with the VPaaS service.

Create meeting

In VPaaS meetings are created on-demand and, once the last participant leaves, the meeting is removed.

To create a meeting we use the function api.create() and return the response to the frontend:

app.post('/meetings', (async (req, res) => {
try {
const response = await api.create();
if (response.status === 200) {
return res.json(response.data);
} else {
return res.status(500).send('Cannot create the meeting');
}
} catch (error) {
return res.status(500).send('Cannot create the meeting');
}
}) as RequestHandler);

The response to this request will have the following structure:

{
"id": "mzcmlqfurei65dh2cimd3aag5q",
"status": "ready",
"created_at": "2024-01-16T16:07:51.436736",
"updated_at": "2024-01-16T16:07:51.436742"
}

This endpoint should be only used to create the meeting. After that, the same id should be shared with the rest of the participants.

Create participant for meeting

In the previous section we developed an endpoint to create a meeting.

However, before a user can join the meeting, they have to create a participant. A participant is a one-time credential that is used to join a specific meeting.

In the same way as before, we will use the @pexip/vpaas-api library and in particular the api.participants({meetingId: string}) function. The value of the meetingId input parameter is the id that we generated in the previous section:

app.post('/meetings/:meetingId/participants', (async (req, res) => {
try {
const response = await api.participants({meetingId: req.params.meetingId});
if (response.status === 200) {
return res.json(response.data);
} else {
return res
.status(500)
.send(
`Cannot get participants from the meeting ${req.params.meetingId}`,
);
}
} catch (error) {
return res
.status(500)
.send(`Cannot get participants from the meeting ${req.params.meetingId}`);
}
}) as RequestHandler);

The result of this call will have the following structure:

{
"meeting_id": "mzcmlqfurei65dh2cimd3aag5q",
"id": "qnol2zvurei65axj5zspbecjne",
"participant_secret": "MMh8jo7t4DE-Eg2L1hULA7Gc6sEZW_Go0PpuGODZeuM",
"status": "ready",
"created_at": "2024-01-16T16:08:40.651578",
"updated_at": "2024-01-16T16:08:40.651585"
}

Through this call, the user will get the id of the participant and the participant_secret that he must use to join the meeting.

Run the server

After all these modifications we have completed the implementation of a simple server.

In the next lessons we will work on the frontend, and will use the endpoints that we have just developed.

Now that everything is in place, we can run the server through the following command:

cd server
npm start

We have several ways to test the server, we can use a graphical interface such as Postman, but the faster way is to use cURL.

  • Get the API address:

    $ curl https://localhost:3000/api-address --insecure
  • Create a meeting:

    $ curl -X POST https://localhost:3000/meetings --insecure
  • Create a participant for the previous meeting:

    $ curl -X POST https://localhost:3000/meetings/<meetingId>/participants --insecure

Now that our backend is ready, we will continue developing our application frontend.

You can compare your code with the solution in Step.01-Solution-Server. You can also check the differences with the previous lesson in the git diff.