# LiveKit Token Server — Sign JWTs for Room Access > Server-side token signing for LiveKit rooms. Python/Node SDKs. Per-user permissions, room scoping, TTL. Required for production. ## Install Save as a script file and run: ## Quick Use 1. `pip install livekit-api` or `npm install livekit-server-sdk` 2. Build an authenticated POST /livekit/token endpoint in your backend 3. Frontend POSTs (user_id, room), gets back {token, url}, joins with the SDK --- ## Intro LiveKit rooms require signed JWT access tokens — your backend signs a token with API key + secret, granting a specific identity permission to join a specific room with specific capabilities (publish, subscribe, record, data). Never sign in the browser. This is the server-side flow with Python and Node examples. Best for: any production LiveKit deployment, multi-tenant voice apps, per-user permission models. Works with: livekit-server-sdk (Python, Node, Go, Ruby, Java). Setup time: 10 minutes. --- ### Python token endpoint (FastAPI) ```python from livekit import api from fastapi import FastAPI, HTTPException, Depends import os app = FastAPI() @app.post("/livekit/token") async def issue_token(user_id: str, room: str, user = Depends(authenticated_user)): if not can_join_room(user, room): raise HTTPException(403, "not allowed in this room") token = ( api.AccessToken(os.environ["LIVEKIT_API_KEY"], os.environ["LIVEKIT_API_SECRET"]) .with_identity(user_id) .with_name(user.display_name) .with_grants(api.VideoGrants( room_join=True, room=room, can_publish=True, can_subscribe=True, can_publish_data=True, )) .with_ttl(timedelta(hours=1)) .to_jwt() ) return {"token": token, "url": os.environ["LIVEKIT_URL"]} ``` ### Node token endpoint (Express) ```javascript import { AccessToken } from 'livekit-server-sdk'; app.post('/livekit/token', requireAuth, async (req, res) => { const { userId, room } = req.body; if (!canJoinRoom(req.user, room)) return res.status(403).send(); const at = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, { identity: userId, name: req.user.displayName, ttl: '1h', }); at.addGrant({ roomJoin: true, room, canPublish: true, canSubscribe: true, canPublishData: true, }); res.json({ token: await at.toJwt(), url: process.env.LIVEKIT_URL }); }); ``` ### Grant cheat sheet | Grant | Use case | |---|---| | `room_join` | Required for any participant | | `can_publish` | Participant can publish audio/video | | `can_subscribe` | Participant can hear/see others | | `can_publish_data` | Send arbitrary chat / state via DataChannel | | `room_admin` | Can mute, remove participants | | `room_record` | Can start egress recording | | `ingress_admin` | Can manage SIP / RTMP ingress | ### Production hardening - **Short TTL** — 1 hour max for end-user tokens. Browser refreshes via your endpoint. - **Per-user identity** — never reuse identities. `user_id` should be your DB primary key. - **Server-side only** — never log secrets, never ship API_SECRET to the browser. - **Cache the SDK** — instantiate once, reuse for many tokens. --- ### FAQ **Q: Can I sign tokens in the browser?** A: No — the API_SECRET would be exposed. Always sign in your backend. The browser only ever sees the resulting JWT, which has limited TTL and scoped grants. **Q: How do agents get their own token?** A: Agents authenticate the same way — your worker process signs an agent identity token (with `agent=True` grant) before joining the room. LiveKit Cloud agent dispatchers handle this automatically when you deploy via the LiveKit CLI. **Q: What about webhooks for participant join/leave?** A: Configure webhooks at cloud.livekit.io → Webhooks. Events: `room_started`, `participant_joined`, `participant_left`, `track_published`, `egress_started`. Verify the X-LiveKit-Signature header server-side. --- ## Source & Thanks > Built by [LiveKit](https://github.com/livekit). Licensed under Apache-2.0. > > [livekit/python-sdks](https://github.com/livekit/python-sdks), [livekit/server-sdk-js](https://github.com/livekit/server-sdk-js) --- ## 快速使用 1. `pip install livekit-api` 或 `npm install livekit-server-sdk` 2. 后端搭一个带认证的 POST /livekit/token endpoint 3. 前端 POST (user_id, room) 拿 {token, url},用 SDK 加入 --- ## 简介 LiveKit room 要签名 JWT 访问 token —— 后端用 API key + secret 签 token,授权某个身份以指定权限(publish / subscribe / record / data)加入指定 room。绝对不要在浏览器里签。这是 Python 和 Node 服务端流程。适合任何生产 LiveKit 部署、多租户语音应用、按用户的权限模型。兼容 livekit-server-sdk(Python / Node / Go / Ruby / Java)。装机时间 10 分钟。 --- ### Python token endpoint(FastAPI) ```python from livekit import api from fastapi import FastAPI, HTTPException, Depends import os app = FastAPI() @app.post("/livekit/token") async def issue_token(user_id: str, room: str, user = Depends(authenticated_user)): if not can_join_room(user, room): raise HTTPException(403, "无权进入该 room") token = ( api.AccessToken(os.environ["LIVEKIT_API_KEY"], os.environ["LIVEKIT_API_SECRET"]) .with_identity(user_id) .with_name(user.display_name) .with_grants(api.VideoGrants( room_join=True, room=room, can_publish=True, can_subscribe=True, can_publish_data=True, )) .with_ttl(timedelta(hours=1)) .to_jwt() ) return {"token": token, "url": os.environ["LIVEKIT_URL"]} ``` ### Node token endpoint(Express) ```javascript import { AccessToken } from 'livekit-server-sdk'; app.post('/livekit/token', requireAuth, async (req, res) => { const { userId, room } = req.body; if (!canJoinRoom(req.user, room)) return res.status(403).send(); const at = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, { identity: userId, name: req.user.displayName, ttl: '1h', }); at.addGrant({ roomJoin: true, room, canPublish: true, canSubscribe: true, canPublishData: true, }); res.json({ token: await at.toJwt(), url: process.env.LIVEKIT_URL }); }); ``` ### Grant cheat sheet | Grant | 用例 | |---|---| | `room_join` | 任何参与者必备 | | `can_publish` | 参与者可发布音视频 | | `can_subscribe` | 参与者可听/看他人 | | `can_publish_data` | 通过 DataChannel 发任意聊天/状态 | | `room_admin` | 可静音、移除参与者 | | `room_record` | 可启动 egress 录制 | | `ingress_admin` | 管理 SIP / RTMP ingress | ### 生产加固 - **短 TTL** —— 终端用户 token 最多 1 小时。浏览器通过 endpoint 续签。 - **按用户 identity** —— 别复用 identity。`user_id` 应是 DB 主键。 - **仅服务端** —— 不打 secret 日志、绝不把 API_SECRET 发到浏览器。 - **缓存 SDK** —— 实例化一次,签多次复用。 --- ### FAQ **Q: 能在浏览器签 token 吗?** A: 不能 —— API_SECRET 会暴露。永远在后端签。浏览器只见结果 JWT,TTL 受限、grant 限定。 **Q: agent 怎么拿自己的 token?** A: agent 同样认证 —— worker 进程签 agent 身份 token(带 `agent=True` grant)再加入 room。LiveKit Cloud agent 调度器在你用 LiveKit CLI 部署时自动处理。 **Q: participant join/leave 的 webhook 呢?** A: 在 cloud.livekit.io → Webhooks 配置。事件:`room_started` / `participant_joined` / `participant_left` / `track_published` / `egress_started`。服务端验 X-LiveKit-Signature header。 --- ## 来源与感谢 > Built by [LiveKit](https://github.com/livekit). Licensed under Apache-2.0. > > [livekit/python-sdks](https://github.com/livekit/python-sdks), [livekit/server-sdk-js](https://github.com/livekit/server-sdk-js) --- Source: https://tokrepo.com/en/workflows/livekit-token-server-sign-jwts-for-room-access Author: LiveKit