Managing SignalR connections for Anonymous user - c#

I am using SignalR version 2.1.2 with ASP.Net MVC 5 & NServiceBus and have following requirement
There is a signup page (anonymous authentication) in which SignalR is used to send notifications. Every form submit will generate a new connection id which needs to be kept in a collection so that I can send response to the client. Context.User.Identity.Name is empty hence _connections.Add(name, Context.ConnectionId); cannot be used in OnConnected() hub event as given in this post
Similar problem exists in Login page.
If there is a possibility to control the ConnectionId then I could overcome this situation but it looks like new version of SignalR has got rid of connection factory.
I am using Redis cache so one option is to write my own connection management code to keep these connection ids in it.
Second option is to use Forms Authentication in such a way that a 'Anonymous Role' is assigned to these users which restricts the usage to anonymous views/controllers but gives a 'Name' to the user so that Context.User.Identity.Name is not empty. With this I can use built in SignalR mechanism to manage connection ids for me.

This is what we did in BaseAnonymousController
public class BaseAnonymousController : Controller
{
protected override void OnAuthentication(System.Web.Mvc.Filters.AuthenticationContext filterContext)
{
if (filterContext.Controller.GetType().Name == "AccountController" && filterContext.ActionDescriptor.ActionName == "login")
{
Guid result;
if (!string.IsNullOrEmpty(SessionVariables.UserId) && Guid.TryParse(SessionVariables.UserId, out result))
{
//Already a anonymous user, so good to go.
}
else
{
//Seems to be a logged in a user. So, clear the session
Session.Clear();
}
}
//Perform a false authentication for anonymous users (signup, login, activation etc. views/actions) so that SignalR will have a user name to manage its connections
if (!string.IsNullOrEmpty(SessionVariables.UserId))
{
filterContext.HttpContext.User = new CustomPrincipal(new CustomIdentity(SessionVariables.UserId, "Anonymous"));
}
else
{
string userName = Guid.NewGuid().ToString();
filterContext.HttpContext.User = new CustomPrincipal(new CustomIdentity(userName, "Anonymous"));
FormsAuthentication.SetAuthCookie(userName, false);
SessionVariables.UserId = userName;
}
base.OnAuthentication(filterContext);
}
}
and used this class as base class for all of anonymous controllers.
public class AccountController : BaseAnonymousController
{
[AllowAnonymous]
public ActionResult Signup()
{
//Your code
}
[AllowAnonymous]
public ActionResult Login()
{
//Your code
}
[AllowAnonymous]
public ActionResult ForgotPassword()
{
//Your code
}
[AllowAnonymous]
public ActionResult ForgotUsername()
{
//Your code
}
}
In the SignalR hub (nothing extraordinary than what is in SignalR documentation)
public override Task OnConnected()
{
SignalRConnectionStore.Add(Context.User.Identity.Name, Context.ConnectionId);
return base.OnConnected();
}
public override Task OnReconnected()
{
string name = Context.User.Identity.Name;
//Add the connection id if it is not in it
if (!SignalRConnectionStore.GetConnections(name).Contains(Context.ConnectionId))
{
SignalRConnectionStore.Add(name, Context.ConnectionId);
}
return base.OnReconnected();
}
public override Task OnDisconnected(bool stopCalled)
{
SignalRConnectionStore.Remove(Context.User.Identity.Name, Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
This works for both anonymous and authenticated users.
SignalRConnectionStore class and Interface
public interface ISignalRConnectionStore
{
int Count { get; }
void Add(string userName, string connectionId);
IEnumerable<string> GetConnections(string userName);
void Remove(string userName, string connectionId);
}
internal class SignalRConnectionStore : ISignalRConnectionStore
{
private readonly Dictionary<string, HashSet<string>> _connections = new Dictionary<string, HashSet<string>>();
public int Count
{
get
{
return _connections.Count;
}
}
public void Add(string userName, string connectionId)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(connectionId))
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(userName, out connections))
{
connections = new HashSet<string>();
_connections.Add(userName, connections);
}
lock (connections)
{
connections.Add(connectionId);
}
}
}
}
public IEnumerable<string> GetConnections(string userName)
{
if (!string.IsNullOrEmpty(userName))
{
HashSet<string> connections;
if (_connections.TryGetValue(userName, out connections))
{
return connections;
}
}
return Enumerable.Empty<string>();
}
public void Remove(string userName, string connectionId)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(connectionId))
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(userName, out connections))
{
return;
}
lock (connections)
{
connections.Remove(connectionId);
if (connections.Count == 0)
{
_connections.Remove(userName);
}
}
}
}
}
}
Declare a static variable of SignalRConnectionStore in Hub class as below.
public class ProvisioningHub : Hub
{
private static ISignalRConnectionStore SignalRConnectionStore;
public ProvisioningHub(ISignalRConnectionStore signalRConnectionStore)
: base()
{
SignalRConnectionStore = signalRConnectionStore; //Injected using Windsor Castle
}
}

Use Forms Authentication, store a Federated Cookie and store the hub region in the cookie as well..
In SignalR jQuery code, use a jQuery plugin to read HTTP cookie and get the region name and subscribe to notifications.
Alternatively, in your .cshtml, render jQuery with region populated from your View Model.
Note: Use FormsAuthentication.SetAuthCookie as this will create HTTP Only cookie and will be sent in Ajax and non-Ajax calls.

Related

.NET Core Singleton Session Wrapper

I am trying to create a Session Wrapper class for ASP.NET Core Razor Pages.
In traditional ASP.NET web forms my singleton session wrapper class used to work normally, but i am not sure if in .net core it will work the same or the same class will be shared across all requests.
I want to store specific information for each request (session) and not to be shared across all requests.
i created the following wrapper :
public class MySession
{
//the ISession interface mandatory
public ISession Session { get; set; }
private static MySession instance;
private MySession() {
}
public static MySession Instance
{
get
{
if (instance == null)
{
instance = new MySession();
}
return instance;
}
}
//properties
public User User
{
get => SessionHelper.SessionExtensions.Get<User>(this.Session, "User");
set => SessionHelper.SessionExtensions.Set<User>(this.Session, "User", value);
}
public bool LoggedIn
{
get => SessionHelper.SessionExtensions.Get<bool>(this.Session, "LoggedIn");
set => SessionHelper.SessionExtensions.Set<bool>(this.Session, "LoggedIn", value);
}
}
and from my page model (Login.cshtml.cs) i am doing the following :
public void OnGet()
{
MySession.Instance.Session = HttpContext.Session;
}
and i am accessing the session and storing and retrieving information perfectly as expected for example :
public ActionResult OnPostAuthenticate()
{
string username = Request.Form["username"];
string password = Request.Form["password"];
User user = this.userDataManager.GetUserAuthentication(username, password);
if (user != null)
{
//authentication success
//save session variables
MySession.Instance.User = user;
MySession.Instance.LoggedIn = true;
return new JsonResult(new { success = true });
}
else
{
return new JsonResult(new { success = false });
}
}
And as i said everything work perfect, but i want to know if it is SAFE to use the session this way or my requests will be messed up and the same session information will be shared across all requests ?

Authorize application service based on client IP address

We have implemented some Application Service methods without any permission. How can we implement authorization based on client IP address for executing methods?
For example, this is GetParsedData method:
public GetParsedDataOutput GetParsedData(GetParsedDataInput input)
{
return _cacheManager.GetCache(nameof(GetData)).Get(input.ToString(), () => gpd(input)) as GetParsedDataOutput;
}
How can we check user permission by IP address? Suppose that client with IP address 192.168.5.2 is granted permission to execute this method.
You can inject IClientInfoProvider to get ClientIpAddress.
Authorize an authenticated user
Override IsGrantedAsync in PermissionChecker:
public override async Task<bool> IsGrantedAsync(long userId, string permissionName)
{
if (permissionName == MyClientIpAddressPermissionName)
{
return Task.Run(() => { return _clientInfoProvider.ClientIpAddress == "192.168.5.2"; });
}
return await base.IsGrantedAsync(userId, permissionName);
}
Usage:
[AbpAuthorize(MyClientIpAddressPermissionName)]
public GetParsedDataOutput GetParsedData(GetParsedDataInput input)
{
// ...
}
Authorize an anonymous user
Since AbpAuthorize requires a user, you should use a custom (i) attribute, (ii) interceptor, and (iii) interceptor registrar.
(i) Attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ClientIpAuthorizeAttribute : Attribute
{
public string AllowedIpAddress { get; set; }
}
(ii) Interceptor:
internal class ClientIpAuthorizationInterceptor : IInterceptor
{
private readonly IClientInfoProvider _clientInfoProvider;
public ClientIpAuthorizationInterceptor(IClientInfoProvider clientInfoProvider)
{
_clientInfoProvider = clientInfoProvider;
}
public void Intercept(IInvocation invocation)
{
var methodInfo = invocation.MethodInvocationTarget;
var clientIpAuthorizeAttribute = methodInfo.GetCustomAttributes(true).OfType<ClientIpAuthorizeAttribute>().FirstOrDefault()
?? methodInfo.DeclaringType.GetCustomAttributes(true).OfType<ClientIpAuthorizeAttribute>().FirstOrDefault();
if (clientIpAuthorizeAttribute != null &&
clientIpAuthorizeAttribute.AllowedIpAddress != _clientInfoProvider.ClientIpAddress)
{
throw new AbpAuthorizationException();
}
invocation.Proceed();
}
}
(iii) Interceptor registrar:
internal static class ClientIpAuthorizationInterceptorRegistrar
{
public static void Initialize(IIocManager iocManager)
{
iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
{
if (ShouldIntercept(handler.ComponentModel.Implementation))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(ClientIpAuthorizationInterceptor)));
}
};
}
private static bool ShouldIntercept(Type type)
{
if (type.GetTypeInfo().IsDefined(typeof(ClientIpAuthorizeAttribute), true))
{
return true;
}
if (type.GetMethods().Any(m => m.IsDefined(typeof(ClientIpAuthorizeAttribute), true)))
{
return true;
}
return false;
}
}
Initialize the registrar in your Application module:
public override void PreInitialize()
{
ClientIpAuthorizationInterceptorRegistrar.Initialize(IocManager);
}
Usage:
[ClientIpAuthorize(AllowedIpAddress = "192.168.5.2")]
public GetParsedDataOutput GetParsedData(GetParsedDataInput input)
{
// ...
}
You should be able to extend that yourself to allow/disallow multiple IP addresses.
To fallback on permission names for an authenticated user, add the permission name as a string property in the attribute. Then inject IAbpSession and IPermissionChecker in the interceptor to call the IsGrantedAsync method.
You can write your own injector service for IApplicationService. And just before the application service method executes, you can make pre-checks.
See how to implement the injection
https://aspnetboilerplate.com/Pages/Documents/Dependency-Injection

Custom authentication in ASP.NET MVC using session

There are many questions about custom authentication in ASP.NET, but none of them answers how to fully integrate it with ASP.NET mechanics.
I want to make a web application for system which already exists and have users.
Let's create new ASP.NET MVC project. I choose "No authentication" to make it from scratch (because I need to read from custom tables, custom hashing function etc, so I'll go this way).
I'll use IIdentity and IPrincipal interfaces to carry logged in user in HttpContext.
public class Identity : IIdentity
{
public Identity(string name)
{
Name = name;
IsAuthenticated = true;
}
public string Name { get; private set; }
public string AuthenticationType { get; set; }
public bool IsAuthenticated { get; set; }
}
public class Principal : IPrincipal
{
public Principal(string email)
{
Identity = new Identity(email);
}
public IIdentity Identity { get; private set; }
public bool IsInRole(string role)
{
return false;
}
}
I'll create SessionsController which will create and destroy Session. Session will contain Id of logged in user.
public class UserManager
{
public bool Authenticate(WorkerDTO user, string password)
{
using (var context = new DatabaseContext())
{
var user = context.Users.SingleOrDefault(w => w.Email == user.Email);
if (user != null)
{
// compute SHA512 of password with salt
var hash = Hash(user.Password);
if (user.HashPassword == hash)
return true;
}
return false;
}
}
}
public class SessionsController : Controller
{
private IDatabaseManager _dbManager;
private UserManager _userManager;
public SessionsController(IDatabaseManager dbManager, UserManager userManager)
{
_dbManager = dbManager;
_userManager = userManager;
}
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(UserLoginViewModel model)
{
var user = _dbManager.Give(new WorkerByEmail(model.Email));
if(user != null && _userManager.Authenticate(user, model.Password))
{
// create session
Session["UserId"] = worker.Id;
// assign User property
User = new Principal(worker.Email);
return RedirectToAction("Index", "Home");
}
return RedirectToAction("New");
}
public ActionResult Logout()
{
Session.Remove("UserId");
User = null;
return View();
}
}
Application won't remember that User = new Principal(worker.Email);.
So, if I want to make use of sessions, I want to tell my ASP.NET application that User behind UserId carried by session (probably a cookie) each request is the logged in user. In Global.asax I have available events with reasonable names:
AuthenticateRequest
PostAuthenticateRequest.
Unfortunately, Session is unavailable in these events.
How to make use of ASP.NET in my application? Maybe Session isn't the way to go?
Should I create new IPrincipal object and attach to User prop each request?
And why User property isn't null at start? Should I set it to null on logout?

Authentication for MVC and WebAPI using customer user, role tables

I need to create a authentication for my MVC Application and WebAPI.
I have the user credential details & role information in a separate table in database. Can anyone suggest which model i can use to achieve this.
Thanks
Which Web Api are you using if it is 2 than try below code, and let me know if i could help you more, because i had same scenario like you have
you have to create a custom authorization filter and call it above ActionMethod,
Create a different class in your project and change build mode in Compile
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public static bool VaidateUserRoleWise(string username, string password, int RoleId)
{
//DO DATABASE CONNECTION DO QUERY HERE
if (Username == username && Password == password)
{
return true;
}
else
{
return false;
}
}
public override void OnAuthorization(QuizzrApi.Controllers.QuizzrController.InputParamAdminLogin LoginDetails)
{
System.Web.Http.Controllers.HttpActionContext actionContext = null;
if (LoginDetails == null)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
else
{
//Bellow is the static method called above will return true or false if user matches
if (!VaidateUserRoleWise(LoginDetails.UserName, LoginDetails.Password, 1))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
base.OnAuthorization(actionContext);
}
}
In controller :
[Route("AuthorizeSystemAdmin")]
[HttpPost]
[BasicAuthentication]
public HttpResponseMessage Login([FromBody] InputParamAdminLogin AdminLoginInput)
{
//do your logic here
}

SignalR connection to old browser drops if new browser opens

I have application in which I am showing data from sensors using SignalR. It uses ASP.net membership to authenticate the users. It all works fine if I only open one browser window(e.g. Firefox). If I open same website in another browser e.g. Chrome at the same time then signalR connection to firefox browser drops even if the user is different. This is what I am using to broadcast message:
Hub
[Authorize]
public class DataHub:Hub
{
private readonly RealTimeData _sensor;
public DataHub() : this(RealTimeData.Instance) { }
public DataHub(RealTimeData data)
{
_sensor = data;
}
public override Task OnConnected()
{
// _sensor.UserId = Context.ConnectionId; changed to
_sensor.UserId = Membership.GetUser().ProviderUserKey.ToString();
return base.OnConnected();
}
}
public class RealTimeData
{
//User Id
public String UserId { get; set; }
private readonly static Lazy<RealTimeData> _instance = new Lazy<RealTimeData>(() => new RealTimeData(GlobalHost.ConnectionManager.GetHubContext<DataHub>().Clients));// Singleton instance
private IHubConnectionContext Clients;
private void BroadcastDataOfAllSensors(List<SensorDetails> sensor)
{
//Clients.Client(UserId).updateDashboard(sensor);changed to
Clients.User(UserId).updateDashboard(sensor);
}
}
Application Startup
public class StartUp
{
public void Configuration(IAppBuilder app)
{
var idProvider = new UserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
app.MapSignalR();
}
}
UserId
public class UserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
var userId = Membership.GetUser().ProviderUserKey;
return userId.ToString();
}
}

Categories