I have a project running MVC4 and using Simple Membership for authentication. I only want to allow a user to login on one browser. To make this transparent to the user, I need a way to have any other authenticated browser log out whenever a user logs in. This means, if two users are trying to use the same login, they would just continuously kick each other off making that very unproductive.
Right now, I have it set up to only allow a user to login once but if that user were to close the browser and move to another computer, they would be locked out for 30 minutes I can see this creating a number of unnecessary support calls.
I would assume I need to track some sort of identifier in a database and check to make sure it matches with each request otherwise they are logged out. Maybe, adding some sort of cookie.
If anyone has an elegant solution to this, I would appreciate it!
This is what I am currently using to lock users into only one login:
Login:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
string sKey = model.UserName;
string sUser = Convert.ToString(System.Web.HttpContext.Current.Cache[sKey]);
if (sUser == null || sUser == String.Empty)
{
TimeSpan SessTimeOut = new TimeSpan(0, 0, System.Web.HttpContext.Current.Session.Timeout, 0, 0);
System.Web.HttpContext.Current.Cache.Insert(sKey, sKey, null, DateTime.MaxValue, SessTimeOut, System.Web.Caching.CacheItemPriority.NotRemovable, null);
Session["user"] = model.UserName;
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
else
{
ModelState.AddModelError("", "You are already logged in.");
}
return View(model);
}
Global.asax
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
if (HttpContext.Current.Session != null)
{
if (Session["user"] != (null)) // e.g. this is after an initial logon
{
string sKey = (string)Session["user"];
// replace the last hit with current time
// Accessing the Cache Item extends the Sliding Expiration automatically
string sUser = (string)HttpContext.Current.Cache[sKey];
}
}
}
Logout:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
UserProfile user = db.UserProfiles.SingleOrDefault(s => s.UserName == User.Identity.Name);
string sKey = user.UserName;
System.Web.HttpContext.Current.Cache.Remove(sKey);
WebSecurity.Logout();
return RedirectToAction("Start", "Home");
}
I had used the term session and have removed it. I'm not trying to delete the user's session but make their authorization invalid using web security.
There's nothing built-in for this. You'd have to develop some methodology on your own. You'd basically need two pieces:
Some way of tracking a logged in user across requests. This could be as simple as a table with a username column which you could use to determine if that particular username has been logged in. You'd need to keep this in sync with your logins/logouts of course, and you would also need to store the session id for the user. You'll need that for the next piece:
Some mechanism of removing the session from whatever store it exists in. This would be easiest if you're using SQL sessions, as you could simply delete the row from the table session table with the matching id. There's no way to do this directly with ASP.NET, so you'd have to directly query the database, used a stored procedure, etc.
So, the general idea would be that when a user logs in, you record their username and session id in a table or some other persisted store. When someone attempts to log in, you'd check this store for the username that is being attempted, and if it exists, go delete the session that corresponds to this. The next time the user with that session tries to access a page, their session cookie will no longer match a valid session and they'll be treated as if they've been logged out.
Related
in ASP.NET web API in the log in algorithm i have a action filter that generates a token for each user and the front end sends that token back to authenticate the user by using that token in web server i can get current user information till now every thing is working fine however i have new requirements that every user has relation many to many with account which means the same user can exists in more than one account with different roles for example in account one he is an admin in account two he is normal user so i have to regenerate the token which requires the user to re log in again i do not want him to be redirected to the log in page again. what i think of is to store user name and password in html 5 local storage but i think that is a bad practices any ideas.
Her is how i generate token.
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.Request.Headers
.Any(header => header.Key == "AuthorizationHeader"))
{
if (this.IsAnonymousAllowed(actionContext) == false)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "Un Autherized");
}
}
else
{
string token = actionContext.Request.Headers
.Where(header => header.Key == "AuthorizationHeader")
.First().Value.First();
if (this.IsAnonymousAllowed(actionContext) == true)
{
return;
}
string passPhrase = System.Configuration.ConfigurationSettings.AppSettings["PassPhrase"];
string ticket_string = Crypto.Decrypt(token, passPhrase);
TicketData ticket = JsonConvert.DeserializeObject<TicketData>(ticket_string);
if (ticket == null || ticket.Expiration < DateTime.Now)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "UnAuthorized");
}
else
{
OurIdentity identity = (OurIdentity)ticket.TokenData.OurIdentity;
System.Threading.Thread.CurrentPrincipal = new OurPrincipal
{
OurIdentity = identity,
};
}
}
}
You are right saving username and password in the local storage is bad. It is bad to save it anywhere on the client.
Usually a token is generated and put in a cookie. That token corresponds with a record on the server, in either a session log or a database.
I strongly suggest to use existing methods for this, like OAUTH Bearer tokens in this tutorial.
As far as I understand, if you are storing a hash (perhaps with a salt for extra protection) it is not nessecescarily bad to store the credentials. These would have to be stored somewhere at the end of the day anyway.
I was trying to let the users change their password in settings. In the ajax page, I was using
WebSecurity.Logout();
So I thought logging out is because of this code. But then I noticed that the user logs out, even if this line isn't present after Password change success. So I tried to Google it. And on many places I found that this code removes the Cache and Cookies, so the user is logged out.
My Question: Is there any way to prevent User logout? Or can I save the Cookie or cache so that the user is still logged in after password change success.
You should use WebSecurity.ChangePassword, this will renew the current cookie with all new crendentials and then send it back into the response.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Manage(ChangePassward model)
{
bool changePasswordSucceeded = false;
try
{
changePasswordSucceeded = WebSecurity.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword);
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("Some Page here", new { Message = "Success" });
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
}
This is an old question, so apologies for bringing it back to life, but for anyone else who faces this issue:
You already have their username and password in this method. Log them back in.
I have an application where in im preventing multiple login by same user.If user tries to login from another webpage/machine(when already logged in another) then a confirm box appears asking whether he needs to logout the previous session or not,if he clicks "yes" then the previous sessions shud be logged off.Im not able to logout the previous session however the code is preventing multiple login but how can i logoff the previous session n redirect user to main page with present session
Here is the code
In loginButton_Click(after authentication)
string sKey = loginControl.UserName + loginControl.Password;
string sUser = Convert.ToString(Cache[sKey]);
if (sUser == null || sUser == String.Empty || sUser == "")
{
TimeSpan SessTimeOut = new TimeSpan(0, 0, HttpContext.Current.Session.Timeout, 0, 0);
HttpContext.Current.Cache.Insert(sKey, sKey, null, DateTime.MaxValue, SessTimeOut, System.Web.Caching.CacheItemPriority.NotRemovable, null);
Session["user"] = loginControl.UserName + loginControl.Password;
Response.Redirect("MainPage.aspx");
}
else
{
Response.Write("<script>if(window.confirm('You have already Logged In.Do you want to sign off the previous Session?')){} else{window.location.href='login.aspx'}</script>");
//if part
return;
}
In Global.asax page
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
if (System.Web.HttpContext.Current.Session != null)
{
if (Session["user"] != null)
{
string sKey = (string)Session["user"];
string sUser = (string)HttpContext.Current.Cache[sKey];
}
else
{
foreach (DictionaryEntry de in HttpContext.Current.Cache)
{
HttpContext.Current.Cache.Remove((string)de.Key);
}
}
}
}
The current approach you are using is problematic. You keep the same "key" ( loginControl.UserName + loginControl.Password ) to identify your user's session, Instead of a unique key (Guid.NewGuid() for instance) per session.
If you have a unique key, you will be able to distinguish one session from another (e.g. if user performs a login twice, you will be able to know which is which)
Explaination:
When a user logs in, create a new Guid and keep it in another Session key (Session["SessionGuid"] = Guid.NewGuid() for instance).
In addition either keep a dictionary (or create a separate entry for each user in the global cache) in which per userId you save the unique Guid.
Since in one session you will have the old Guid and in the new session you will have the new Guid, you can check in the Application_PreRequestHandlerExecute which Guid the current session has, and if it doesn't match - disconnect the user.
LoginPage.aspx:-
protected void Button1_Click(object sender, EventArgs e)
{
Context.Items["Username"] = txtUserId.Text;
Context.Items["Password"] = txtPassword.Text;
//
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, Context.Items["Username"].ToString(), DateTime.Now, DateTime.Now.AddMinutes(10), true, "users", FormsAuthentication.FormsCookiePath);
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
Response.Cookies.Add(cookie);
Response.Redirect("Default.aspx");
}
Global.asax file:-
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id =
(FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// Get the stored user-data, in this case, our roles
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
Response.Write(HttpContext.Current.User.Identity.Name);
Response.Redirect("Default.aspx");
}
}
}
}
I get the following error after signing in
This webpage has a redirect loop.
The webpage at http://localhost:1067/Default.aspx has resulted in too many redirects. Clearing your cookies for this site or allowing third-party cookies may fix the problem. If not, it is possibly a server configuration issue and not a problem with your computer.
This is the rough idea of what your module should look like. Your module will run on every request. You don't invoke it or pass anything to it, it just automatically fires whenever a request is made that ASP.Net is set to process.
Your module will do two things, 1) authenticate a user in the login page, 2) authenticate a user on subsequent pages. The first step is to subscribe to the BeginRequest method which will be given the current HttpApplication as the first parameter. From there you need to determine if the user is on your login page or not. If they're not on your login page, check your session or cookie or querystring token, or whatever you're using to make sure that they're still valid. If they're invalid, bounce them back to the login page.
If they're on your login page and have made a POST, look at the raw form fields and validate them. TextBoxes, checkboxes, etc don't exist here, only raw form fields. If they're valid, set your authentication token however you want (session, cookies, etc). If they're invalid, either redirect to the login page or inject a "try again" message or something.
Also, if you double-post a message please reference it so that we can follow the chain of what was already said.
class MyModule : IHttpModule
{
void IHttpModule.Init(HttpApplication context)
{
//Subscribe to the BeginRequest event
context.BeginRequest += new EventHandler(this.Application_BeginRequest);
}
private void Application_BeginRequest(Object source, EventArgs e)
{
//Initialize our variables, null checks should be put here, too
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
System.Web.SessionState.HttpSessionState s = context.Session;
//Normally our module needs to validate every request to make sure our request is still authenticated.
//The exception to that rule is on our logon page where they obviously don't have credentials yet.
if(!context.Request.FilePath.ToLowerInvariant().StartsWith("/login.aspx")){
//If we're here then we're not on the logon page, validate our current session according to whatever logic we want
if (s != null && s["isvalid"] == "true"){
return;
}else{
context.Response.Redirect("/login.aspx");
}
}else{
//If we're here then we're on the login page itself. If there's a post, assume that they've hit the login button
if (context.Request.HttpMethod == "POST")
{
//Whatever your form variables are called
string username = context.Request.Form["username"];
string password = context.Request.Form["password"];
//Your own validation logic would go here
if (MyCustomLogin.IsUserValid(username, password))
{
s["isvalid"] = "true";
context.Response.Redirect("/Home.aspx");
}else{
s["isvalid"] = "false";
context.Response.Redirect("/login.aspx?error=invalid_login");
}
}else{
//If we're here then the request is probably a GET or HEAD which would be from a person
//initially browsing to our page so just do nothing and pass it through normally
}
}
}
}
There is no direct way to have access to this information in the module (for authenticated user, you can access the username via the context, but not the password). The module checks if a request is carrying required authentication information and serve or deny the request based on that. Unless you deliberately from the login page collect this information and store somewhere where you can access it in the module, e.g session. But ideally, storing password is not widely recommended, collect it use it for authentication and destroy.
You might ideally throw more light on the reason why you want to have access to this information in the module and guys can then suggest methods to accomplish it.
Edited, after Chandan comment:
#Chandan, your comment here suggest to me what you want to do is use httpmodule for your authentication as against using standard form authentication. If I am on track, then you can check this project on codeproject at http://www.codeproject.com/KB/web-security/AspNetCustomAuth.aspx. Goodluck
I'm developing an application in .NET mvc2. I'm using aspnetMembershipProvider for User registration and related activities. I need some custom information about user that I stored in a separate table (sysUser for example) and linked it to aspnetUser table through foreign key.
After login I need to fetch user's credentials from sysUser table and push it to the session. For this Account controller's Logon method seemed best to me and I pasted following code in my Logon ActionResult
if (!ValidateLogOn(userName, password))
{
return View();
}
FormsAuth.SignIn(userName, rememberMe);
ApplicationRepository _ApplicationRepository = new ApplicationRepository();
MembershipUser aspUser = Membership.GetUser(userName);
SessionUser CurrentUser = _ApplicationRepository.GetUserCredentials(aspUser.ProviderUserKey.ToString());
//Session["CurrentUser"] = CurrentUser;
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
The code is working perfectly for me and put my desired information in the session but the thing is that if a user selects Remember me and on his next visit he won't have to Log in and I would not find my desired information in the Session. Where should I put my code that stores the user information in the session?
FormsAuthentication.SetAuthCookie(userName, saveLogin);
MSDN Documentation for SetAuthCookie Method