Validate Opaque User Id or helixToken for security in websocket message processing?

I’m working on an extension that uses a remote websocket server.
It works fine sending/receiving messages as long as the URL for the server is in my extensions allow list for fetching domains. But I’m worried about mean people sending bogus messages.

It seems trivial to look at the source for my extension in the browser, see the destination URL for the websocket and how the messages are formatted. It would suck to have someone send a ton of them and/or include inappropriate values to try and wreak havoc on my systems.

The naive solution would be to include the Opaque User Id and/or helixToken from window.Twitch.ext.onAuthorized in the messages and make an API call on my system to verify that the message indeed came from a legitimate instance of my extension, and not just someone who knows the URL/format.

I’ve looked through the API reference and dont see anything obvious for that kind of validation. Can I use the helixToken to make an API call from my system, or is there a way to validate the Opaque User Id?

Your frontend should sent the JWT from onAuthorized to your server with every request.

You can then Validate the JWT by using your Extension Client Secret. This allows you to be sure that all the information in the JWT, such as the user ID if they’ve shared their identity, or any other info, is all legitimate and not modified, as only you and Twitch know the Extension Client Secret so no one else can sign a JWT that’d pass validation.

1 Like

Thank you, I had seen that page but glazed over on all the stuff about signing the JWT and missed the obvious validation info.

My websocket server is in C# and I imported the Microsoft JWT stuff.

I added the auth.token from onAuthorized as an attribute to the XML message I’m sending to the websocket server.

Here’s the routine that I’ve come up with and initial testing looks good. If anything doesnt check out with the validation, the tokenChannelIdStr doesnt match my expected extensionChannelId and I respond with a failure message. Otherwise, the validation must have worked and the channel Id’s match!

using Microsoft.IdentityModel.Tokens;
using System.ServiceModel.Security.Tokens;
using System.IdentityModel.Tokens.Jwt;
...
            //In my websocket onMessage after getting the extensionMessage XML Root Node
            string tokenChannelIdStr = "";
            XAttribute tokenAttr = extensionMessage.Attribute("JwtToken");
            if (tokenAttr != null)
            {
                string tokenStr = tokenAttr.Value;
                byte[] secretByteArray = Convert.FromBase64String(extensionJwtSecret);
                SecurityKey secretKey = new SymmetricSecurityKey(secretByteArray);
                SecurityToken validatedToken = null;
                var tokenHandler = new JwtSecurityTokenHandler();
                var tokenParameters = new TokenValidationParameters()
                {
                    IssuerSigningKey = secretKey,
                    ValidateAudience = false,
                    ValidateIssuer = false
                };

                try
                {
                    tokenHandler.ValidateToken(tokenStr, tokenParameters, out validatedToken);
                }
                catch (Exception ex)
                {
                    _vaProxy.WriteToLog($"JWT ValidateToken Exception: {ex.Message}", "red");
                    validatedToken = null;
                }

                if (validatedToken != null)
                {
                    if (((JwtSecurityToken)validatedToken).Payload.TryGetValue("channel_id",out var tokenChannelId))
                    {
                        tokenChannelIdStr = tokenChannelId.ToString();
                    }
                }
            }

            if (tokenChannelIdStr != extensionChannelId)
            {
                XElement failMessage = new XElement("ServerReply",
                    new XElement("Type", "Fail"),
                    new XElement("Message", "JWT Token validation failed"),
                    new XElement("Timestamp", DateTime.Now.ToString())
                    );
                _vaProxy.WriteToLog("JWT Token does not match our ChannelId!", "red");

                if (this.ConnectionState == WebSocketState.Open)
                {
                    Send(failMessage.ToString());
                }
                return;
            }
            //Carry on, JwtToken looks good...

Any constructive criticism or best practices guidance is appreciated, I’m pretty new to all this and in particular the tokenParameters used in ValidateToken feel like a shot in the dark.

I’ll go ahead and mark this as solved since testing the code I posted above has gone well and I havent needed to make any more changes to avoid exceptions or other issues.

If anyone else is doing this sort of thing in C# in the future the biggest thing I had as a stumbling block was getting the payload data out of the validated token.

After you call:

tokenHandler.ValidateToken(tokenStr, tokenParameters, out validatedToken);

The validatedToken you get shows all the data in the debugger, but the object itself does not present the properties you expect.

((JwtSecurityToken)validatedToken).Payload.TryGetValue(“channel_id”,out var tokenChannelId)

Apparently the validateTokens method requires the out parameter to be of type SecurityToken but the object has data as if it is a JwtSecurityToken and you need to cast it that way to access things like the Payload. That in itself took a while to get working, the rest of it was pretty much just following the documentation.