ServiceStack ungraceful client disconnect - c#

In a ServiceStack app is there any way to determine that the client has ungracefully disconnected? I would like to get a list of users that are online, but
var sessionPattern = IdUtils.CreateUrn<IAuthSession>("");
var sessionKeys = Cache.GetKeysStartingWith(sessionPattern).ToList();
var activeSessions = Cache.GetAll<IAuthSession>(sessionKeys).Values;
will only get valid sessions, that are valid until they expire or client logouts (which doesn't reflect whether he is onlline or not).

If you're referring to Server Events subscriptions, their lifetimes are completely unrelated to User Sessions. A Server Event subscription just represents a long-lived HTTP connection to the Server Events /event-stream which may or may not be from an Authenticated User.
The way to find out active connected users is to call /event-subscribers.
Inside ServiceStack, all information available on Server Event subscriptions is accessible through the IServerEvents dependency, e.g. To find out all active subscriptions for a user you can call GetSubscriptionInfosByUserId()

Related

When is it appropriate for the client to re-negotiate with SignalR Service?

I'm trying to understand the access token returned by the negotiate step of SignalR Service.
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo(HubName = "chat")]SignalRConnectionInfo connectionInfo)
{
return connectionInfo;
}
By default, how long is the token returned by SignalRConnectionInfo valid?
Is there a way to manually configure the token lifespan?
If the token is no longer valid, what happens to the Microsoft.AspNetCore.SignalR.Client.HubConnection object (particularly its State property)? Will it be set to Disconnected?
Basically, I'm trying to determine when I should re-negotiate to keep my connection open. In the event that the token expires, how can the client know it needs to negotiate one more time before being able to send and receive real time messages?
Drilling down into the Azure SignalR SDK code, the default access token lifetime seems to be 1 hour.
While the SDK seems to support customizing the lifetime, the service binding doesn't seem expose it.
With 3, don't think you would really need it but you could raise on issue on its repo or contribute a PR to support it.
In the #microsoft/signalr package, you could either enable automatic reconnect or manually reconnect. I believe the library handles the negotiate calls internally on its own.
In the Authentication and authorization section in the Microsoft documentation says:
The access token function you provide is called before every HTTP request made by SignalR. If you need to renew the token in order to keep the connection active (because it may expire during the connection), do so from within this function and return the updated token.
So you need to send a "refreshed" token when you first start the connection. In an application context, when your token expires it should redirect the user to identity server to refresh the token and then start again a new connection.
In our company we have the persistent connection to the hub with the application token, so when it expires, it forces to refresh the token, and that will trigger a new connection to the hub with the new token.

Context property of SignalR Hub does not give me Http Context by using GetHttpContext method

In my application I plan to use SignalR in order for the backend code to send messages to the logged in user based on the conditions that arise on the server.
In particular I want the SignalR to call methods on the JS client whenever something happens on the backend. This could be periodic calculations happening on the backend that suddenly pass a threshold and I need to invoke something on the JS client for a particular User. The particular User is the key element here. Only the User that should know about this.
I'm assuming this should be very simple with SignalR but I have some problem understanding the way to implement this. My solution is as follow;
Each time a user logs in, I create a SignalR Group and add that user to that group.
Each time a user logs out, I remove them from the Group. (I don't know if I can also delete the Group itself)
Now each time something happens on the back end, I use SignalR to push information to a particular User that needs to know about that event by calling a JS client method and sending to the group with the name equal to the Name Identifier of the user of interest.
So to apply this solution, I need to get the information about the User that just logged in inside the C# Hub in order to create the group and join him.
Now my problem is, how to access the information that I need from the User that logs in to the application in the C# Hub . I'm interested in the User Name Identifier of course. I have been thinking that the Context Property in the Hub should give me the information that I need (This is my understanding of the MS Docs) but I cannot make it to work. Please study below code.
// As soon as a User logs in, below method fires. I try to get the information
// in here but no success
public override async Task OnConnectedAsync()
{
var CID = Context.ConnectionId; // this gives a unique connection ID
var user = Context.User; // This returns nothing
var userID = Context.UserIdentifier; // This returns nothing
var httpContextObject = Context.GetHttpContext(); // This returns nothing
await base.OnConnectedAsync();
}
When I call other methods in the C# Hub from JS the method is fired and
information is passed but I still have no access to the information the
Context Property should provide.
Please help me first by confirming or correcting my overall approach and if there are well known best practices available. Secondly please let me know how can I get information that the Context Property provides in the Hub and generally where this information is available and how to access and use them.
Many thanks in advance.
'User' is a claims principle (normally stored in the asp cookie), to get the User form your Identity database use:
var user = await _userManager.GetUserAsync(User);
For future reference, your question is very long, you will get more help if you keep questions brief and to the point.
I've solved my problem by using Authentication with SignalR. I don't need to add a user to any groups to push massages to them. In the Startup class I add Authentication above SignalR service and then the user object is passed to the Hub in the Context property. I can also push to any user by injecting the IHubContext to any class constructor I need.

Does Connecting to Remote WMI from an ASP.NET Page using Constrained Delegation Require Protocol Transition?

I'm working on an older web app that I did not originally build, but now maintain. It is a classic ASP app with ASPX pages mixed in.
The site is setup for Kerberos authentication and delegation, and that is working properly to other boxes (e.g. I can run a SQL query against a back-end server from an ASP page in the site and it connects using the front-end client's credentials properly). So SPNs are registered, delegation privileges are configured in AD, etc.
Now, the part I'm having trouble with is an ASPX page which invokes a remote WMI call to check on the status of an IIS website, using the \root\WebAdministration WMI namespace. The ASPX page is itself invoked by way of an XHR which resides in the client-side code of a different ASP page. The ASPX, when invoked, makes the WMI call, then Response.Write's back the necessary data, which the originating ASP page then utilizes to populate the page that the user sees. The problem is, I cannot get the IIS box to properly delegate the user's credentials to the back-end machine that its making the WMI call against.
This all works properly (including the constrained delegation), but only if I enable protocol transition. If I set the delegation on the middle-tier (IIS) box to use only Kerberos authentication, it fails (I get an anonymous logon attempt on the back-end box).
Now, I've done numerous packet captures on both the front-end client and the IIS box to see exactly what is going on here, and I can see several things:
The front-end client is properly getting its Kerberos ticket, and presenting it to the IIS box for authentication.
The IIS box is accepting the Kerberos ticket from the client.
However, the IIS box is not using the ticket received from the client as the "evidence ticket" that it should be presenting to the KDC in order to obtain a service ticket to access the back-end service on behalf of the front-end user. Instead, the IIS box is using a S4U2Self call to the KDC to obtain a ticket on the front-end user's behalf for itself, then using that ticket in the subsequent S4U2Proxy call to try and obtain the ticket to the back-end. This is where the problem lies.
The behavior noted above is why this works when protocol transition is enabled, and does not work when it is not.
I cannot figure out for the life of me, why the IIS box feels the need to obtain a TGS for itself to use as the "evidence ticket" to get the ticket for accessing the back-end, instead of simply using the ticket presented by the client. There is nothing invalid about the client's ticket from what I can tell, and the client is establishing a Kerberos-authenticated connection with the web server just fine, so there should be no need for protocol transition here. I could enable it if really needed, but I really just want to know why its necessary (if there is valid reason and this is by design then so be it).
The IIS app pool is running as the built-in app pool identity, and the delegation settings are thus configured on the IIS machine account in AD. SPNs are registered for the site against the IIS machine account, and for the back-end services against those service and/or machine accounts, and the "allowedToDelegateTo" list is configured on the IIS machine account, allowing constrained delegation to the necessary services. The specific SPN we are trying to delegate creds to in this scenario is RPCSS/[machine] for the WMI call. I've verified via the packet capture that the SPN in the request matches the SPN in the A2D2 list exactly (of course, if it didn't, then it wouldn't be working when protocol transition was enabled anyway).
As for the actual WMI connection code, I've tried a few ways. One was something like this:
ConnectionOptions co = new ConnectionOptions();
// I did try ImpersonationLevel set to both Impersonate and Delegate, but I don't think I need
// Delegate here because I'm not delegating from the remote WMI machine to a different box; instead,
// I'm delegating from the IIS box to the remote WMI machine.
co.Impersonation = ImpersonationLevel.Impersonate;
co.Authentication = AuthenticationLevel.PacketPrivacy;
co.EnablePrivileges = true;
// Tried this for the Authority line because I noticed in the packet captures that the principal
// specified here becomes the SPN that is used in the S4U2Proxy request.
co.Authority = "kerberos:RPCSS/machine.domain.com";
ManagementScope ms = new ManagementScope(#"\\machine.domain.com\root\WebAdministration", co);
Then I also tried this:
ConnectionOptions co = new ConnectionOptions();
co.Impersonation = ImpersonationLevel.Impersonate;
co.Authentication = AuthenticationLevel.PacketPrivacy;
co.EnablePrivileges = true;
// I also tried this for the Authority line based on various other code examples for similar
// issues, but this resulted in an incorrect SPN being used in the request.
co.Authority = #"kerberos:DOMAIN\machine";
ManagementScope ms = new ManagementScope(#"\\machine.domain.com\root\WebAdministration", co);
I also tried the same as above, but without an Authority line, and the correct SPN was used in the request but it still didn't work.
Finally, I also tried this, with no ConnectionOptions object at all, hoping it would just pass on the default creds:
ManagementScope ms = new ManagementScope(#"\\machine.domain.com\root\WebAdministration");
Any help here on either how I can get this working without enabling protocol transition, or info on why this setup would require protocol transition, would be very much appreciated!

ServiceStack session lifetime is increased on heartbeat

I have a Service with a ServerEventsFeature. I'm using a ServerEventsClient which by default sends heartbeats to the service. As far as I know ServiceStack doesn't support sliding expiration from the box, but session lifetime is refreshed after every heartbeat. Is this how it should be and are there any ways to control that? I've tested it by setting SessionExpiry to 30s and HeartbeatInterval to 20s - the app sends multiple (10+) heartbeats w\o any problems. In addition, if client app crashes and force closes - the session will be "alive" in the server sessions list until I try to use it, is there a way to drop it earlier?
Example:
start the server
launch client 1
execute
var sessionPattern = IdUtils.CreateUrn<IAuthSession>("");
var sessionKeys = Cache.GetKeysStartingWith(sessionPattern).ToList();
var allSessions = Cache.GetAll<IAuthSession>(sessionKeys);
the results count is 1
launch client 2 and 3
drop client 1 through force shutdown (no logout calls)
execute the same code again - results count is 3
A Server Events subscription doesn't have any impact on a Users Session.
A Users Session is created after a user successfully authenticates and is destroyed when they explicitly logout or when the session expiry elapses.
A Server Events subscription is just a reference to a users long-lived HTTP Connection to the SSE /event-stream, it's only relation to a Users Session is that the subscription may be that of an authenticated user.
But the life-cycle of a Users Session (which is just a AuthUserSession POCO persisted in the registered ICacheClient) is not affected by a Server Events subscription since it never re-saves the Users Session, so never changes the Session Expiry.

SignalR: How to stop creating new connection on page reload

Hi i am developing a chat application along with some other pages in my application. once i login i am maintaining the session of the user. My main intention is that user should get notification whenever another user connects to server.
The problem that i am facing is whenever i navigate to some other page in my application the connection is lost. How to stop this behaviour and continue the connection until user logs out.
I am using SignalR 2.0 in ASP.NET MVC4 project, any help??
Each connection only has a lifecycle for the duration of the time the user spends on a given page. When they navigate to another page, a new connection is established. Also, if a user has more than one tab or browser window open, they will have multiple connection Ids. I don't think you want to try to persist the connection Id beyond it's intended lifecycle.
In a similar scenario that I work on, we store the connectionIds OnConnect and delete them OnDisconnect. When a message needs to be sent to a given user, we send it to all of their connectionIds. This ensures that the message will be delivered to all tabs/windows.
EDIT 1 the following is in response to #Saurabh's comments:
Consider what the scope of the Hub is, and that of your other classes and the client. The Hub is there to facilitate communications between the browser and the server. While it's possible to do a lot of work within the hub, I think it's best to move most of the scope outside of communictions to other places.
The client knows that it just reloaded a page, so it's a good candidate for making the decision that this is a reconnect event.
_chatHub.server.reJoinRooms();
Then the Hub can query the user's rooms, by UserId, rather than ConnectionId.
public Task ReJoinRooms()
{
// get the user's rooms from your repository
// optionally get the user's connectionIds from your repository
// Clients.Caller.onJoinRooms(rooms);
// or Clients.Clients(_connectionIds).onJoinRooms(rooms);
}
Then the client can decide whether or not to take action:
$chatModule.client.onJoinRooms = function (rooms) {
for (var i in rooms) {
var _room = rooms[i];
// if I'm not already in this room, add it to my rooms
console.log('joining room', _room)
}
}
You could skin this many different ways. The client could own the scope of remembering rooms, instead of a server-side repository, too.
EDIT 2
If the number of groups/rooms that a user belongs to is ever-increasing, the above example may not scale up very well.
In that case, each user could join personal feed(s) instead (i.e. join a feed named for the user's GUID). We would keep track of each user that is affiliated with a group. When a message is sent to that group, we would iterate over those user's and publish a message to each feed.

Categories