Adding user info to session data right when user logins - c#

I currently have the following login script:
[HttpPost]
public ActionResult LogOn(LogOnModel model)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
Guid MyUserId = (Guid)Membership.GetUser().ProviderUserKey;
System.Web.HttpContext.Current.Session[MyUserId.ToString()] = profileRepository.GetFullProfile(MyUserId);
return Json(true);
}
else
{
return Json("The user name or password provided is incorrect.");
}
}else
return Json("The user name or password provided is incorrect.");
}
I get an error on the Membership.GetUser() saying the object reference error. I researched this and i found it was because only my next http request will be authenticated, so what is the best way to save a user's data into session when the user logins? will it be assured to be in the session until the user's session expires and kicks him out or will there ever be an instance where the user is logged in without the session data set? If so ill need something to handle that as well.

This has happened to me in LoginButton_Clicked in an asp.net application (Not MVC). Even thought this event (LoginButton_Clicked) fires after a successful login Membership.GetUser(), Roles.GetRolesForUser(), Profile Are yet filled in. Therefore username needs to be passed into each method to get the details.
I think same thing happening in MVC as well.
You can get the user details by passing model.UserName as follows.
MembershipUser mu = Membership.GetUser(model.UserName);

Related

Authorize Attribute always returning false ASP.net MVC Identity

When I decorate a method with an Authorize roles attribute it returns false everytime. I'm trying to limit access to an admin page for users in the "Admin" role only.
I have verified that the user im currently logged in as is in fact in the "Admin" role.
I have tried to use a custom authorization attribute. Same result. I can add the code if needed.
I have found that the authorization attribute works for Users but not for Roles.
I believe this problem is somehow tied into the fact that the following does not work in my application:
User.IsInRole("Admin").
However, this statement does work:
userManager.IsInRole(user.Id, "Admin")
Here is my code:
public class AdminController : Controller
{
//[AuthLog(Roles = "Admin")] //Custom authorization attribute
[Authorize(Roles = "Admin")]
public ActionResult Users()
{
return View();
}
}
Maybe this can help with debugging:
Microsoft.AspNet.Identity.Core: V.2.1.0
Microsoft.AspNet.Identity.EntityFramework: V.2.1.0
I am open to suggestions on anything else I can post from my project in order to debug easier. I have scoured the stack for 2 weeks now.
Update 1: How user is logged in
// POST: /account/login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(AccountLoginModel viewModel)
{
// Ensure we have a valid viewModel to work with
if (!ModelState.IsValid)
return View(viewModel);
// Verify if a user exists with the provided identity information
var user = await _manager.FindByEmailAsync(viewModel.Email);
// If a user was found
if (user != null)
{
// Then create an identity for it and sign it in
await SignInAsync(user, viewModel.RememberMe);
// If the user came from a specific page, redirect back to it
return RedirectToLocal(viewModel.ReturnUrl);
}
// No existing user was found that matched the given criteria
ModelState.AddModelError("", "Invalid username or password.");
// If we got this far, something failed, redisplay form
return View(viewModel);
}
private async Task SignInAsync(IdentityUser user, bool isPersistent)
{
// Clear any lingering authencation data
FormsAuthentication.SignOut();
// Create a claims based identity for the current user
var identity = await _manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Write the authentication cookie
FormsAuthentication.SetAuthCookie(identity.Name, isPersistent);
}
The
FormsAuthentication.SetAuthCookie(identity.Name, isPersistent);
unfortunately doesn't store any roles with the identity. Thus, when the identity is recreated from the cookie, you have no roles. To verify try
this.User.IsInRole("Admin")
and you'll get false, even though the userManager tells you otherwise.
There are multiple workarounds.
You could for example switch to any other identity persistor, like the SessionAuthenticationModule which could store your username and roles in the cookie. You could follow my tutorial on that.
Another approach would be to have an explicit role manager and use its feature that automatically causes your roles to be stored in another cookie, separate from the forms authentication cookie. This involves configuring the role provider and writing your own role provider that would be an adapter over the user manager.
Finally, you could forget forms authentication and use Identity's native way of issuing cookies, which would involve calling SignInAsync on the authentication manager.

How to properly return a view from controller?

I have an application with login. I have a controller and a view. From controller, I try to decide if the user is admin or not, and then to show a specific message in view.
The problem is that when I run the application from Startup and I press the login button, the application redirects me to the home page. When I run the application from the view, it works (it's stupid, but it works).
The link when I run the app prof Startup: localhost:2627/Account/Login
The link when I run the app from view:localhost:2627/Account/LoginReturnUrl=%2FUsers%2FIndex
This is my controller:
public class UsersController : Controller
{
// GET: Users
[Authorize]
public ActionResult Index()
{
if (User.Identity.IsAuthenticated)
{
var user = User.Identity;
ViewBag.Name = user.Name;
ViewBag.displayMenu = "No";
if (isAdminUser())
{
ViewBag.displayMenu = "Yes";
}
return View();
}
else
{
ViewBag.Name = "Not Logged IN";
}
//return View();
return View();
}
This is my View:
#{
ViewBag.Title = "Index";
}
#if (ViewBag.displayMenu == "Yes")
{
<h1>Welcome Admin. Now you can create user Role.</h1>
<h3>
<li>#Html.ActionLink("Manage Role", "Index", "Role")</li>
</h3>
}
else
{
<h2> Welcome <strong>#ViewBag.Name</strong> :) .We will add user module soon </h2>
}
I was trying to follow this tutorial(the login part).
I can't figure out why it doesn't open my view. The user is authenticated, but I only see home view.
What am I doing wrong? Do you have any suggestions?
Thank you!
The reason it's working when you start the app from the User's view is that you are trying to access a protected resource ( i.e., a resource that requires authentication). When the user is not authenticated, the default behavior is to challenge the user ( i.e., authenticate the user) and the user is redirected to a login page. When the user is redirected, the returnUrl query parameter is being set so that the user can be redirected back to this resource after authentication. In this case, the returnUrl is the User's index view.
The link when I run the app from
view:localhost:2627/Account/LoginReturnUrl=%2FUsers%2FIndex
It sounds like you want the Users view to be your homepage or at least the page that the user is redirected to after login. In your AccountController, you will want to force this action if this is the desired behavior.
for example:
switch (result)
{
case SignInStatus.Success:
return return RedirectToAction("Index", "Users");
...
}
You will also want to beware if a returnUrl is present too. Based on your requirements, you might want to give the returnUrl the highest precedence.
If ( SignInStatus.Success)
{
return !string.IsNullOrEmpty(returnUrl) ?
RedirectToLocal(returnUrl):
RedirectToAction("Index", "Users");
}
Basically, it really comes down to where you want to send (aka route) the user after authentication. Maybe the user has a preference for their home page or that if the user is an admin that you always route them to the index view in the UsersController.
In the demo project, you should review the Start.Auth.cs file and become familiar with the authentication options.
For example, the LoginPath is being set in this file. This is the path that user will be redirected to for authentication challenges.
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{...}
});
Reference RedirectToAction:
http://msdn.microsoft.com/en-us/library/dd470594.aspx

MVC4 Pattern for receiving password reset token

Trying to get password reset functionality in place. This will be for a user who has not and cannot log in to the system. I think I'm close but it doesn't feel right:
I have a ResetPassword method/view...it simply asks for the user's email address, does not confirm an account to the user but if one exists, sends email with link+token. That all works fine.
The next part is where my questions are....I receive the password token with this method (via the user's email link being clicked):
[HttpGet]
public ActionResult ReceiveResetToken(string token)
{
try
{
if (!String.IsNullOrEmpty(token))
{
var username = (from u in db.Users
where u.Userid == WebSecurity.GetUserIdFromPasswordResetToken(token)
select u.Email).ToString();
if (!String.IsNullOrEmpty(username))
{
WebSecurity.ConfirmAccount(token);
}
}
RedirectToAction("Index", "Home");
}
catch (Exception)
{
throw;
}
}
I'm missing something obvious here. The method isn't complete because I keep rethinking it...get the username, confirm the account, somehow log them in without knowing what their password is, redirect to change password page? Doesn't feel right.
So then I thought maybe pass along the hidden username with a ViewBag to the change dialogue...doesn't feel right either. I'd like a more elegant approach, receive the token, ask the username for the new password, update db and login. What is the pattern for receiving a password reset token?
EDIT -------
So as I am continuing to search for answers, I came across this little gem. Apparently there is a WebSecurity.ResetPassword method that accepts the token and a new password. This feels like the right path, no need to worry about logins, just change it and redirect to login...I'll finish up the code and post a solution as this seems to be a popular and often unanswered question on SO.
If anyone could confirm that I'm on the right path or post any thoughts on adding elegance to the pattern that'd be cool
It's a right path !
for me,
User give his email and i send him a token who is generate an GUID and i have passwordResetTokenDate who take a date when user asked the reset. (token is valid 48hours)
in email, there is a link with token and i give him a token, if when he click and something is wrong, he can copy pasted the token in textbox or re-clicking on the link
when he click on the link, i check the token and the date and passwordResetTokenDate if all is right, there is two textbox and user enter 2 times his new password.
when he save his password, i logged him.
WebSecurity.ResetPassword do the job !
here an example : (i have a custom websecurity with custom provider)
[AllowAnonymous]
public ActionResult ForgotMyPassword(string confirmation, string username)
{
username = HttpUtility.UrlDecode(username);
ViewBag.Succeed = false;
SetPasswordViewModel Fmp = new SetPasswordViewModel(username,confirmation);
return View(Fmp);
}
//
// POST: /Account/ForgotMyPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ForgotMyPassword(SetPasswordViewModel model)
{
ViewBag.Succeed = false;
if (ModelState.isValid)
{
ViewBag.Succeed = WebSecurity.ResetPassword(model.UserName, model.PasswordResetToken, model.Password.NewPassword);
}
if (!ViewBag.Succeed)
{
ModelState.AddModelError("","something"); //something
}
return View(model);
}
This is how it should work (as implemented in ASP Security Kit)
User clicks on forgot password link (which opens /account/forgot for example)
On this page, you ask user for his userName (which could be his email).
You check whether that user exists. If yes, you generate a reset token, saving it in the database for that username and send out an email to that user with a link (http://yourdomain.com/account/confirm/[tokenHere])
You display user a message something like "if you have an account with this username, you will receive an email with instructions to reset your password shortly." but you don't login user on this page because you just asked him for his username!
User receives the email, clicks on the link and the reset password page opens (/account/confirm/[tokenHere])
On this page, user needs to fill password and confirm password fields. Once done you will redirect user to login page (you may argue that you can directly sign in user once he resets his password; but redirecting to login seems to be the standard practice followed on most sites.)
Answering my own question in case it helps anyone.
Provide a form asking for email address to send password reset link
Use WebSecurity.GeneratePasswordResetToken(email, 1440) to generate token
Send email to user with link pointing to a token receiving method
Write an HttpGet method to receive the token and display newPassword form
The form posts the token and the new password model to a method that uses WebSecurity.ResetPassword(token, newPassword)
redirect to login
Haven't written it all out yet but I think this is the way to do it properly

Set proxy user in a GenericPrincipal, while keeping the old identity, using MVC

I have a site where I allow some users to proxy in as an other user. When they do, they should see the entire site as if they where the user they proxy in as. I do this by changing the current user object
internal static void SetProxyUser(int userID)
{
HttpContext.Current.User = GetGenericPrincipal(userID);
}
This code works fine for me.
On the site, to proxy in, the user selects a value in a dropdown that I render in my _layout file as such, so that it appears on all pages.
#Html.Action("SetProxyUsers", "Home")
The SetProxyUsers view looks like this:
#using (#Html.BeginForm("SetProxyUsers", "Home")) {
#Html.DropDownList("ddlProxyUser", (SelectList)ViewBag.ProxyUsers_SelectList, new { onchange = "this.form.submit();" })
}
The controller actions for this looks like this
[HttpGet]
public ActionResult SetProxyUsers()
{
ViewBag.ProxyUsers_SelectList = GetAvailableProxyUsers(originalUserID);
return PartialView();
}
[HttpPost]
public ActionResult SetProxyUsers(FormCollection formCollection)
{
int id = int.Parse(formCollection["ddlProxyUser"]);
RolesHelper.SetProxyUser(id);
ViewBag.ProxyUsers_SelectList = GetAvailableProxyUsers(originalUserID);
return Redirect(Request.UrlReferrer.ToString());
}
All this works (except for the originalUserID variable, which I put in here to symbolize what I want done next.
My problem is that the values in the dropdown list are based on the logged in user. So, when I change user using the proxy, I also change the values in the proxy dropdown list (to either disappear if the "new" user isn't allowed to proxy, or to show the "new" user's list of available proxy users).
I need to have this selectlist stay unchanged. How do I go about storing the id of the original user? I could store it in a session variable, but I don't want to mess with potential time out issues, so that's a last resort.
Please help, and let me know if there is anything unclear with the question.
Update
I didn't realize that the HttpContext is set for each post. I haven't really worked with this kind of stuff before and for some reason assumed I was setting the values for the entire session (stupid, I know). However, I'm using windows authentication. How can I change the user on a more permanent basis (as long as the browser is open)? I assume I can't use FormAuthentication cookies since I'm using windows as my authentication mode, right?
Instead of faking the authentication, why not make it real? On a site that I work on we let admins impersonate other users by setting the authentication cookie for the user to be impersonated. Then the original user id is stored in session so if they ever log out from the impersonated users account, they are actually automatically logged back in to their original account.
Edit:
Here's a code sample of how I do impersonation:
[Authorize] //I use a custom authorize attribute; just make sure this is secured to only certain users.
public ActionResult Impersonate(string email) {
var user = YourMembershipProvider.GetUser(email);
if (user != null) {
//Store the currently logged in username in session so they can be logged back in if they log out from impersonating the user.
UserService.SetImpersonateCache(WebsiteUser.Email, user.Email);
FormsAuthentication.SetAuthCookie(user.Email, false);
}
return new RedirectResult("~/");
}
Simple as that! It's been working great. The only tricky piece is storing the session data (which certainly isn't required, it was just a nice feature to offer to my users so they wouldn't have to log back in as themselves all the time). The session key that I am using is:
string.Format("Impersonation.{0}", username)
Where username is the name of the user being impersonated (the value for that session key is the username of the original/admin user). This is important because then when the log out occurs I can say, "Hey, are there any impersonation keys for you? Because if so, I am going to log you in as that user stored in session. If not, I'll just log you out".
Here's an example of the LogOff method:
[Authorize]
public ActionResult LogOff() {
//Get from session the name of the original user that was logged in and started impersonating the current user.
var originalLoggedInUser = UserService.GetImpersonateCache(WebsiteUser.Email);
if (string.IsNullOrEmpty(originalLoggedInUser)) {
FormsAuthentication.SignOut();
} else {
FormsAuthentication.SetAuthCookie(originalLoggedInUser, false);
}
return RedirectToAction("Index", "Home");
}
I used the mvc example in the comments on this article http://www.codeproject.com/Articles/43724/ASP-NET-Forms-authentication-user-impersonation to
It uses FormsAuthentication.SetAuthCookie() to just change the current authorized cookie and also store the impersonated user identity in a cookie. This way it can easily re-authenticate you back to your original user.
I got it working very quickly. Use it to allow admin to login as anyone else.

Deleted User logged in even after deleting on the other broswer

var query = from p in AdminModelContext.Users
where p.UserName == model.UserName && p.Password == encryptPassword
&& p.IsDeleted == false
select p;
IList<Users> userList = query.ToList();
if (userList.Count() > 0)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (CheckUrl(returnUrl))
{
return Redirect(returnUrl);
}
SetRoleForUser(userList[0].RolesId);
LoggerService.Info(string.Format("Login Successful for the user : {0}",
model.UserName));
return RedirectToAction("Index", "Home");
}
I am using the following code to Login through my website. The problem that I am facing is when I logged in with a user on a specific browser and at the same time login with a different user on a different browser, and then I delete the user(logged in on the other browser). Still I am able to navigate through the pages with the deleted user logged in.
I am not finding a fair solution to put authentication logic on every page. My website is in MVC model and using Form based authentication.
Please suggest how can I put the logged in user session validation and achieve this.
None of the answers so far actually acknowledge the question.
Lets look at the control flow:
User A enters log in page, supplies valid credentials
User A is issued Ticket A.
User B enters site, supplies valid credentials.
User B is issued Ticket B.
User B then revokes User A's access CREDENTIALS.
At this point nothing happens to Ticket A. Because the Ticket is independent on the credentials. When Ticket A expires they will then be required to present their credentials and it will fail login.
So what you've noticed that kicking a live user out of your site is actually pretty hard. As you've realized the ONLY solution is to have authentication logic on EVERY request. That unfortunately is really heavy.
In the login system I built I handled this aspect by creating 2 tickets, 1 ticket that's stored in the Forms Auth ticket as normal that has a big duration, and a ticket that's stored in HttpRuntime.Cache, I set the cache expiration to 15 minutes on this ticket.
On every page request I check to see whether a user has a ticket in the cache (based off their Forms Auth ticket information), at this point if they have no ticket I do a user data refresh and poll the user database. If the user has become suspended or deleted they will be logged out at then.
Using this method I know that my site can disable a user and within 15 minutes that user will be barred from the site. If I want them immediately barred I can just cycle the app config to clear the cache and FORCE it to happen.
Normally if you have the [Authorize] attribute defined for an Controller or an Action the Authentication is checked on every post back.
The build in MembershipProvider handles all that for you. But it seems you are using your own user Database. Then you have to implement your own MembershipProvider, IPrincipal and MembershipUser and have this added to your Web.config replacing the default one.
More you'll find here how to implement your own MembershipProvider: http://msdn.microsoft.com/en-us/library/f1kyba5e.aspx
My suggestion is to create an empty MVC project and have a look at the default authentication mechanism. And if your building a new Application with a new Database, try to use the default authentication.
Your validateUser function in your own MembershipProvider could look like this.
public override bool ValidateUser(string username, string password)
{
bool isValid = false;
bool isApproved = false;
string pwd = "";
using (AdminModelContext db = new AdminModelContext())
{
var user = db.Users.FirstOrDefault(u => u.UserName == username);
if (user != null)
{
pwd = user.Password;
isApproved = user.IsApproved;
if (CheckPassword(password, pwd))
{
if (isApproved)
{
isValid = true;
user.LastLoginDate = DateTime.Now;
user.LastActivityDate = DateTime.Now;
try
{
db.SubmitChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
else
{
UpdateFailureCount(username, "password");
}
}
}
return isValid;
}
I see the problem now. I dont know how this works in MVC, but by using Authenticate_Request, you can validate if the user is still valid. The business logic may also double check if the user is still valid. But as far as I know, there is no way of iterating all the open sessions and killing the requierd ones, even in that case, the authorization cookie should be double checked on Session_Start event.
Another options is adding a global invalidated_users list on the application, and then checking that user against the invalid list. This list should only contain users that are invalidated after the application has restarted.
Link for Reading All Users Session:
http://weblogs.asp.net/imranbaloch/archive/2010/04/05/reading-all-users-session.aspx

Categories