This post was most recently updated on July 31st, 2022.
3 min read.This article explains a (fairly) simple solution on how you can grab role claims – or any other claims with some tweaking – and use them as the basis for targeting your WebSocket messages. Very nifty, if you ask me, without all the enterprisiness of SignalR.
Talking about SignalR, another way to implement pretty much the same thing, but with all of the enterprisey goodness of SignalR, is described in this blog post:
How to access claims of a SignalR user in ASP.NET Core?
That said, let’s jump to the case at hand!
Background
A while ago, I had to implement a quick proof-of-concept for targeting WebSockets based on user roles coming from IdentityServer. The project wasn’t using SignalR, so targeting needed to be done using vanilla WebSockets – at least in the first phase.
I found a nice, quick solution – and since it worked nicely, we proceeded to replace it with SignalR 😅 Sometimes you end up wasting a cool little piece of code for a more enterprise-ready one.
So anyway, I thought that I’d document the main points about the solution because even though it’s definitely not production-ready, it was pretty nifty!
Solution
Below, we will go through the prerequisites and our basic configuration to make this work, and then show a sample of how we were using it.
Time needed: 30 minutes
How to use role claims to target WebSocket messages?
- Make sure each user’s access token is stored in the SubProtocol of their WebSocket
This is a pretty key thing – and very nifty. I posted about this earlier, so if the title doesn’t speak your language, take a quick look here:
How to secure your WebSocket connection using .NET Core? - Extract and validate the token
This step depends on your identity provider. The sample below shows our implementation for IdentityServer 4.
This would be in whatever background service that needs to be sending something down the WebSockets.var authorityEndpoint = [Our identity server url];
var openIdConfigurationEndpoint = $"{authorityEndpoint}/.well-known/openid-configuration";
IConfigurationManager configurationManager = new ConfigurationManager(openIdConfigurationEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidIssuer = authorityEndpoint,
ValidAudiences = new[] { "identity.api" },
IssuerSigningKeys = openIdConfig.SigningKeys,
};
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
foreach (var s in Sockets)
{
var user = handler.ValidateToken(s.SubProtocol, validationParameters, out validatedToken);
// The rest of the implementation omitted - see below
}
Pretty heavyweight, but it gets the job done. It’s the user that was really needed and everything else is just extra to get that entity. :)
If formatting is off – there’s a fair chance it will be – just copy-paste the code to VS Code and see what it looks like. - Validate the user and send whatever you needed to send!
Now you know that the user has authenticated successfully.
Mind the token expiration, though!
Now you can do whatever claim-based validation for your user and decide whether you want to send the message or not. Below is one such example:if (MappingService.VerifyMessageRole(user, message))
{
string jsonString = JsonSerializer.Serialize(message, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
byte[] buffer = Encoding.ASCII.GetBytes(jsonString);
s.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
Here is the socket in the loop from the last step. We’ll only send it if VerifyMessageRole -method call is successful. More on that below.
That’s it! The code is not concise, as it’s drafted directly from a production environment (I don’t just build these for fun, after all…) but I hope it gets the message through!
“What’s in that VerifyMessageRole -method?“, you might ask. And that’s a valid question! That’s where matching the user’s claims and the message’s targets happen. One possible implementation is shown below:
public static bool VerifyRole(ClaimsPrincipal user, Message m)
{
foreach (var role in m.TargetRoles)
{
if (user.HasClaim("identity_roles", role))
{
return true;
}
}
return false;
}
Here Message is a custom class – essentially just a string that’ll be passed down the WebSockets, accompanied by a list of claims to target.
References
Well, for once I actually didn’t just creatively recycle other people’s work, but had to figure this stuff out by myself! The manual JWT parsing & validation is built based on this: https://codetalk.in/posts/2020/05/19/manually-validate-jwt-token
- “Phone Link” permanently disabled and Windows claiming “Some of these settings are managed by your organization”? (Probably) an easy fix! - December 3, 2024
- Refreshing DefaultAzureCredential in Visual Studio - November 26, 2024
- M365 Copilot claiming “You have turned off web search in the work mode”? Easy fix! - November 19, 2024