Reputation: 175
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:
We can observe in the previous image that the video claim is an object, but for my code I get this:
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
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
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
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