Fork me on GitHub
Loading...
Searching...
No Matches
Authenticating the Janus API

By default no authentication is involved when using the Janus API. This means that the API is completely open, and that everybody can talk to Janus and its plugins and set up media connections. There are times, though, where limiting access to Janus may be desirable, e.g., when you want to prevent unauthorized users to join a service you created, or when you wrap the Janus API in your server and you want your application to be the only one to be able to interact with Janus from a messaging perspective.

There are a couple of ways to authenticate requests in Janus:

Stored token based authentication mechanism

The token based authentication mechanism expects all users to provide, in each request, a token string attribute: if this token is known to Janus, the request will be accepted, otherwise it will be rejected as an unauthorized response. Configuring the token based authentication mechanism is easy enough: you can do that either via the command line (-A or --token-auth ) or in the janus.jcfg configuration (token_auth value in the general section).

These tokens are completely opaque to Janus, meaning they can be pretty much anything that you want. Janus does not do any form of authorization/authentication itself: it's up to you to provide it with valid tokens users can use, e.g., as part of your server-side application handling users. You can add and remove tokens dynamically using the Admin/Monitor API, which means you will need to enable it if you want to use tokens, or otherwise all requests will fail (Janus will never have a valid token, so all requests will be rejected).

You add tokens using the add_token admin request, while you remove them using remove_token. You can also limit the scope of tokens to specific plugins, by passing a list of plugins to add_token or modifying the token properties via allow_token and disallow_token. By default (add_token without any plugin specified) Janus assumes a new token is allowed to access all plugins. A list of all the existing tokens can be retrieved with a list_tokens request.

Here are a couple of examples of how you can use the requests:

{
        "janus" : "add_token",
        "token": "a1b2c3d4",
        "transaction" : "sBJNyUhH6Vc6",
        "admin_secret": "adminpassword"
}

This adds a new token (a1b2c3d4) that is allowed to access all the plugins in Janus (no limitation provided in add_token ). To create a new token and limit the scope to a few selected plugins, you can use this other syntax instead (notice the extra plugins array):

{
        "janus" : "add_token",
        "token": "a1b2c3d4",
        "plugins": [
                "janus.plugin.streaming",
                "janus.plugin.videoroom"
        ],
        "transaction" : "sBJNyUhH6Vc6",
        "admin_secret": "adminpassword"
}

In this other example, we're creating a new token, and also telling Janus that the only plugins a user with this token can access are the Streaming and Videoroom plugins. An attempt to attach to a different plugin (e.g., EchoTest) will result in an error.

You can change the permissions a token has with respect to plugin access at any time. In the following example, we add a new plugin to the permissions for an existing token:

{
        "janus" : "allow_token",
        "token": "a1b2c3d4",
        "plugins": [
                "janus.plugin.echotest"
        ],
        "transaction" : "sBJNyUhH6Vc6",
        "admin_secret": "adminpassword"
}

This way, the provided token is now also allowed to access the EchoTest plugin. To remove a permission, the syntax is this one instead:

{
        "janus" : "disallow_token",
        "token": "a1b2c3d4",
        "plugins": [
                "janus.plugin.videoroom"
        ],
        "transaction" : "sBJNyUhH6Vc6",
        "admin_secret": "adminpassword"
}

To retrieve a list of all the valid tokens Janus is aware of, together with the plugins each of them is allowed to access, a list_tokens request can be used:

{
        "janus" : "list_tokens",
        "transaction" : "sBJNyUhH6Vc6",
        "admin_secret": "adminpassword"
}

Finally, you can get rid of a token using a remove_token request:

{
        "janus" : "remove_token",
        "token": "a1b2c3d4",
        "transaction" : "sBJNyUhH6Vc6",
        "admin_secret": "adminpassword"
}

As anticipated, with the token based mechanism enabled, all users will need to provide a valid token as part of their requests. This is done by adding a token attribute to the request root, e.g.:

{
        "janus" : "create",
        "transaction" : "sBJNyUhH6Vc6",
        "token": "usertoken"
}

The same applies for the long poll GET messages as well, which will need to contain the token as a query string parameter.

A valid token will mean the request will be accepted and processed normally. A missing or invalid token, instead, will result in an error being returned:

{
        "janus" : "error",
        "transaction" : "sBJNyUhH6Vc6",
        "error" : {
                "code" : 403,
                "reason" : "Unauthorized request (wrong or missing secret/token)"
        }
}

An attempt to use a valid token to attach to a plugin it is not allowed to access, instead, will result in a different error:

{
        "janus" : "error",
        "transaction" : "sBJNyUhH6Vc6",
        "error" : {
                "code" : 405,
                "reason" : "Provided token can't access plugin 'janus.plugin.echotest'"
        }
}

HMAC-Signed token authentication

NOTE WELL: At the time of writing, HMAC-Signed tokens are ONLY available for the VideoRoom plugin. If you need authentication for other plugins, you should use the Stored token based authentication mechanism instead.

Simple token based authentication requires the application host to continuously update the Janus instance on permission changes. Since Janus stores the tokens in memory, it can be problematic to guarantee the permissions of a dynamic application stay in sync with Janus.

This problem can be solved by using a type of nonce / lease system to let the application server generate automatically expiring tokens without requiring direct communication with or any data storage in Janus.

You can use the HMAC signed token mechanism by enabling token authentication in general, as above (-A or --token-auth) and specifying an encryption secret using --token-auth-secret. The same can be accomplished using token_auth and token_auth_secret in the general section of janus.jcfg.

With Signed token support enabled, dynamic token creation via the Admin/Monitor API is not supported. Instead, Janus will look for tokens with a format like:

<timestamp>,janus,<plugin1>[,plugin2...]:<signature>

Where timestamp is a UNIX timestamp (seconds since 0:00 UTC, 1.1.1970) that marks the point in time at which the token expires; plugin1 etc. are the bundle names of plugins (such as janus.plugin.videoroom); and signature is the base64-encoded HMAC-SHA1 signature of the expiry timestamp in ASCII format, hashed using the --token-auth-secret as a key.

The following function can be used to sign tokens using the node.js crypto library:

const crypto = require('crypto');
function getJanusToken(realm, data = [], timeout = 24 * 60 * 60) {
  const expiry = Math.floor(Date.now() / 1000) + timeout;

  const strdata = [expiry.toString(), realm, ...data].join(',');
  const hmac = crypto.createHmac('sha1', secret);
  hmac.setEncoding('base64');
  hmac.write(strdata);
  hmac.end();

  return [strdata, hmac.read()].join(':');
};

const token = getJanusToken('janus', ['janus.plugin.videoroom']);

The janus parameter here is the realm of the token. For authenticating the Janus API it should always be set to janus.

When Janus encounters a token, it will:

  • verify that the timestamp has not passed
  • verify that the signature matches the timestamp
  • if the request requires access to a plugin, verify that the signature allows access

Since the auth secret should never leave the application side, a signature like this can only be generated by the application server, which needs to be configured using the same secret.

Please note that tokens of this sort cannot be revoked after being signed and passed to the client. Instead of signing tokens with late expirys, it is recommended to use tokens with shorter durations and generate and transition to a new token within the expiry time of every last token when the lease time is unknown and security is critical.

Shared static secret

Several deployers showed an interest in wrapping the Janus API on their server side: this allows them to keep the interaction with their users the way it was before, while still benefiting from the features Janus provides. This is an easy enough step, as it just needs developers to relay the involved SDP, and implementing the Janus API messages to handle the logic.

That said, since in this case Janus would be contacted, through the API, just by a limited number of applications (e.g., application servers made in node.js, Ruby, Java Servlets or whatever) and not random browsers, it is reasonable to involve a mechanism to control who is allowed to contact and control it. The previous section described how you can exploit a token based mechanism for authenticating requests, but since in this case you only need a single application, or a limited set of them, to be able to talk to Janus, it's worthwhile to resort to something simpler and more static. To allow for that, Janus also exposes a shared API secret mechanism: that is, you configure Janus with a string applications need to present when sending requests, and if they don't, Janus rejects them with an unauthorized message.

Configuring the API secret mechanism is easy enough: you can do that either via the command line (-a or --apisecret ) or in the janus.jcfg configuration (api_secret value in the general section). When enabled, all requests addressed to that Janus instance MUST also contain an apisecret field in the Janus message headers. For instance, this message presented above would fail:

{
        "janus" : "create",
        "transaction" : "<random alphanumeric string>"
}
{
        "janus" : "error",
        "transaction" : "<same as request>"
        "error" : {
                "code" : 403,
                "reason" : "Unauthorized request (wrong or missing secret/token)"
        }
}

For a successful transaction, the message would have to look like this:

{
        "janus" : "create",
        "apisecret" : "<API secret configured in Janus>",
        "transaction" : "<random alphanumeric string>"
}

The same applies for the long poll GET messages as well, which will need to contain the apisecret as a query string parameter.