aspnetboilerplate: SignalR JWT Authentication - signalr

We are trying to integrate SignalR in an 3rd party application to talk to our Hubs we have for our aspnetboilerplate application. This is using the .NET Core template. We are having an issue with the session in aspnetboilerplate having a null UserId even when getting past the attribute on our Hub to check for authorization.
The issue we are having is at random times the UserId inside of AbpSession will just be null. It gets past the [Authorize] attribute but aspnetboilerplate seems to think the UserId is null at random times. I can invoke a method on our Hub and see the UserId is correct for that user. Then the very next time I invoke that same method on the hub with the same user the UserId inside of AbpSession is null. I can then invoke the method again and the UserId will sometimes be null or sometimes be correct. Their doesn't seem to be any consistency in this issue. Every now and then it will alternate between being null and having the correct UserId.
Our client code:
let connection = new signalR.HubConnectionBuilder()
.withUrl('ENTER HUB URL HERE',
{
transport: signalR.HttpTransportType.LongPolling,
accessTokenFactory: () => {
return 'BEARER TOKEN HERE'
}}).build()
connection.invoke('sendGroupMessage', text, hardCodedChatGroup)
Here is a sample of our SignalR Hub on the server:
[AbpMvcAuthorize]
public class OpenChatHub : Hub, ITransientDependency
{
public IAbpSession AbpSession { get; set; }
public ILogger Logger { get; set; }
public OpenChatHub()
{
AbpSession = NullAbpSession.Instance;
Logger = NullLogger.Instance;
}
public async Task SendGroupMessage(string message, string groupName)
{
// logic for the SendGroupMessage would be here
var msg = new
{
sendById = AbpSession.UserId, // this will be null at random times
message = message
};
await Clients.Group(group).SendAsync("receiveChatMessage", msg);
}
}
I can view the requests for SignalR negotiating and communicating with the Hub and I can see the token being passed correctly each time.

After doing a bit more research on this while trying to get a test project together that I could put on GitHub to reproduce the issue I did end up solving the issue.
Using the following inside of our Hub gives us the correct UserId each time now. Context.User.Identity.GetUserId();
I believe this must be a bug inside of aspnetboilerplate now. I will be trying to get an issue reported on the GitHub.

Related

Azure Function SignalR Negotiate function works but Send function fails

i have a xamarin app that is trying to talk to use SignalR in Azure functions.
i have 2 azure functions as per the documentation.
public static class NegotiateFunction
{
[FunctionName("negotiate")]
public static SignalRConnectionInfo GetSignalRInfo(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
//, UserId = "{headers.x-ms-client-principal-id}"
{
return connectionInfo;
}
}
and
public static class SendMessageFunction
{
[FunctionName("Send")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
// var chatObj = (ChatObject)(message);
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
// UserId = chatObj.ReciversId,
Target = "Send",
Arguments = new[] { message }
});
}
}
in my xamarin client i am connecting like this.
try
{
_connection = new HubConnectionBuilder()
.WithUrl("http://192.168.1.66:7071/api")
.Build();
_connection.On<string>("Send", (message) =>
{
AppendMessage(message);
});
await _connection.StartAsync();
}
I send message using this code in one of the pages of Xamarin app page.
try
{
await _connection.SendAsync("Send", MessageEntry.Text);
MessageEntry.Text = "";
}
connection code works it hits "negotiate" function properly but when i call SendAsync it does not hit break-point in [FunctionName("Send")] and nothing happens. It doesn't give me any exception as well.
local settings are like this
Update
i also tried Invoke. it didnt worked.
Should i try making a POST call to [FunctionName("Send")] ?
The way SignalR SaaS works in Functions is slightly different to using the NuGet package in a .NET Application.
You can't invoke a function using the SignalR library, as you can see on the attribute in your function, it's expecting a Http trigger so you have to do a POST to this endpoint instead of invoking it as you normally would.
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
You still want to listen to the Send target as normal.

How to persist SignalR connection ID

I’m trying to build a chat apps whereby users id are represented by their auto generated signalR connection id. On page refresh, the connection id changes when a new connection is instantiated. Is there a way to persist the state of the connection id of a user until the browser session is ended (i.e until he ends his session on client).
Any guide or documentation? It really would help.
i am new in signalr. so trying to know many things searching Google. from this url i got a similar snippet http://kevgriffin.com/maintaining-signalr-connectionids-across-page-instances/
they are saying it is possible. the problem is signalr often create a new connection id if we referesh the page. i want to prevent this but how.......
this code snippet.
public class MyConnectionFactory : IConnectionIdFactory
{
public string CreateConnectionId(IRequest request)
{
if (request.Cookies["srconnectionid"] != null)
{
return request.Cookies["srconnectionid"];
}
return Guid.NewGuid().ToString();
}
}
$.connection.hub.start().done(function () {
alert("Connected!");
var myClientId = $.connection.hub.id;
setCookie("srconnectionid", myClientId);
});
function setCookie(cName, value, exdays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = cName + "=" + c_value;
}
my doubt is does it work in all signalr version? if not then how to handle it in new version specially for not generate a new connection id if page gets refreshed. looking for suggestion.
if we work with Persistent connection class instead of hub then what happen.....in this case connection id will persist if we refresh the page at client side? please guide.
thanks
SignalR allows you to send messages to a user via their IPrincipal.Identity.Name. Just use Clients.User(userName) instead of Clients.Client(connectionId).
If you for some reason cannot address a user using their IPrincipal.Identity.Name you could create your own IUserIdProvider. This is the replacement for IConnectionIdFactory which no longer exists in SignalR >= 1.0.0.
The equivalent IUserIdProvider would look like this:
public class MyConnectionFactory : IUserIdProvider
{
public string GetUserId(IRequest request)
{
if (request.Cookies["srconnectionid"] != null)
{
return request.Cookies["srconnectionid"];
}
return Guid.NewGuid().ToString();
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var idProvider = new MyConnectionFactory();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
app.MapSignalR();
}
}
public class MyHub : Hub
{
public Task Send(string userName, string message)
{
return Clients.User(userName).receive(message);
}
}
It would be really trivial to spoof a user given this MyConnectionFactory. You could make it more secure by using an HMAC.
Ideally you would just use the default IUserIdProvider which retrieves the user ID from IRequest.User.Identity.Name.
The user id provider doesn't work, because the connection id doesn't come from it. I implemented a solution (https://weblogs.asp.net/ricardoperes/persisting-signalr-connections-across-page-reloads) that uses a pseudo-session id stored in session storage. Every SignalR connection id is then mapped to this pseudo-session id.

LiveAuthClient broken?

It seems very much that the current version of LiveAuthClient is either broken or something in my setup/configuration is. I obtained LiveSDK version 5.4.3499.620 via Package Manager Console.
I'm developing an ASP.NET application and the problem is that the LiveAuthClient-class seems to not have the necessary members/events for authentication so it's basically unusable.
Notice that InitializeAsync is misspelled aswell.
What's wrong?
UPDATE:
I obtained another version of LiveSDK which is for ASP.NET applications but now I get the exception "Could not find key with id 1" everytime I try either InitializeSessionAsync or ExchangeAuthCodeAsync.
https://github.com/liveservices/LiveSDK-for-Windows/issues/3
I don't think this is a proper way to fix the issue but I don't have other options at the moment.
I'm a little late to the party, but since I stumbled across this trying to solve what I assume is the same problem (authenticating users with Live), I'll describe how I got it working.
First, the correct NuGet package for an ASP.NET project is LiveSDKServer.
Next, getting user info is a multi-step process:
Send the user to Live so they can authorize your app to access their data (the extent of which is determined by the "scopes" you specify)
Live redirects back to you with an access code
You then request user information using the access code
This is described fairly well in the Live SDK documentation, but I'll include my very simple working example below to put it all together. Managing tokens, user data, and exceptions is up to you.
public class HomeController : Controller
{
private const string ClientId = "your client id";
private const string ClientSecret = "your client secret";
private const string RedirectUrl = "http://yourdomain.com/home/livecallback";
[HttpGet]
public ActionResult Index()
{
// This is just a page with a link to home/signin
return View();
}
[HttpGet]
public RedirectResult SignIn()
{
// Send the user over to Live so they can authorize your application.
// Specify whatever scopes you need.
var authClient = new LiveAuthClient(ClientId, ClientSecret, RedirectUrl);
var scopes = new [] { "wl.signin", "wl.basic" };
var loginUrl = authClient.GetLoginUrl(scopes);
return Redirect(loginUrl);
}
[HttpGet]
public async Task<ActionResult> LiveCallback(string code)
{
// Get an access token using the authorization code
var authClient = new LiveAuthClient(ClientId, ClientSecret, RedirectUrl);
var exchangeResult = await authClient.ExchangeAuthCodeAsync(HttpContext);
if (exchangeResult.Status == LiveConnectSessionStatus.Connected)
{
var connectClient = new LiveConnectClient(authClient.Session);
var connectResult = await connectClient.GetAsync("me");
if (connectResult != null)
{
dynamic me = connectResult.Result;
ViewBag.Username = me.name; // <-- Access user info
}
}
return View("Index");
}
}

SignalR - Set ClientID Manually

I want to be able to have individual users send messages to each other using SignalR, therefore I need to send to a Specific Client ID. How can I define the client ID for a specific user at the start of the session - say a GUID Primary Key for the user?
Replace the IConnectionIdFactory with your own https://github.com/SignalR/SignalR/wiki/Extensibility.
Sample usage:
http://www.kevgriffin.com/maintaining-signalr-connectionids-across-page-instances/
EDIT: This is no longer supported in the latest versions of SignalR. But you can define a user id for a specific connection using the new IUserIdProvider
In SignalR version 1, using the Hubs approach, I override the Hub OnConnected() method and save an association of a .NET membership userId with the current connection id (Context.ConnectionId) in a SQL database.
Then I override the Hub OnDisconnected() method and delete the association between the .NET membership userId and the current connection id. This means, on a page reload, the userId/connectionId association will be up-to-date.
Something along the lines of:
public class MyHub : Hub
{
private MembershipUser _user
{
get { return Membership.GetUser(); }
}
private Guid _userId
{
get { return (Guid) _user.ProviderUserKey; }
}
private Guid _connectionId
{
get { return Guid.Parse(Context.ConnectionId); }
}
public override Task OnConnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Create(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnConnected();
}
public override Task OnDisconnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Delete(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnDisconnected();
}
}
Then when I need to trigger a SignalR event for a specific user, I can work out the connectionId from the database association(s) with the current userId - there may be more than one association if multiple browser instances are involved.
The SignalR Client Side documentation outlines the following:
connection.id
- Gets or sets the client id for the current connection
This certainly indicates that one should be able to set the clientID client side, without all the above plumbing. Is this not working? If working, how would this line of code look like?

WCF Adding Custom Headers and Session

I have a web page that uses a WCF service. Multiple users maybe using the web page at any one time and therefore making requests to the WCF service which is on a remote machine.
Each user on the web page gets a unique ID, I want to add this unique ID to the request header of each request made by that user.
So far I have created the following code which correctly adds a header to the WCF message.
public class HeaderIdPusher : IClientMessageInspector
{
private static readonly string _balancerKey = "balancerId";
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
Guid userId = Guid.NewGuid();
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[_balancerKey]))
{
httpRequestMessage.Headers[_balancerKey] = userId.ToString();
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(_balancerKey, userId.ToString());
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
}
However I am no stuck because I can't get the ID to persist between requests. You can see here that at the moment I am generating an ID for each request, however I can't store this in the Session of the page the user is on because the HttpContext.Current is null. Is there another way of storing this? Is there another way of passing in the HttpContext of the user on my web page?
The problem is discussed here:
http://social.msdn.microsoft.com/forums/en-US/wcf/thread/27896125-b61e-42bd-a1b0-e6da5c23e6fc
Essentially WCF doesn't have sessions, as you could pass anything you wanted as a parameter (in this case, your Unique ID) and handle it any way you wanted in your implementation.
After much hacking I found a solution, it isn't great but it works.
In the ASP.NET page before I create the WCF service instance I create an address header and endpoint:
AddressHeader header = AddressHeader.CreateAddressHeader("MyKey", "http://www.w3.org/2005/08/addressing", "MyValue");
EndpointAddress endpoint = new EndpointAddress(new Uri("http://www.myservice.com/service"), header);
Then I create an instance of the service passing in the endpoint:
using (WcfService service = new WcfService(_configName,endpoint ))
{
}
This gets the data into the WCF service, then in the HeaderIdPusher : IClientMessageInspector detailed above I pull the header value out:
public class HeaderIdPusher : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
string id = "Not found";
if(channel.RemoteAddress.Headers.Any(x=>x.Name == "MyKey"))
{
id = channel.RemoteAddress.Headers.First(x => x.Name == "MyKey").GetValue<string>();
}
This solution isn't ideal and it puts extra data into the SOAP message but it is the only way I have found of sharing data from the ASP.NET page with the WCF process.

Resources