Go で LiveKit の入室トークンを発行する最小サーバー
LiveKit の room に入るためのアクセストークンを Go で発行する最小構成。docker で dev server を立てて、発行 → claims 確認 → 署名検証まで実際にやったメモ。
LiveKit を触り始めた。クライアント(ブラウザ)が room に入るには、サーバー側で発行したアクセストークンが要る。これを Go で作る最小構成を組んで、docker で立てた dev server 相手に動かすところまでやった。
結論
- LiveKit の入室許可は JWT(HMAC-SHA256 署名)
- 発行に大きな SDK は要らない。
github.com/livekit/protocol/authパッケージだけで作れる - トークンには「どの room に」「誰が」「何をしていいか(grant)」を詰める
- サーバーは受け取った JWT を API secret で署名検証して、通れば入室させる
環境(実測):Go 1.26.4 / github.com/livekit/protocol v1.49.0 / LiveKit server v1.13.2。
LiveKit の dev server を docker で立てる
--dev を付けると、鍵を用意しなくても固定の開発用キー(devkey / secret)で起動する。
docker run -d --rm --name lk-dev -p 7880:7880 \ livekit/livekit-server --dev --bind 0.0.0.0起動ログにキーが出る:
no keys provided, using placeholder keys {"API Key": "devkey", "API Secret": "secret"}starting LiveKit server {"portHttp": 7880, ... "version": "1.13.2"}この devkey / secret を、これからトークン発行サーバー側にも渡す。両者で同じ鍵を共有していることが署名検証の前提になる。
トークン発行サーバー (Go)
/token?room=...&identity=... を叩くと JWT を返すだけの HTTP サーバー。
package main
import ( "log" "net/http" "os" "time"
"github.com/livekit/protocol/auth")
// /token?room=<name>&identity=<user>func getToken(w http.ResponseWriter, r *http.Request) { room := r.URL.Query().Get("room") identity := r.URL.Query().Get("identity") if room == "" || identity == "" { http.Error(w, "room and identity are required", http.StatusBadRequest) return }
apiKey := os.Getenv("LIVEKIT_API_KEY") apiSecret := os.Getenv("LIVEKIT_API_SECRET")
at := auth.NewAccessToken(apiKey, apiSecret) grant := &auth.VideoGrant{ RoomJoin: true, Room: room, } at.SetVideoGrant(grant). SetIdentity(identity). SetValidFor(time.Hour)
token, err := at.ToJWT() if err != nil { http.Error(w, "failed to create token", http.StatusInternalServerError) return }
w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"token":"` + token + `"}`))}
func main() { port := os.Getenv("PORT") if port == "" { port = "8080" } http.HandleFunc("/token", getToken) log.Printf("token server listening on :%s", port) log.Fatal(http.ListenAndServe(":"+port, nil))}やっていることは3つだけ:
auth.NewAccessToken(key, secret)— 鍵を渡してトークンビルダーを作るVideoGrant{ RoomJoin: true, Room: room }— 「この room に入っていい」という許可SetIdentity/SetValidFor— 誰の・何時間有効か
identity は room 内でその参加者を一意に識別する名前。同じ identity で2重に入ると後勝ちで前の接続が切られる、という挙動だった。
セットアップと起動
go mod init lk-tokengo get github.com/livekit/protocol/authgo build -o lk-token .
# dev server と同じ鍵を渡すLIVEKIT_API_KEY=devkey LIVEKIT_API_SECRET=secret PORT=8099 ./lk-token叩く:
curl "http://localhost:8099/token?room=demo&identity=alice"# {"token":"eyJhbGci..."} (268文字くらいの JWT が返る)中身を確認する
返ってきた JWT の真ん中(payload)を base64url でデコードすると、詰めた grant がそのまま入っている:
{ "iss": "devkey", "sub": "alice", "exp": 1782964258, "nbf": 1782960658, "iat": 1782960658, "identity": "alice", "video": { "roomJoin": true, "room": "demo" }}iss(発行者)に API key がそのまま入る。サーバーはこれを見て「どの secret で検証するか」を選ぶvideoが grant。roomJoin: trueとroom: "demo"がクライアントの権限expはSetValidFor(time.Hour)の1時間後
サーバーが「受理する」とはどういうことか
LiveKit サーバーが入室を許すかどうかは、JWT の HMAC-SHA256 署名が API secret(secret)と一致するかで決まる。同じ判定を手元で再現してみた:
# header.payload を secret で HMAC-SHA256 して、3つ目の署名部分と一致するか$parts = $token.Split('.')$hmac = New-Object System.Security.Cryptography.HMACSHA256$hmac.Key = [Text.Encoding]::UTF8.GetBytes("secret")$sig = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($parts[0] + "." + $parts[1]))$b64 = [Convert]::ToBase64String($sig).Replace('+','-').Replace('/','_').TrimEnd('=')$b64 -eq $parts[2] # → TrueTrue が返った。つまり secret を知っている側なら誰でも同じ署名を再現できて、サーバーはそれで正当性を判断している。逆に言うと API secret が漏れると任意の grant のトークンを偽造できるので、secret はサーバー側だけに置く。クライアントに配るのは発行済みのトークンだけ。
詰まったところ
- 発行サーバーの
LIVEKIT_API_KEY/SECRETを dev server のdevkey/secretと揃えないと、トークンは発行できるがサーバー側の署名検証で弾かれる。両者が同じ鍵を共有していることが大前提 -p 7880:7880だけだと signaling(HTTP/WS)は通るが、実際のメディアには RTC 用のポート(7881/TCP・7882/UDP)も要る。トークン発行と入室確認だけなら 7880 で足りた
この先
トークンを配れれば、あとはブラウザ側の LiveKit クライアント SDK に serverUrl とこのトークンを渡せば room に入れる。次は Go 側から webhook(room 開始・参加者の出入り)を受ける口を作ってみる。