I managed to get a working solution with SignalR
First, pay attention to the order in which SignalR is setup in the Startup.Auth.cs
How to send message via SignalR to a specific User(Identity Id)? and also create an implementation of the IUserIdProvider that is going to be registred only AFTER the Cookies and OwinContext in order to make it able to leverage the Identity User field (i.e. non-null).
public partial class Startup
{
public void ConfigureAuth(IAppBuilder appBuilder)
{
// Order matters here...
// Otherwise SignalR won't get Identity User information passed to Id Provider...
ConfigOwinContext(appBuilder);
ConfigCookies(appBuilder);
ConfigSignalR(appBuilder);
}
private static void ConfigOwinContext(IAppBuilder appBuilder)
{
appBuilder.CreatePerOwinContext(ApplicationDbContext.Create);
appBuilder.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext(LdapAdEmailAuthenticator.Create);
}
private static void ConfigCookies(IAppBuilder appBuilder)
{
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>
(
TimeSpan.FromHours(4),
(manager, user) => user.GenerateUserIdentityAsync(manager)
)
}
});
appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
appBuilder.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
appBuilder.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new HubIdentityUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
}
public class HubIdentityUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
return request == null
? throw new ArgumentNullException(nameof(request))
: request.User?.Identity?.GetUserId();
}
}
Second, declare a hub on the server-side
public class UserHub : Hub
{
}
Third, in a controller (API or not) where a change that involves a logout of a specific user, force a signout + an update of the identity securitystamp:
var userHub = GlobalHost.ConnectionManager.GetHubContext<UserHub>();
userHub.Clients.User(userId).send("Roles added: " + rolesToAdd.Join() + Environment.NewLine + "Roles removed: " + rolesToRemove.Join());
return Request.CreateResponse(HttpStatusCode.OK);
Fourth, use the hub on the JS client-side, I created a partial view which is only used when the current user is authenticated, LoggedOutPartialView.cshtml
:
@if (Request.IsAuthenticated)
{
<div class="modal fade" id="loggedOutModal" tabindex="-1" role="dialog" aria-labelledby="loggedOutModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="loggedOutModalLabel">Notification</h4>
</div>
<div class="modal-body">
<h6 class="align-center">Sorry, but it seems that you just have been logged out!!!</h6>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
var userHub = $.connection.userHub;
console.log(userHub.client);
userHub.client.logout= function (message) {
$('#loggedOutModal').modal('show');
};
$.connection.hub.start().done(function () {
});
});
</script>
}