Greetings,
can someone give me some advices or links that will help me to implement to following scenario.
Page will be written in asp.net mvc. Authorization is going to be implemented by Memberships. The scenario is as follows:
User1 has just logged in. After a
while, User2 attempts to login with
success. Then user1 should be notified
that User2 has just logged in. Additionally User2
should be notified that User1 is
online.
How can I achieve something like that? It should also be possible for these users to write messages to each other. (chat like).
There are methods to do this in the asp.net membership providers, specifically, IsUserOnline() and something like CountUsersOnline(). The only problem with these methods is that they are really lame. They depend on the membership provider's LastActivityDate() and a window you can set in web.config. In other words, the user is considered online if his last encounter with the membership provider plus the time window in web.config has not expired.
We took this scenairo and made it work for us by setting up a Comet server, and pinging the Web server every ten minutes. When the Web server is pinged, it updates the LastActivityDate of the membership provider.
We set the activity window to 12 minutes, as well as the Session timer. This allows us to determine who is online to an accuracy of aproximately ten minutes.
Here is the line in Web.config:
<membership userIsOnlineTimeWindow="12">
Here is jQuery Comet server:
function getData() {
$.getJSON("/Account/Timer", gotData);
}
// Whenever a query stops, start a new one.
$(document).ajaxStop(getData, 600000);
// Start the first query.
getData();
Here's our server code:
public JsonResult Timer()
{
MembershipUser user = Membership.GetUser(User.Name);
user.LastActivityDate = DateTime.Now;
Membership.UpdateUser(user);
// You can return anything to reset the timer.
return Json(new { Timer = "reset" }, JsonRequestBehavior.AllowGet);
}
Sounds to me like you need some jQuery polling to happen.
You can easily do a jQuery post to an ActionResult which would then check for users online and returns a PartialView back to the calling jQuery function.
The returning PartialView might have all the users logged in which can then be popped up in some sort of animating panel.
Use javascript to execute a timed poll back to the controller.
You cannot get onlines live in web. you should refresh page or refresh content with ajax -or else-. So it gonna solve i think.
ps. chat and online issues, you have two options; you can store them in database -its what i suggest- or store in memory, you may want to look this
Tables:
Users
-Id / int - identity
-LoginTime / datetime
-IsOnline / bit
Friends
-Id / int - identity
-FirstUserId / int
-SecondUserId / int
public class UserInformation
{
public IList<User> OnlineFriends { get; set;}
public IList<User> JustLoggedFriends { get; set; } /* For notifications */
}
public class UserRepository
{
public UserInformation GetInformation(int id, DateTime lastCheck)
{
return Session.Linq<User>()
.Where(u => u.Id == id)
.Select(u => new {
User = u,
Friends = u.Friends.Where(f => f.FirstUser.Id == u.Id || f.SecondUser.Id == u.Id)
})
.Select(a => new UserInformation {
JustLoggedFriends = u.Friends.Where(f => f.IsOnline && f.OnlineTime >= lastCheck).ToList(),
OnlineFriends = u.Friends.Where(f => f.IsOnline).ToList()
})
.ToList();
}
}
public class UserService
{
public UserInformation GetInformation(int id, DateTime lastCheck)
{
return repository.GetInformation(id, lastCheck);
}
}
UI:
public class UserController
{
public ActionResult Index()
{
var userId = (int)Session["UserId"];
var lastCheck = (DateTime)Session["LastCheck"];
UserInformation info = userService.GetInformation(userId, lastCheck);
Session["LastCheck"] = DateTime.Now;
//show up notifications and online users.
}
public ActionResult Login()
{
User user = null; // TODO: get user by username and password
Session["UserId"] = user.Id;
Session["LastCheck"] = DateTime.Now;
}
}
Related
I am working on a DOT NET Core project in which I have to perform the task of auto-login. I have tried several ways but did not find the solution.
[HttpPost]
public async Task<IActionResult> IndexAsync(myLoginModel lm){
if (lm.IsRemember)
{
CookieOptions option = new CookieOptions();
option.Expires = DateTime.Now.AddDays(10);
Response.Cookies.Append("UserEmail", lm.Email, option);
Response.Cookies.Append("UserPassword", lm.Password, option);
Response.Cookies.Append("IsRemember", lm.IsRemember.ToString(), option);
}
}
Now during 10-days if the user opens the website, I want to redirect to the DASHBOARD PAGE Directly.
public IActionResult Index()
{
string IsRemember= Request.Cookies["IsRemember"];
if (IsRemember== "True")
{
lm.Email = Request.Cookies["UserEmail"];
lm.Password = Request.Cookies["UserPassword"];
//Check if user visited the website during 10 days of time then redirect to DASHBOARD PAGE.
}
}
I have tried many ways in which the below code is one of them but did not get the perfect solution.
Tried Way 1
foreach (HttpCookie thisCookie in this.Request.Cookies)
{
if (thisCookie.Expires < DateTime.Now)
{
// expired
}
}
Tried Way 2
if (Request.Cookies["UserEmail"].Expires.ToString() < DateTime.Now)
{
}
You can not read the cookies expiration date. Please refer: (c# Asp.net getting Cookie expiration time set in javascript always returns 01.01.0001)
So you need to change your approach like following way.
Add Remember Time in Cookies.
Response.Cookies.Append("RememberTime", DateTime.Now(), option);
And read this RememberTime Cookies value and write your logic for check with as per you requirement day.
if (Convert.ToDateTime(Request.Cookies["RememberTime"]).AddDays(10) > DateTime.Now)
I have implemented the IdentityServer4 SSO in my application. SSO works fine as well as Logout for all the clients,However there is a new requirement where if the user is already logged in into an application and if he tries to login again (From different devise/browser) then he should be automatically logged out of the previous browser. I am not getting my head around this.How to implement this and if it is possible at all to track the user login sessions?
Update:-
We have tried following way of doing it, We have added the Session info into the Global Static variables using "Action" filter attribute.Here we stored the Login Session Info after user gets logged in.
private class LoginSession
{
internal string UserId { get; set; }
internal string SessionId { get; set; }
internal string AuthTime { get; set; }
internal DateTimeOffset AuthDateTime
{
get
{
if (!string.IsNullOrEmpty(AuthTime))
return DateTimeOffset.FromUnixTimeSeconds(long.Parse(AuthTime));
else
return DateTimeOffset.UtcNow;
}
}
}
private static List<LoginSession> LoginSessions = new List<LoginSession>();
In "Action Filter" methods we check if the user's session id is already present or not. If the session is present and it's SessionId is not matching with claims session id then we check the Login time of the Session. If the login time is less than the current login time then the user is logged out of the system else we update the login session with the latest session id and login time. Due to this workflow for the second login the Login Session will be updated as the Login Time is always Greater than the saved Login Session Info. And for the old Logged in session the user will be logged out of the system as the login time would always be less than the updated session info.
public class SessionValidationAttribute : ActionFilterAttribute
{
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string action = context.RouteData.Values["action"].ToString();
if (!string.IsNullOrEmpty(action) &&
context.Controller.GetType().GetMethod(action).GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Length == 0)
{
var claims = ((ClaimsIdentity)((Microsoft.AspNetCore.Mvc.ControllerBase)context.Controller).User.Identity).Claims;
var sessionId = claims.Where(x => x.Type == "sid").First().Value; // context.HttpContext.Request.Cookies.TryGetValue("idsrv.session", out var sessionId);
var userId = claims.Where(x => x.Type == "sub").First().Value;
var authTime = claims.Where(x => x.Type == "auth_time").First().Value;
var authDateTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(authTime));
if (LoginSessions.Where(x => x.UserId.Contains(userId)).Count() > 0) // if already logged in
{
var latestLogin = LoginSessions.Where(x => x.UserId == userId).OrderByDescending(x => x.AuthDateTime).First();
if (sessionId != latestLogin.SessionId)
{
if(authDateTime > latestLogin.AuthDateTime) // login using new browser(session)
{
latestLogin.SessionId = sessionId; // assign latest sessionId
latestLogin.AuthTime = authTime; // assign latest authTime
}
else if (authDateTime < latestLogin.AuthDateTime) // login using old browser(session)
{
LoginSessions.RemoveAll(x => x.UserId == userId && x.SessionId!=latestLogin.SessionId);
context.Result = ((Microsoft.AspNetCore.Mvc.ControllerBase)context.Controller)
.RedirectToAction(actionName: "Logout", controllerName: "Home",
routeValues: new { tenant = string.Empty, isRemoteError = false });
}
}
}
else
{
var newLogin = new LoginSession() { UserId = userId, SessionId = sessionId, AuthTime = authTime };
LoginSessions.Add(newLogin);
}
}
return base.OnActionExecutionAsync(context, next);
}
}
This works as we tested for few users but Will this solution work in actual scenario where there are thousands of users login into the system?Is it a good idea to use Static variable globally for storing session info? What will be potential drawbacks of using this.Please advice. We are open to new ideas also,if there is any new methods of implementing this functionality please let us know.
Thanks!!!
Disclaimer: I have no practical experience with IS4.
You probably have a good reason but I'm failing to understand why you are overwriting the latestLogin session's details when you are validating the current latest login?
If I'm not mistaken this line will loop through all sessions in your application, which you have multiple alike of in the lines that follow.
if (LoginSessions.Where(x => x.UserId.Contains(userId)).Count() > 0)
This is indeed something you wouldn't want to do in an application you expect to scale.
Unfortunately I'm not familiar with IS4 and I can not tell you if there is a possibility to solve this problem entirely by utilizing its APIs, instead I can give you practical advice.
You can use a separate centralized storage, the possibilities are endless but something along the lines of memcached is perfect. Then the algorithm is fairly simple:
Whenever a user tries to log in, retrieve the value stored under user's ID from storage.
If present, that would be the current session ID, then destroy it in IS4 and continue.
Create new login session and store the session ID in memcached under the user's ID.
This way there will never be more than 1 session for a given user and you've successfully reduced the complexity of the algorithm from O(n), or worse in your case, to O(1).
When i call my admin controller- Index Action method will get all the user details
when i want select particular user again i dont want to hit the DB.
both action method same controller and i'm using model popup for display details.
My Question
I dont want to use entity framework.
- when admin form load i will get all the user details this is Index Action Method
-based on user id i need to display particular user so again i dont want hit to the DB already i'm having all the user details. that details how to get another action method?
i can remember asp.net i used session to share the data globally. like that asp.net mvc is possible? please help me.
Thanks
It looks you're looking for a cache mechanism. For simple scenarios, I use a simple static variable, but I keep it in a separated class. Let's suppose you have a User class like this:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
You could create a class like this:
public static class UserCacheService
{
private static IEnumerable<User> _users;
private static readonly object lockObj = new object();
public static IEnumerable<User> GetUsers()
{
lock (lockObj)
{
if (_users == null)
{
using (var db = new MyNiceDbContext())
{
_users = db.Users.ToList();
}
}
return _users;
}
}
public static void InvalidateCache()
{
lock (lockObj)
{
_users = null;
}
}
}
Then you can get your shared users in any action, of any controller like this:
public class AdminController : Controller
{
public ActionResult Index()
{
// the first time, it'll need to get users from DB (e.g with Entity Framework)
var users = UserCacheService.GetUsers();
return View();
}
}
The first time, the _users in your UserCacheService will be null, and as expected, it'll need to load users from database. However, the next time it won't, no matter if you are using another controller:
public class AnotherController : Controller
{
public ActionResult Index(string userId)
{
// now, it won't load from DB anymore, because _users is already populated...
var users = UserCacheService.GetUsers();
var currentUser = users.Where(u => u.Id == userId).FirstOrDefault();
if (currentUser != null)
{
// do something with the user...
}
return View();
}
}
There are times when unfortunately your _users will become null again, for example when you restart your ApplicationPool in IIS, but UserCacheService is already prepared for fetching database once if that's the case.
Be careful about three things:
Whenever you keep data in memory (like _users), you are consuming
your server's memory, which might be limited. Don't start trying to
keep everything in memory, only data you know you'll need everytime.
Whenever you update something in your users, like a name, an address or something else, since the _users will not get from database everytime, you need to call the UserCacheService.InvalidateCache() method, in order to force the next call to load again from database, thus making sure you have _users up to date.
This only works for simplistic scenarios. If you have your application distributed in two or more servers, this won't work, as each server has it's own memory and they can't share it out of the box. That's when you would look forward for something like Redis. Though, I don't think it's your case here.
I'm working on a ASP.Net MVC 5 app and using ASP.Net identity 2, and need to authorize users based on roles and permissions. roles and permissions is not related to each other. for example, to access "action1" action method,( "admin" role ) or ( combination of "role1" and "permission1" ) must exist for him, but other users that is not in "admin" role or combination of ( "role1" and "permission1") is not true for theirs, don't allow to access that action method.
how i can do this scenario?
do claims based authorization useful in this manner?
or i must implement Permission entity and custom AuthorizeAttribute? if true how?
best regards
Check out the ResourceAuthorize attribute in the Thinktecture.IdentityModel.Owin.ResourceAuthorization.Mvc package.
This attribute authorizes a user based on an action (e.g. read) and a resource (e.g. contact details). You can then base whether or not they are allowed to perform that action on a resource based on a claim (e.g. their presence in a role).
See here for a good example.
Might not be exactly what you are looking for, but you can take inspiration and implement your own authorization attribute using similar logic.
This is custom made Authorize which checks permission from database.
For example you have 3 bools for permission Account,Clients,Configuration
and you want to restrict user based on them.
you can add even two permission on one action, for example you have a method which can be accessed by Account and Client permission than you can add following line
Modify this to use roles with permissions in this, this is the easiest and best way to handle it.
[PermissionBasedAuthorize("Client, Account")]
This method below is which check the bools from database.
public class PermissionBasedAuthorize : AuthorizeAttribute
{
private List<string> screen { get; set; }
public PermissionBasedAuthorize(string ScreenNames)
{
if (!string.IsNullOrEmpty(ScreenNames))
screen = ScreenNames.Split(',').ToList();
}
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
var UserId = HttpContext.Current.User.Identity.GetUserId();
ApplicationContext db = new ApplicationContext();
var Permissions = db.Permissions.Find(UserId);
if (screen == null || screen.Count() == 0)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
bool IsAllowed = false;
foreach (var item in screen)
foreach (var property in Permissions.GetType().GetProperties())
{
if (property.Name.ToLower().Equals(item.ToLower()))
{
bool Value = (bool)property.GetValue(Permissions, null);
if (Value)
{
IsAllowed = true;
}
break;
}
}
if (!IsAllowed)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
}
I implemented a Permission-based extension for Microsoft Identity 2 membership system. But in this extension, permissions and roles are related together. there is a many-to-many relation between them. Also you can have a complex authentication with combination of roles and permissions. I suppose it can help you to do permission based authentication.
You can do permission authentication in two ways:
First approach:
// GET: /Manage/Index
[AuthorizePermission(Name = "Show_Management", Description = "Show the Management Page.")]
public async Task<ActionResult> Index(ManageMessageId? message)
{
//...
}
Second approach:
// GET: /Manage/Users
public async Task<ActionResult> Users()
{
if (await HttpContext.AuthorizePermission(name: "AllUsers_Management", description: "Edit all of the users information."))
{
return View(db.GetAllUsers());
}
else if (await HttpContext.AuthorizePermission(name: "UnConfirmedUsers_Management", description: "Edit unconfirmed users information."))
{
return View(db.GetUnConfirmedUsers());
}
else
{
return View(new List<User>());
}
}
Also it's an open source and free extension and you can access to the repository here.
I have an authorization requirement to have my security roles based on actions methods, which can not be achieved using the default asp.net mvc authorization. so i have created the following action filter, to implement my custom authorization requirments:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
Repository repository = new Repository();
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// var user = User.Identity.Name; // or get from DB
string ADusername = filterContext.HttpContext.User.Identity.Name.Substring(filterContext.HttpContext.User.Identity.Name.IndexOf("\\") + 1);
if (!repository.can(ADusername,Model,Action)) // implement this method based on your tables and logic
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
which is calling the following Repository method:-
public bool can(string user, string Model, string Action)
{
bool result;
bool result2;
int size = tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var securityrole = tms.SecurityroleTypePermisions.Where(a => a.PermisionLevel.PermisionSize >= size && a.TechnologyType.Name == Model).Select(a => a.SecurityRole).Include(w=>w.Groups).Include(w2=>w2.SecurityRoleUsers).ToList();//.Any(a=> a.SecurityRoleUsers.Where(a2=>a2.UserName.ToLower() == user.ToLower()));
foreach (var item in securityrole)
{
result = item.SecurityRoleUsers.Any(a => a.UserName.ToLower() == user.ToLower());
var no = item.Groups.Select(a=>a.TMSUserGroups.Where(a2=>a2.UserName.ToLower() == user.ToLower()));
result2 = no.Count() == 1;
if (result || result2)
{
return true;
}}
return false;
i am calling the action filter inside my controller class as follow:-
[CheckUserPermissions(Action = "Read", Model = "Server")]
but i have the following concerns:-
inside my repository i will be retrieving all the users and groups (when calling the .Tolist()), and then check if the current login user is inside these values. which will not be very extensible when dealing with huge number of users?
each time the user call an action method the same security code will run (of course ideally the user permission might chnage during the user session),,, which might cause performance problems ?
So can anyone adice how i can improve my current implementation, taking the two concerns in mind ?
Thanks
I would change your approach and use claims based authentication.
This way you have a lot more granular control over authorization (it can be driven by resource and actions).
You can use a ClaimsAuthorizationManager to check access at every level in a central place.
This article expains how to implement this and also use secure session tickets to save accessing the database everytime.
http://dotnetcodr.com/2013/02/25/claims-based-authentication-in-mvc4-with-net4-5-c-part-1-claims-transformation/