I'm trying to send a message using SignalR and it works if I send to everybody, but not to s specific user. I tried to use the ConnectionId that in theory should be unique, but every time I tried to use the same connectionId that I received by the client, it doesn't work.
The server-side:
public async Task SendMessage(string user, string message)
{
var a = Context.UserIdentifier;
await Clients.User(Context.ConnectionId).SendAsync("ReceiveMessage", user, message);
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
Trying to pass the same ConnectionId in context doesn't send the message, only when I call Clients.All
The client is an android app and I'm not sure if I should register something on my client-side.
hubConnection = HubConnectionBuilder.create("http://192.168.1.5:3000/notification").build()
hubConnection.start()
hubConnection.on<String, String>(
"ReceiveMessage",
Action2 { user: String?, message: String? ->
requireActivity().runOnUiThread(java.lang.Runnable {
Toast.makeText(
context,
"I'm here.",
Toast.LENGTH_LONG
).show()
})
},
String::class.java,
String::class.java
)
You should use
await Clients.Client(Context.ConnectionId).SendAsync("ReceiveMessage", message);
It should works well.
SignalR allows messages to be sent to a particular client connection, all connections associated with a specific user, as well as to named groups of connections. => await Clients. User(userId).
if you want to send message to specific user in SignalR, easiest way is the use Form authentication. Also you can use your custom session with form authentication. Right after creation your session code put this code. FormsAuthentication.SetAuthCookie (username.Trim (), false); Then in signalR you can use this line for send message to this user:
Related
I'm creating a SignalR server and I added a couple of rules that the clients should follow when they want to connect to server.
The rules (also call them 'validators') are, for example, that a certain header should be present when the client request to connect.
My question is: how can "reject" a connection with a proper "status code" and "message" and kick out the user?
I didn't find any helpful thread around.
Thanks for reading.
I Checked the hub class and found:
and if the connection is assciated with httprequest,you could use Context.GetHttpContext() method to get the httpcontext,
So I tried as below:
public override async Task OnConnectedAsync()
{
var errormessage = "the connection was disconnected due to Some reason";
var header = Context.GetHttpContext().Request.Headers;
if (header.ContainsKey("Origin"))
{
await Clients.Caller.SendAsync("Disconnect", errormessage);
Context.Abort();
.......
}
}
The Result:
When the user submits his credentials to my api, I call an external api to authenticate the user. After that, a token gets generated on the external api and will be sent to me. For that I implemented the HandleAuthenticateAsync function from the AuthenticationHandler:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//before this: make call to external api to get the access token
var claims = new[] {
new Claim(ClaimTypes.Name, submittedToken),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
I have implemented a custom AuthorizationHandler which I want to check for the access token that you got when you successfully authenticate. Note that the actual authentication and authorization is done by an external api which is a custom implementation. Here is the function:
public class IsAuthorizedRequirement : AuthorizationHandler<IsAuthorizedRequirement>, IAuthorizationRequirement
{
public AuthenticateHandlerHelperFunctions AuthenticateHandlerHelper;
public IsAuthorizedRequirement()
{
AuthenticateHandlerHelper = new AuthenticateHandlerHelperFunctions();
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IsAuthorizedRequirement requirement)
{
if(!context.User.HasClaim(c => c.Type == ClaimTypes.Name))
{
context.Fail();
return;
}
var token = context.User.FindFirst(c => c.Type == ClaimTypes.Name).Value;
if (!string.IsNullOrEmpty(token))
{
context.Fail();
return;
}
var checkedToken = await AuthenticateHandlerHelper.CheckAccessToken(token);
if (checkedToken == null)
{
context.Fail();
return;
}
context.Succeed(requirement);
}
}
The CheckAccessToken function makes a simple HTTP Post Request to the external Api where I get back if the token is still valid or not. Is this a valid implementation especially when multiple users are using this? Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request? Currently I have no way to test this so I just wanted to know if I am on the right track for this. Thanks
Is this a valid implementation especially when multiple users are using this?
I strongly stand against this approach. Implementation like this mean you would call external API for validate and generate token(or cookie or any form of authenticated certificate) on external server for each and any of your request(which require authentication).
It's could be consider acceptable if we have some special cases on just some endpoints. But for the whole API/Web server. Please don't use this approach.
Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request?
They'll create for each request. As I can see in the code there are no part for generate cookie or some form of retaining user information for the client to attach next request afterward.
How can I manage to have only one connection of signalR per user in different browsers/tabs? so if user is active in one tab/browser and wants to open another tab/browser I have to alert him to first close the previous tab/browser. like Whatsapp.
I have implemented this so far but I don't know how to fill the commented par?!
public override async Task OnConnectedAsync()
{
bool isUserExisted = _onlineUsers.TryAdd(Context.User.FindFirst("username").Value, Context.ConnectionId);
if (!isUserExisted)
{
//avoid to connect the already connected user
//and send alert to him
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
_onlineUsers.TryRemove(Context.User.FindFirst("username").Value, out _);
await base.OnDisconnectedAsync(exception);
}
Any help would be appreciated.
I'm not an expert on the client side, but I assume you cannot maintain the same SignalR connection from different tabs because each tab has its own context and establishes its own connection.
You can, however, on the server side create a user group, where you would pack all connections of the current user:
var user = GetUser(username); // Get your user from context
await this.Groups.AddToGroupAsync(this.Context.ConnectionId, GetUserGroupName(user)); // Generate unique group name per user and add all user connections to it
Than you can play with whom you send your events, e.g. send to all user connections but for current one (i.e. but for current tab):
this.ctx.Clients.GroupExcept(groupName, currentConnectionId);
I'm not sure when alerting should occur? Do you call a REST method on the server? Or directly a SignalR method? If it's an independent REST method, you could pass your current SignalR connection id in a custom header and read it in the hub.
I'm using SignalR 1 with MVC4 C# web application with form authentication.
I have a code in my layout page in JavaScript :
$(documnet).ready(function(){
connect to hub code ...
})
I want to disconnect a user form the hub and start connect again after he does a login and validate ok.
I want to do it from server side inside my account controller and method :
public ActionResult LogOn(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (System.Web.Security.Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
....here , disconnect from hub
....to make the user reconnect
}
The reason I want to do it is because SignalR throws an error if user changed to authenticated after login and the connection remains . The error is:
The connection id is in the incorrect format.
You cannot stop and start SignalR connections from the server. You will need to call
$.connection.hub.stop(); //on the client before the user attempts to log on and then call
$.connection.hub.start(); //after the log on attempt has completed.
One way you could do what you ask is to write a disconnect event on your client that the server can call through SignalR. Maybe something somewhat like this:
myHub.client.serverOrderedDisconnect = function (value) {
$.connection.hub.stop();
};
Then, on the server, something like this:
Clients.Client(Context.ConnectionId).serverOrderedDisconnect();
If someone is still looking for solution(SignalR version 2.4.1):
GlobalHost.DependencyResolver.Resolve<ITransportHeartbeat>().GetConnections().First(c => c.ConnectionId == "YourId").Disconnect();
Try controlling everything from javascript. The following is a logout example, login would be similar.
From http://www.asp.net/signalr/overview/security/introduction-to-security:
If a user's authentication status changes while an active connection
exists, the user will receive an error that states, "The user identity
cannot change during an active SignalR connection." In that case, your
application should re-connect to the server to make sure the
connection id and username are coordinated. For example, if your
application allows the user to log out while an active connection
exists, the username for the connection will no longer match the name
that is passed in for the next request. You will want to stop the
connection before the user logs out, and then restart it.
However, it is important to note that most applications will not need
to manually stop and start the connection. If your application
redirects users to a separate page after logging out, such as the
default behavior in a Web Forms application or MVC application, or
refreshes the current page after logging out, the active connection is
automatically disconnected and does not require any additional action.
The following example shows how to stop and start a connection when
the user status has changed.
<script type="text/javascript">
$(function () {
var chat = $.connection.sampleHub;
$.connection.hub.start().done(function () {
$('#logoutbutton').click(function () {
chat.connection.stop();
$.ajax({
url: "Services/SampleWebService.svc/LogOut",
type: "POST"
}).done(function () {
chat.connection.start();
});
});
});
});
You can the hub context to abort() the connection directly in .Net Core
Context.Abort();
See the method below
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.hubcallercontext.abort
Try this:
public ActionResult LogOn(LoginModel model, string returnUrl) {
if (ModelState.IsValid) {
if (System.Web.Security.Membership.ValidateUser(model.UserName, model.Password)) {
FormsAuthentication.SetAuthCookie(model.UserName, false);
connection.Stop();
}
}
Assuming your connection handle is connection. The challenge is accessing a handle to your connection object in your Action Method.
Copy and paste the following function into your Hub
Use HttpContext.Current.Response.End();
to force the client to disconnect in your hub
The goal of my simple try is to display online user list. I mean display not socket.io ID but display user profile. When authorized user connects to my server, open socket.io channel, it is required to get his profile and send message to other connected user that new user (Name, email, etc) has being connected. I saw many examples how to do it within authorization, but it doesn't handle a disconnect. What i want to do and what i can't do in pseudocode:
var io = require("socket.io").listen(server);
io.set("authorization", function(data, callback){
// ... some code...
callback(null, true);
});
io.sockets.on('connection', function (socket) {
var UserProfile = passport.getUserProfile(socket.id)
io.sockets.emit('user_connected', {UserProfile: UserProfile, socketID: socket.id});
io.sockets.on('disconnect', function (socket) {
io.sockets.emit('user_disconnected', {socketID: socket.id});
});
});
This is a pseudocode!
My stack is overflowed. I just want to link socket.io ID and passport account together within connection. How can i do it?
I got the same problem, and my solution is the following (hopefully someone gets a better idea):
i add the username to the render call (using jade):
res.render('chatroom', {username: req.user.username});
right after connecting to socket on the client (io.connect), i emit a
message to the server, with the username as parameter, using the connect event on the client (socket.on('connect', function (data) { ... });
on the server, i store the username in an object (clients[socket.id]
= username)
after that, i get the username in every socket message by accessing
the clients object