Diego Satizabal
Diego Satizabal

Reputation: 175

Issue generating LiveKit JWT Token in C#

I'm trying to generate a LiveKit Token in C# for a custom implementation where available Node/Go SDKs are not suitable to be used.

I came up with the following code:

using System.Security.Claims;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;


namespace LiveKitToken
{
    [Serializable]
    class VideoClaim
    {
        public string room { get; set; }
        public bool roomCreate { get; set; }
        public bool roomJoin { get; set; }
    }

    internal class TokenGenerator
    {
        public string CreateToken(string apiKey, string apiSecret, string roomName, string identity, TimeSpan validFor)
        {
            var now = DateTime.UtcNow;

            VideoClaim videoClaim = new VideoClaim()
            {
                room = roomName,
                roomCreate = true,
                roomJoin = true
            };

            var claims = new Claim[]
            {
                new Claim(JwtRegisteredClaimNames.Iss, apiKey),
                new Claim(JwtRegisteredClaimNames.Sub, identity),
                new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(now).ToUnixTimeSeconds().ToString()),
                new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(now.Add(validFor)).ToUnixTimeSeconds().ToString()),
                new Claim("video", JsonConvert.SerializeObject(videoClaim, Formatting.Indented))
            };

            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(apiSecret));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                claims: claims,
                signingCredentials: credentials
            );

            var tokenHandler = new JwtSecurityTokenHandler();
            return tokenHandler.WriteToken(token);
        }
    }
}

The code works and actually generates a JWT Token, however the Token is invalid (I'm of course using valid apiKey and apiSecret for the corresponding server).

When I decode a valid Token I see this:

enter image description here

We can observe in the previous image that the video claim is an object, but for my code I get this:

enter image description here

In this case the video claim is always included as an string, more over, the constructor for a Claim in C# does not offer any type to add an object among the various types it supports.

I guess this is what causes my Token to be invalid but I haven't been able to generate a Token identical to the valid one.

Any help is much appreciated in advance!

Upvotes: 0

Views: 781

Answers (3)

pabloFuente
pabloFuente

Reputation: 301

You can simply use this LiveKit .NET SDK: livekit-server-sdk-dotnet

It is built on top of the official livekit/protocol and supports the whole API: AccessToken, WebhookReceiver, Room API, Egress API, Ingress API, SIP API, AgentDispatch API.

It is available for download in NuGet.

Upvotes: 0

user3284707
user3284707

Reputation: 3351

For anyone else struggling with this, I was able to build on the answer above to generate a correct token which I could use in my app and generate a room and log in.

For anyone interested here is my code so far which I aim to build on soon.


    public class LiveKitTokenGenerator
    {
        private readonly string _serverUrl;
        private readonly string _apiKey;
        private readonly string _apiSecret;

        public LiveKitTokenGenerator(string url, string apiKey, string apiSecret)
        {
            _serverUrl = url;
            _apiKey = apiKey;
            _apiSecret = apiSecret;
        }

        public async Task<string> GetRoomTokenAsync(string roomName, string identity)
        {
            using var client = new HttpClient();
            var token = CreateJWT(roomName, identity);
            client.BaseAddress = new Uri(_serverUrl);
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

            var payload = new
            {
                name = roomName
            };

            var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
            var response = await client.PostAsync("/room-tokens", content);

            response.EnsureSuccessStatusCode();
            return token;
        }

        public string CreateJWT(string roomName, string identity)
        {
            var now = DateTime.UtcNow;
            JwtHeader headers = new(new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_apiSecret)), "HS256"));

            JwtPayload payload = new();
            payload.Add("exp", new DateTimeOffset(now.AddHours(6)).ToUnixTimeSeconds());
            payload.Add("iss", _apiKey);
            payload.Add("nbf", 0);
            payload.Add("sub", identity);
            payload.Add("name", identity);

            var videoGrants = new Dictionary<string, object>()
            {
                { "canPublish", true },
                { "canPublishData", true },
                { "canSubscribe", true },
                { "room", roomName },
                { "roomJoin", true },
            };

            payload.Add("video", videoGrants);

            JwtSecurityToken token = new(headers, payload);
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }

Then using this like

   var generator = new LiveKitTokenGenerator(
     serverUrl,
     apiKey,
     apiSecret
   );
   var roomName = "exampleRoom";
   var identity = "sampleUser";
   var token = await generator.GetRoomTokenAsync(roomName, identity);

Upvotes: 1

melleg
melleg

Reputation: 21

I think I figured it out using the standard Microsoft JWT libraries--you can actually generate nested claims (the video grants) by passing a Dictionary<string, object> into JwtPayload.Add("claim-name", dictionary). Here's my token generation code:

JwtHeader headers = new(new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(your-api-secret)), "HS256"));

JwtPayload payload = [];
payload.Add("exp", your-expiration-unix-here);
payload.Add("iss", your-api-key-here); // not the apiSecret
payload.Add("nbf", your-nbf-here);
payload.Add("sub", your-room-name-here);

var videoGrants = new Dictionary<string, object>()
{
    { "canPublish", true },
    { "canPublishData", true },
    { "canSubscribe", true },
    { "room", roomName },
    { "roomJoin", true },
};

payload.Add("video", videoGrants);

JwtSecurityToken token = new(headers, payload);
return new JwtSecurityTokenHandler().WriteToken(token);

This resulting JWT will contain the following claim once decoded:

  "video": {
    "canPublish": true,
    "canPublishData": true,
    "canSubscribe": true,
    "room": "quickstart room",
    "roomJoin": true
  }

Upvotes: 2

Related Questions