SignalR: how to survive accidental page refresh - signalr

Imagine there is a chat web app between two browser clients using SignalR. What is the best approach to survive accidental F5 hit done by one of the client?
Here is described the strategy using cookies together with custom SignalR IConnectionIdFactory. But the author of the article says "The SignalR team doesn’t recommend this action". So what is the recommended architecture?

You can map the temporary connection id to a permanent user id/name (which might be stored in your DB) when the client connects. See the ShootR example which does this (last time I checked, at least).
For example, you could keep a ConcurrentDictionary of objects representing users that you track active users with and associate them with their connection ids in OnConnected (and revert this in OnDisconnected) similar to this:
private static readonly ConcurrentDictionary<string, User> Users
= new ConcurrentDictionary<string, User>();
public override Task OnConnected() {
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
var user = Users.GetOrAdd(userName, _ => new User {
Name = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}

Related

Multiple Requests to Refresh Access Token At the Same Time In OIDC

I have a bit of a head-scratcher for updating a refresh tokens in a certain situation with a single page application making multiple api calls at the same time. I have an SPA which has a stack that consists of the following.
Html/JS SPA -> MVC Application -> WebAPI
I make use of the Hybrid flow, when a user logs onto the page I store the id_token the access_token and the refresh_token in the session cookie.
I use a HttpClient which has two DelegatingHandlers to talk to the web API. One of the delegating handlers simply adds the access token to the Authorization header. The other one runs before this and checks the lifetime left on the access token. If the access token has a limited amount of time left the refresh_token is used to get new credentials and save them back to my session.
Here is the code for the OidcTokenRefreshHandler.
public class OidcTokenRefreshHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly OidcTokenRefreshHandlerParams _handlerParams;
public OidcTokenRefreshHandler(IHttpContextAccessor httpContextAccessor, OidcTokenRefreshHandlerParams handlerParams)
{
_httpContextAccessor = httpContextAccessor;
_handlerParams = handlerParams;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
var handler = new JwtSecurityTokenHandler();
var accessTokenObj = handler.ReadJwtToken(accessToken);
var expiry = accessTokenObj.ValidTo;
if (expiry - TimeSpan.FromMinutes(_handlerParams.AccessTokenThresholdTimeInMinutes) < DateTime.UtcNow )
{
await RefreshTokenAsync(cancellationToken);
}
return await base.SendAsync(request, cancellationToken);
}
private async Task RefreshTokenAsync(CancellationToken cancellationToken)
{
var client = new HttpClient();
var discoveryResponse = await client.GetDiscoveryDocumentAsync(_handlerParams.OidcAuthorityUrl, cancellationToken);
if (discoveryResponse.IsError)
{
throw new Exception(discoveryResponse.Error);
}
var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = discoveryResponse.TokenEndpoint,
ClientId = _handlerParams.OidcClientId,
ClientSecret = _handlerParams.OidcClientSecret,
RefreshToken = refreshToken
}, cancellationToken);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResponse.IdentityToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResponse.AccessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResponse.RefreshToken
}
};
// Sign in the user with a new refresh_token and new access_token.
var info = await _httpContextAccessor.HttpContext.AuthenticateAsync("Cookies");
info.Properties.StoreTokens(tokens);
await _httpContextAccessor.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
}
}
The problem is that many calls hit this at roughly the same time. All of these calls will then hit the refresh endpoint at the same time. They will all retrieve new valid access tokens and the application will continue to work. However if 3 requests happen at the same time, three new refresh tokens will be created and only one of these will be valid. Due to the asynchronous nature of the application I have no guarantee that the refresh token stored in my session is actually the latest refresh token. The next time I need to refresh the refresh token may be invalid (and often is).
My thoughts on possible solutions so far.
Lock at the point of checking the access token with a Mutex or similar. However this has the potential to block when it is being used by a different user with a different session (to the best of my knowledge). It also doesn't work if my MVC app is across multiple instances.
Change so the refresh tokens remain valid after use. So it doesn't matter which one of the three gets used.
Any thoughts on which of the above is better or has anyone got a really clever alternative.
Many Thanks!
When all your requests come from the same SPA, the best should be to sync them in the browser and get rid of the problem serverside. Each time your client code requires a token, return a promise. The same promise instance to all requests, so they all get resolved with the only request to the server.
Unfortunately if you proxy all the requests through your local API and never pass your bearer to the SPA, my idea wouldn't work.
But if you keep your refresh token absolutely secure (never send it to the front), I can't see any problem to make it reusable. In that case you can switch on sliding option as excellently described here to perform less renewal requests.

ASP.NET MVC Custom user fields on every page

Background:
I'm building more and more web applications where the designers / template makers decide that adding a "profile picture" and some other user-related data, of course only when someone is logged in.
As most ASP.NET MVC developers I use viewmodels to provide razor layouts with the information that I need shown, sourced from repositories et al.
It is easy to show a user name through using
HttpContext.Current.User.Identity.Name
What if I want to show information that's saved in my backing datastore on these pages? Custom fields in the ApplicationUser class like a business unit name or a profile picture CDN url.
(for sake of simplicity let's assume I use the Identity Framework with a Entity Framework (SQL database) containing my ApplicationUsers)
Question
How do you solve this:
Without poluting the viewmodel/controller tree (e.g. building a BaseViewModel or BaseController populating / providing this information?
Without having to roundtrip the database every page request for these details?
Without querying the database if a user is not logged in?
When you cannot use SESSION data (as my applications are often scaled on multiple Azure instances - read why this isn't possible here- I'm not interested in SQL caching or Redis caching.
I've thought about using partials that new their own viewmodel - but that would still roundtrip the SQL database every pageload. Session data would be safe for now, but when scaled up in azure this isn't a way either. Any idea what would be my best bet?
TLDR;
I want to show user profile information (ApplicationUser) on every page of my application if users are logged in (anon access = allowed). How do I show this info without querying the database every page request? How do I do this without the Session class? How do I do this without building base classes?
The best way with Identity is to use claims to store custom data about the user. Sam's answer pretty close to what I'm saying here. I'll elaborate a bit more.
On ApplicationUser class you have GenerateUserIdentityAsync method which used to create ClaimsIdentity of the user:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
new Claim("MyApp:LastName",this.LastName),
});
return userIdentity;
}
This adds key-value pairs on the user identity that is eventually serialised and encrypted in the authentication cookie - this is important to remember.
After user is logged in, this Identity are available to you through HttpContext.Current.User.Identity - that object is actually ClaimsIdentity with claims taken from the cookie. So whatever you have put into claims on login time are there for you, without having to dip into your database.
To get the data out of claims I usually do extension methods on IPrincipal
public static String GetFirstName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new DomainException("User is not authenticated");
}
var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
if (personNameClaim != null)
{
return personNameClaim.Value;
}
return String.Empty;
}
This way you can access your claims data from your Razor views: User.GetFirstName()
And this operation is really fast because it does not require any object resolutions from your DI container and does not query your database.
The only snag is when the values in the storage actually updated, values in claims in the auth cookie are not refreshed until user signs-out and signs-in. But you can force that yourself via IAuehtenticationManager.Signout() and immediately sign them back in with the updated claims values.
You could store your extra information as claims. In your log in method fill your data to generated identity. For example if you are using Identity's default configuration you could add your claims in ApplicationUser.GenerateUserIdentityAsync() method:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyValueName1","value1"),
new Claim("MyValueName2","value2"),
new Claim("MyValueName2","value3"),
// and so on
});
return userIdentity;
}
And in your entire application you have access those information by reading current user claims. Actually HttpContext.Current.User.Identity.Name uses same approach.
public ActionResult MyAction()
{
// you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
var claims = ((ClaimsIdentity)User.Identity).Claims;
// or
var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
}
I think the "how to" is a little subjective as there are probably many possible ways to go about this but I solved this exact problem by using the same pattern as HttpContext. I created a class called ApplicationContext with a static instance property that returns an instance using DI. (You could alter the property to generate a singleton itself as well if you aren't, for some reason, using DI.)
public interface IApplicationContext
{
//Interface
string GetUsername();
}
public class ApplicationContext : IApplicationContext
{
public static IApplicationContext Current
{
get
{
return DependencyResolver.Current.GetService<IApplicationContext>();
}
}
//appropriate functions to get required data
public string GetUsername() {
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
return HttpContext.Current.User.Identity.Name;
}
return null;
}
}
Then you just reference the "Current" property in your view directly.
#ApplicationContext.Current.GetUsername()
This would solve all of you requirements except #2. The database call may not add a significant enough overhead to warrant avoiding altogether but if you require it then your only option would be to implement some form of caching of the user data once it is queried the first time.
Simply implement ChildAction with caching and vary by loggedin user

What is the way to perform some session cleanup logic regardless of user logout/timeout/browser close?

I have an IIS hosted web application with a C# backend.
When a user logs in, I want to instantiate an instance of HttpClient() for the logged in user to communicate with the back-end over a REST API. Once that client is created, the backend will initialize some user-specific memory which should be cleared once the user has logged out (that is, the HttpClient() object is disposed).
It seems like the right thing to do here is to instantiate that HttpClient() object at log-in, and then have some code that is called when either the user manually logs out or the user session times out or the user closes the browser, and that code will dispose of the HttpClient() manually.
This is surely a well-travelled problem, so there must be an elegant solution to it. How can I dispose of this user-specific HttpClient() when any possible log-out scenario occurs (manual/timeout/browser close)?
Handling the departure of a web user is not trivial, as the HTTP protocol is stateless. The server can never be certain if the user is still there; a HTTP connection that gets closed doesn't mean that user have to have gone away, and the server can think that a connection is still open eventhough the user is no longer there.
Unless you will be using the HttpClient object intensly, so that you expect that keeping it alive would save a lot of resources, you should just dispose it at the end of each REST request, and open a new one for the next request.
A web request normally takes a short time to handle, and most resources used for it is freed when the request is gone. That make most of the objects short lived, and those are the ones that the garbage collector handles most efficiently. Holding on to objects across several requests makes them very long lived, which uses up memory on the server, and make the garbage collector work harder. Unless there is a specific reason to hold on to an object, you shouldn't let it live longer than it takes to handle the request.
What you could do is create a class which performs the user-specific memory functions you want to perform. This class would contain a method which instantiates the HttpClient() object and then performs the user-specific operations(functions). This class would also contain another method which clears the user-specific memory functions i.e. it disposes the HttpClient() object and performs cleanup of any user-specific data.
So, essentially, you code would look like this:
public class HttpHelper
{
public void LoadUserInformation()
{
HttpClient httpClientObj = new HttpClient();
//perform user-specific tasks
//your logic here
//Store the httpClientObj object in session
}
public void DisposeUserInformation()
{
//Fetch the httpClientObj from session
//perform user-specific tasks
//your logic here
httpClient.Dispose();
}
}
Now, in either of the scenarios, whether the session times out or the user logs out, you could call the DisposeUserInformation() method and that would handle both of your scenario's be it session timing out or user logging out.
There is a Session_End() method in global.asax. The global.asax file will be wired to call this method when the session ends. You can call the DisposeUserInformation() method there.
You could also call this method on the logout button click in the controller.
Hope this helps!!!
I really don't recommend storing anything IDisposable in the session. What if in the process of downloading from the Web APi, in another window the user clicks Logout, you disposed of the HttpClient while it's in use. That is a small edge-case, but there can be plenty of edge cases with storing IDisposable in session. Also if you need to scale out to multiple servers, that requires storing Session in something other than in-proc which requires the object to be serializable (which HttpClient is not).
Instead:
[serializable]
public sealed class ApiClient
{
public ApiClient(uri baseAddress)
{
this._BaseAddress = baseAddress;
}
public Uri BaseAddress { get; set; }
public IEnumerable<Person> GetPersons()
{
var address = new Uri(this.BaseAddress, "Employees/Persons");
using (var client = new HttpClient())
{
// something like this
var task = GetStringAsync(address);
await task;
var json = task.Result;
}
}
}
Nice session wrapper:
public static class SessionExtensions
{
public static bool TryGetValue<T>(this HttpSessionStateBase session, out T value)
where T : class
{
var name = typeof(T).FullName;
value = session[name] as T;
var result = value != null;
return result;
}
public static void SetValue<T>(this HttpSessionStateBase session, T value)
{
var name = typeof(T).FullName;
session[name] = value;
}
public static void RemoveValue<T>(this HttpSessionStateBase session)
{
var name = typeof(T).FullName;
session[name] = null;
}
public static bool ValueExists(this HttpSessionStateBase session, Type objectType)
{
var name = objectType.FullName;
var result = session[name] != null;
return result;
}
}
Now you can create the api per client:
Session.SetValue(new ApiClient(new Uri("http://localhost:443")));
Somewhere else you can get persons:
ApiClient client;
if (Session.TryGetValue(out client))
{
client.GetPersons();
}

SignalR polling database for updates

I'm hoping to use SignalR to provide updates to the client, the updates are going to come from a message table which is updated when things happen across the application..
My problem is that the application will have around 500-600 concurrent users and I cant have all them having a connection to the database and constantly polling against the table..
What id like to do is have a single thing{?} polling the table and then updating the hubs rather than each connection polling.. I was thinking of using a singleton for this? so maybe when the application starts something is created that will then do all the work really..
My question is - say I had a singleton that had an event which was fired every time there was an update.. what would the performance be like for say 500 controllers subscribing to this event?
Also.. if there is a better way to do this then pleases say.. this is my first and only idea sadly!
any help would be fantastic!
EDIT: the data is bring provided by a legacy application and I have no control over how the data is entered so database polling will be needed.
ste.
I'd rather not to poll the database as it would be wasteful. I would approach this problem by opening only one single point of entry for my data (an HTTP API, etc) and then broadcast the update to all connected clients through the SignalR Hub. Brad Wilson has a super cool presentation which demonstrate this approach:
Brad Wilson - Microsoft’s Modern Web Stack, Starring ASP.NET Web API
Here is a code sample for this approach which uses ASP.NET Web API technology for data entry. It uses in-memory dictionary for data store but the data storage technique is not the concern here:
// This hub has no inbound APIs, since all inbound communication is done
// via the HTTP API. It's here for clients which want to get continuous
// notification of changes to the ToDo database.
[HubName("todo")]
public class ToDoHub : Hub { }
public abstract class ApiControllerWithHub<THub> : ApiController
where THub : IHub {
Lazy<IHubContext> hub = new Lazy<IHubContext>(
() => GlobalHost.ConnectionManager.GetHubContext<THub>()
);
protected IHubContext Hub {
get { return hub.Value; }
}
}
public class ToDoController : ApiControllerWithHub<ToDoHub> {
private static List<ToDoItem> db = new List<ToDoItem> {
new ToDoItem { ID = 0, Title = "Do a silly demo on-stage at NDC" },
new ToDoItem { ID = 1, Title = "Wash the car" },
new ToDoItem { ID = 2, Title = "Get a haircut", Finished = true }
};
private static int lastId = db.Max(tdi => tdi.ID);
// Lines removed for brevity
public HttpResponseMessage PostNewToDoItem(ToDoItem item) {
lock (db) {
// Add item to the "database"
item.ID = Interlocked.Increment(ref lastId);
db.Add(item);
// Notify the connected clients
Hub.Clients.addItem(item);
// Return the new item, inside a 201 response
var response = Request.CreateResponse(HttpStatusCode.Created, item);
string link = Url.Link("apiRoute", new { controller = "todo", id = item.ID });
response.Headers.Location = new Uri(link);
return response;
}
}
// Lines removed for brevity
}
The full source code for the application which Brad demoed is also available: https://github.com/bradwilson/ndc2012.
The other option, which you don't prefer, is make your database to fire notifications as soon as data is changed. Then, you can pick that up and broadcast it through SignalR. Here is an example:
Database Change Notifications in ASP.NET using SignalR and SqlDependency
Sorry that this solution is not signalR, but maybe you can get ideas from it.
Here is the full example for download on GitHub

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?

Resources