A video token is a signed note. It says who you are, which room you may enter, and what you may do there. The media server trusts it because it carries a signature only your secret can produce.
Whoever holds that secret can write any note they like. That includes one that joins your customer's board meeting as a participant nobody can see, and records it. This is why video SDK token auth is a security decision before it is an integration step.
The integration guide covers the mechanics: your backend mints tokens, the client never sees the secret. This post covers what goes wrong when that rule is broken, and the five decisions a token service has to get right when it isn't.
What the Secret Can Sign
Tokens are usually JWTs signed with the provider's API secret. The secret doesn't open one room; it can produce a token for any room, any identity and any permission the platform supports.
LiveKit's tokens and grants reference is a useful list because it is explicit. A token can carry, among others:
Grant | What it allows |
|---|---|
roomJoin + room | Join a named room |
canPublish, canPublishSources | Send audio, video or screen share, optionally limited by source |
canSubscribe | Receive everyone else's media |
roomList, roomCreate | List and create rooms |
roomAdmin | Moderate a room: remove people, mute tracks |
roomRecord | Start recordings and exports |
hidden | Join without appearing in the participant list |
Read that table as an attacker would. A secret that reaches the browser lets anyone list your rooms, join any of them hidden, and record what they hear. Other platforms name the permissions differently, but every one signs tokens with a secret and trusts whatever the signature covers.
What Ships When the Secret Ships
Anything in a JavaScript bundle is public. Minification doesn't hide a string, and environment variables baked in at build time end up in the file the browser downloads. A secret in the client is a secret on every user's machine.
Be precise about which value matters. Many platforms hand out two things: an identifier (an app ID or API key) and a secret. The identifier often appears in client code by design. The secret must not. Agora shows the difference clearly: its projects can run with an App ID alone, and Agora's project documentation recommends App ID plus token, saying production should use token authentication rather than App ID only.
A leaked secret costs more than a leaked password, for two reasons:
- Revoking it ends every session at once. Tokens signed with the old secret stop verifying, so legitimate users are cut off along with the attacker.
- Rotation is a release. If the secret lives in client code, replacing it means shipping a new build to every web, iOS and Android user, while old builds keep the leaked one.
The Token Service: Five Decisions
The fix is a small backend endpoint. The client asks for a token, the server decides, signs and returns it. Each request answers five questions.
1. Who is asking?
Authenticate the user with your own identity system first, then put that identity in the token. Never take the identity from the request body. If a client can send identity: "ceo@customer.com" and get a token for it, you have built impersonation as a service.
2. Do they belong in this room?
The room name in the request is a claim, not a fact. Check it against your own data: is this user invited to this meeting, a member of this support queue, assigned to this case? Room names are often guessable (team-standup, sequential IDs), so the check matters.
3. What may they do?
Derive permissions from role, never from the request:
Role | Grants |
|---|---|
Attendee | join, publish, subscribe |
Viewer (webinar, monitoring wall) | join, subscribe; no publish |
Host | the above plus room admin |
Recording or AI bot | its own service identity, only the grants it needs |
Least privilege here has a concrete payoff: a viewer token that leaks lets someone watch one room, not broadcast into it. Keep hidden for service participants such as recorders, never for people.
4. How long should it live?
This is the decision with a real trade-off. Short tokens limit damage; long tokens survive reconnection.
On security, the lifetime is your revocation window. LiveKit's guide to removing participants notes that removing someone from a room does not invalidate their token, and recommends short TTLs and not issuing that person a new token. Your token service is the kill switch: when it stops issuing tokens to someone, they are locked out once their current token expires.
On reliability, a reconnect has to present a valid token. Twilio lists an expired token as a reason reconnection fails in its reconnection documentation, and recommends tokens valid for the maximum session length, which its access token documentation caps at 24 hours. LiveKit takes the other route: the server refreshes tokens for connected clients, so a short lifetime only constrains the first join.
So the answer depends on the platform. With server-side refresh, use minutes. Without it, either have the client fetch a fresh token before expiry, or accept a lifetime as long as your longest realistic call and compensate with tight room and role checks. The failure on the other side is covered in keeping a call alive when the network drops.
5. If you verify tokens yourself, verify them properly
The media server verifies the provider's tokens. But teams often reuse the pattern: a recording worker, a webhook receiver or an internal API that accepts JWTs. Those are relying parties, and RFC 8725, the JWT best-practice document, sets the rules: accept only the algorithms you expect, check that the signing key belongs to the issuer, and require and validate the audience claim.
Where the Secret Lives
On the server, the secret belongs in a secret manager or the deployment's environment, never in the repository. Use one secret per environment, so a leaked staging key can't mint production tokens. Make rotation a configuration change you have practised: if your platform allows more than one active key, add the new one, move the token service to it, then retire the old one.
Keep exactly one place that signs. Teams that put one token endpoint behind your own interface get this for free: one service, one secret, one audit log of who was issued what. That log is what a security review will ask for.
A minimal endpoint, using LiveKit's Node server SDK as the illustration:
app.post('/video-token', requireLogin, async (req, res) => {
const user = req.user;
const room = req.body.room;
if (!(await canJoin(user.id, room))) return res.sendStatus(403);
const role = await roleIn(user.id, room);
const at = new AccessToken(
process.env.LK_KEY,
process.env.LK_SECRET,
{
identity: user.id,
ttl: '10m',
}
);
at.addGrant({
roomJoin: true,
room,
canPublish: role !== 'viewer',
canSubscribe: true,
roomAdmin: role === 'host',
});
audit.log({ user: user.id, room, role });
res.json({ token: await at.toJwt() });
});Ten minutes is safe here only because LiveKit refreshes tokens for connected clients. On a platform without refresh, the lifetime comes from decision four.
When the Simple Version Is Fine
Not every setup needs all of this on day one. A local prototype can use a development key, and App-ID-only modes exist so a demo works in an afternoon. The line is the first build a real user can download. From then on the secret lives on the server, because rotating it out of shipped clients is the expensive part.
Short tokens also have a cost. They add load to your token endpoint, and on platforms without refresh they create a reconnection failure that long tokens would never have. Choose the lifetime deliberately; don't copy one from a quickstart.
The Bottom Line
The signing secret isn't a key to a room. It is the press that prints every token your platform will honour. Keep it on the server, put your own identity and room checks in front of it, derive permissions from role, and treat the token lifetime as both your revocation window and a reconnection constraint.
What's Next
Tokens carry the identity your own system vouches for. Next: what IT will ask before a rollout, from SSO to automatic deprovisioning.
Frequently Asked Questions
Where should video SDK token auth happen, client or server?
On the server. The client authenticates with your app, asks your backend for a token, and the backend signs one with the provider's secret. Signing in the client means shipping the secret, and anyone with the secret can mint tokens for any room and any permission.
Is it safe to put a video API key in frontend code?
It depends which value it is. App IDs and public API keys are identifiers and often belong in client code. The secret that signs tokens never does, because it can create tokens with every permission the platform supports.
How long should a video access token last?
As short as your platform's reconnection behaviour allows. With server-side token refresh, minutes is fine. Without it, an expired token breaks reconnection, so either refresh from the client before expiry or use a lifetime that covers your longest call.
Does kicking a participant revoke their token?
Not necessarily. On LiveKit, removing a participant does not invalidate their token, so they can rejoin until it expires unless your backend stops issuing them new ones. Short lifetimes plus a token service that refuses removed users is the practical revocation.
What should a video token service log?
Every token issued: user, room, role and time. That log answers "who was in this meeting and with what rights" during an incident or audit. On any platform, Samvyo included, the token service sits in your own backend, so this log stays with you.
Does self-hosting video change token security?
The model is the same: a secret signs tokens and the media server trusts them. What changes is who holds the secret and the verification side. With an on-premise deployment, which Samvyo supports, both the signing secret and the media server sit inside your network, so rotation and audit are entirely in your hands.