Authentication

AppLooma RTC uses two credentials with two different lifetimes. A long-lived key pair authenticates your server to us. A short-lived token authenticates one participant, in one channel, for one session. Getting this split right is the whole of security on the platform.

The two-tier model#

CredentialWho holds itLifetimeWhat it grants
API key pair (ak_… / as_…)Your server onlyUntil you rotate itFull access to the project: issue any token, call every REST endpoint.
TokenOne client, one participant6 hoursJoin one named channel, with one identity and one role.
App IDAnyone, including your client appPermanentNothing on its own. It only identifies the project.

Never embed the API secret in an app

Mobile binaries can be unzipped and JavaScript bundles can be read. Anyone who extracts your secret can issue unlimited tokens on your account, join any channel, and spend your quota. There is no client-side use for the secret — not in React Native, not in a Flutter app, not behind obfuscation. Tokens are issued server-side, always.

Authenticating your server#

Every server-to-server call carries the key pair in two headers.

Headers on every REST call
x-applooma-key: ak_0f31c9a2b7d84e6f1c0a5b3e9d72f418
x-applooma-secret: as_9c4b1e07af35d2680b7c1f4ae83d59c2a70e1b6f

A missing header, an unknown key, a wrong secret or a deactivated key pair all return 401. The secret is compared against a stored hash — we never hold the plaintext, which is why it cannot be shown to you again after creation.

Building a token endpoint#

Your token endpoint is the gate. It is the one place where you decide who may join, under what identity, and with which role. Authenticate your own user first, then derive every token field from your own records — never from the request body.

1

Authenticate your user

Use whatever session or bearer token your app already uses. If the request is not signed in, return 401 and stop.

2

Decide identity, role and channel yourself

Take the identity from your own user record. Take the role from your own permission check. Only the channel usually comes from the client, and even that should be checked against what the user is allowed to join.

3

Request the token and return it

Return token and wsUrl to the client. Nothing else from the response needs to leave your server.

token-endpoint.js
app.post('/api/rtc-token', async (req, res) => {
const user = await authenticate(req); // your auth
if (!user) return res.status(401).json({ error: 'Sign in first' });
const channel = req.body.channel;
if (!(await user.mayJoin(channel))) { // your rules
return res.status(403).json({ error: 'Not allowed in this channel' });
}
const { token, wsUrl } = await applooma.createToken({
roomName: channel,
identity: user.id, // from YOUR record
role: user.isPresenter ? 'host' : 'audience', // from YOUR check
metadata: { name: user.displayName }, // public to everyone
});
res.json({ token, wsUrl });
});

What goes in a token#

FieldRequiredRules
roomNameYesThe channel. Must match [a-zA-Z0-9_-]{1,64}, otherwise 400.
identityYes1–128 characters. Must be unique in the channel: a second connection with the same identity replaces the first.
roleNoOne of host, cohost, audience. Defaults to host. Anything else is 400.
metadataNoA plain object, under 2048 bytes serialised. Arrays and non-objects are 400.

Metadata is public inside the channel

Metadata travels inside the token and is broadcast to every other participant. Put display names and avatars there. Do not put email addresses, phone numbers, internal flags or anything you would not show to every person in the call.

The service writes the project and the role into the metadata itself after your fields, so a client cannot claim a different project or a higher role by passing those keys.

Default role is host

If you omit role, the token is issued as host — able to publish and to administer the channel. Pass the role explicitly on every call so a listener never gets host rights by accident.

Token lifetime#

Tokens are valid for 6 hours. The response includes expiresAt, the absolute expiry time.

  • The expiry is checked when the participant joins. A session already connected is not cut off mid-call when its token passes 6 hours: the media server refreshes the credential in place, so there is no renew call to make from the client.
  • An expired token cannot be used to join or rejoin. Fetch a new one from your endpoint and join again.
  • Tokens are not refreshable and cannot be revoked individually. Keep them short-lived by fetching one per join, rather than caching one for the day.
Rejoin with a fresh token
async function joinWithFreshToken(channel) {
const res = await fetch('/api/rtc-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel }),
});
const { token, wsUrl } = await res.json();
await engine.joinChannel(token, wsUrl, { role: 'audience' });
}
// Do not cache the token across app launches — just call this again.

Rotating a key pair#

Rotate from the project's API keys page in the console. Rotation replaces the key and the secret on the same key-pair record, and the new secret is shown once.

Rotation takes effect immediately

The old key and secret stop working as soon as you rotate. Deploy the new values to your server's environment in the same change. If you need a gap-free switchover, create a second key pair on the project, move your servers to it, then deactivate the old one.

A key pair can also be deactivated without being rotated. A deactivated pair returns 401 on every call.

If a secret leaks

  • Rotate the affected key pair immediately, or deactivate it if you have another pair in place.
  • Deploy the new secret to every server that calls the API.
  • Check usage in the console for minutes you did not expect. See Usage and quotas.
  • Rotating does not end calls that are already connected. Existing tokens remain valid until they expire, up to 6 hours.
  • Email support@applooma.dev with your App ID if you see traffic you cannot account for.

Common mistakes#

MistakeWhat you seeFix
API secret shipped in the client appNothing, until someone else spends your quotaMove token issuance to your server. Rotate the exposed key pair.
Token endpoint with no authentication of your ownAnyone who can reach the URL can join any channelAuthenticate your user before issuing a token.
Identity taken from req.bodyUsers impersonate each other and disconnect each otherTake the identity from your own session record.
Role taken from req.bodyAn audience member asks for host and gets itDerive the role from your own permission check.
role omittedListeners can publish audio and videoPass role: 'audience' explicitly.
One token cached and reused by many usersParticipants kick each other out of the channelOne token per participant, with a unique identity.
Channel name with a space, colon or slash400 roomName must match [a-zA-Z0-9_-]{1,64}Slugify the name before sending it.
Sensitive data in metadataEvery participant in the channel can read itKeep metadata to display information only.
Token cached beyond 6 hoursJoin fails after the token expiresFetch a new token and rejoin.

Where to go next#