MVC only alow admin login - asp.net-mvc

I am working on a MVC5 EF6 database in Visual Studio 2013. The database is using Individual User Accounts for authentication with user roles.
In IIS it is possible to get the entire site down (e.g. for maintenance) by putting the app_offline.htm file. After I have updated the site and maybe migrated the database, as administrator I would like to perform some tests on the production machine before allowing all other users to login.
Is there a simple way to lockout all users except administrator from logging in until the administrator allows them to login ?
It would be nice if a similar construction can be used as the app_offline.htm file.
Below the code of the login method.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
I can imagine it would be possible to add some code in case Success to logout the user (if not memeber of administrators) and redirect to a special "logins temporary disabled" page. However, there might be better (or builtin) alternatives.
Edit:
I tried the filtes and they do not work as I expected. However, I will keep filters in mind to test with if I have some more time to do so.
For now I use the following code in the login method
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationUser au = UserManager.FindByName(model.UserName);
// If user is found and not member of administrators
if (au != null && !UserManager.IsInRole(au.Id, "Administrator"))
{
// Check if the adminmode file exisits in the root. If so, redirect to it
String adminModeFilePath = System.IO.Path.Combine(HttpRuntime.AppDomainAppPath, "app_adminmode.htm");
if (System.IO.File.Exists(adminModeFilePath))
{
return new FilePathResult(adminModeFilePath, MediaTypeNames.Text.Html);
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
For "Administrator" and "app_adminmode.htm" I actually use constants which are declared in a central place, but for SO completeness I use the string here. If the user trying to login exists and is not in role Administrator, I will check the file. If the file exists the user is not logged in, but redirected

Maybe using the Authorize Attribute in FilterConfig.cs:
if(!ConfigurationManager.AppSettings["SystemLive"])
{
filters.Add(new AuthorizeAttribute{Roles = "Adminsitrator"});
}
else
{
filters.Add(new AuthorizeAttribute());
}
Edit: Thanks #gavin-coates be sure to Allow Anonymous on the login page or any page that should be accessible by un-uathenticated users.
[AllowAnonymous]
public ActionResult Login() {
// ...
}

Adding a check to see if the user is an admin sounds ok to me.
The only thing I would change though, is to add some kind of boolean flag to enable/disable the restrictions, and place this in your web.config file, so you don't need to modify your code and re-upload it to enable/disable access.
Add the following into your web.config, <appSettings> section:
<add key="SystemLive" value="true" />
Then within your login function, add:
if(!ConfigurationManager.AppSettings["SystemLive"])
{
return View("logins_temporary_disabled_page");
}

Related

ASP.NET MVC 5 Identity - How can I keep a user logged in after changing their password?

I'm building my first ASP.NET MVC app and I would like the user to remain logged in whenever they change their password and are redirected to the home page.
I am using SecurityStamp to enable 'Log Off Everywhere' when the user signs in somewhere else. I have the validateInterval set to 0. So far this functionality works fine, if I log in using another browser the original session logs out when another request is made.
The issue I am having now is that I would like the user to remain logged in and be redirected to the home screen (not the login page) when they update their password. Is it possible to do this?
My change password controller looks like:
public async Task<ActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await UserManager.UpdateSecurityStampAsync(user.Id);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// go to welcome page
}
}
}
I am confused about the order in which I need to change the password, update the security stamp, and sign the user in. Do I also need to sign them out before signing them back in?

What's the purpose of this SignInManager call when the controller has the [Authorize] attribute?

Having set up a default ASP.Net MVC 5 application, I fail to understand why the below snippet has a call to SignInManager.
This is in the ManageController, which has the [Authorize] attribute.
//
// POST: /Manage/RemoveLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RemoveLogin(string loginProvider, string providerKey)
{
ManageMessageId? message;
var result = await UserManager.Get().RemoveLoginAsync(User.Identity.GetUserId<int>(), new UserLoginInfo(loginProvider, providerKey));
if (result.Succeeded)
{
var user = await UserManager.Get().FindByIdAsync(User.Identity.GetUserId<int>());
if (user != null)
{
await SignInManager.Get().SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
message = ManageMessageId.RemoveLoginSuccess;
}
else
{
message = ManageMessageId.Error;
}
return RedirectToAction("ManageLogins", new { Message = message });
}
I am wondering if, whenver I retrieve the authenticated user, I should repeat this step, i.e. check if the user is null and if not, await SignInAsync.
Edit: check if the user is null and if it is, await SignInAsync
Now, I've created a new controller, that I've given the [Authorize] attribute, and in the Index() function of the controller, I do:
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId<int>());
If I load that page in two tabs, sign out in one of them and then refresh the page, I'm redirected to the login screen. I've attached a debugger and have been unable to cause a case where the SignInManager is hit.
In what scenario would the user be not-null?
They do entirely different things. The Authorize attribute checks that the user is authenticated and authorized to access the action (based on role permissions and such). It doesn't authenticate the user. That's what the call to SignInManager is for. It actually authenticates the user, so that the user can then pass checks made by the Authorize attribute.
As for when the user might be null, effectively, if the action is protected by Authorize it probably never will be. It would have to take some strange confluence of events where the user was found in the database in order to sign them in, but then somehow was removed by the time you tried to retrieve it. In other words: not bloody likely. Still, it's good practice to always perform a null-check like this when a value could potentially be null, which user could.

User.Isauthenticated works randomly in ASP mvc

I am trying to make sure that my users log in as a elementryUser .So in Login controller i check the username and password ,if the authentication be true the user can enter the page the code to handle this is given below :(this part of code is login action )
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
System.Threading.Thread.Sleep(10000);
if (User.IsInRole("ElementryUser"))
{
int UserId = objuserrepository.FindBy(i => i.Email == User.Identity.Name).First().Id;
if (firstIdeaRepository.FindBy(i => i.UserId == UserId).Count() > 0)
{
return RedirectToAction("Index", "FirstIdea");
}
}
else
{
return RedirectToAction("Index", "Dashboard");
}
As you can see if the username and password be true the cookie is initialized,in this line if (User.IsInRole("ElementryUser")) when i want to check my user permission but it doesn't work and it doesn't execute if statement .so i trace the code and i found that the User.Isauthenticated returns false !!!!why?what is the problem?So i put and thread between these two line because i thought maybe the thread could solve the problem.But it doens't workUser.Isauthenticated returns false and sometimes returns true and when it returns true my if statement works .!!
Best regards
I am answer here general, because I can not do otherwise to your question. I can only give you some guidelines to look, and maybe move forward.
The authentication is connected with one cookie, as you already know.
So if the cookie is not readed then user is not authenticated, beside the fact that can be readed and not authenticate for other reasons.
When a cookie that you have set one one page, can not be readed you can check this reasons:
When you move from https to http pages and the cookie is set only for secure pages.
When you move from example.com to www.example.com to whatevet.example.com
When you set a cookie, but you make a redirect before the cookie have been transmitted to the client.
On web.config you can set up the 1 and 2 on this line.
<authentication mode="Forms">
<forms name=".auth"
path="/"
requireSSL="false"
cookieless="UseCookies"
domain="example.com"
enableCrossAppRedirects="false" />
</authentication>
Set the path, the requireSSL and the domain, without subdomains to set cookie be visible everywhere on your site.
Now if you left the requireSSL="false" the cookie can be possible read by the middle men and login to your site if stolen. Related : Can some hacker steal the cookie from a user and login with that name on a web site?
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); only sets the cookie, it doesn't set IPrincipal in your current request context ("User" is a shortcut to the current requests IPrincipal).
On the next request the user makes to the server, the browser will send the cookie and the FormsAuthentication module will then read that cookie and set IPrincipal on the current request context.
In other words, do something like
public ActionResult Login()
{
... stuff ...
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("someaction");
}
public ActionResult SomeAction()
{
if (User.IsInRole("ElementryUser"))
{
int UserId = objuserrepository.FindBy(i => i.Email == User.Identity.Name).First().Id;
if (firstIdeaRepository.FindBy(i => i.UserId == UserId).Count() > 0)
{
return RedirectToAction("Index", "FirstIdea");
}
}
else
{
return RedirectToAction("Index", "Dashboard");
}
}
This is a problem I have seen in my own apps before. You do end up with an extra roundtrip, but it's only at login so you should be fine.
I suspect the reason you see it sometimes working would have been because you had an authentication cookie already set from your previous test, i.e. you were already logged in before you called FormsAuthentication.SetAuthCookie.
No need of Thread.Sleep();
You can follow this article. You have to use custom authorization filter:
http://www.dotnet-tricks.com/Tutorial/mvc/G54G220114-Custom-Authentication-and-Authorization-in-ASP.NET-MVC.html

MVC 4 SimpleMembership - Why WebSecurity.CurrentUserId -1 after login

I am trying to set a cookie upon login and having issues with getting the current user id after login. In the below, intUserId is -1 and WebSecurity.IsAuthenticated is false. Is this not the correct place to put this code? After this, it redirects to the home page...so not sure why this is not the correct place.
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
//TODO: set current company and facility based on those this user has access to
int intUserId = WebSecurity.GetUserId(User.Identity.Name);
int intUserId2 = WebSecurity.GetUserId(model.UserName);
UserSessionPreferences.CurrentCompanyId = 1;
UserSessionPreferences.CurrentFacilityId = 1;
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Login only sets the Forms Authentication cookie.
The way asp.net authentication works is that it must read the cookie to set the authenticate the request, but since the cookie did not exist when the Login page was started, the framework doesn't know anything about the user.
Reload the page, and you will find the information is available.
FYI, this is nothing new with SimpleMembership or WebSecurity, this is the way Forms Authentication has always worked.

.Net Membership Provider not catching email duplications

I am relatively new to .Net/MVC3, and am working on a C#, MVC3, EF4 application that makes use of the default membership provider. From my reading, it should automatically catch duplicate emails, but it does not seem to do so, and I'm not certain why. What I really need to figure out is where to look to see if the right pieces are in place, and for the reason(s) why it might not do that validation check (it seems to do most/all of the others, like duplicate user names, or invalid password formats, etc. with only duplicate email not getting caught.)
Customizations include adding new users to a specific role, and redirecting to a 'first time' welcome page.
Here is the code:
// POST: /Account/Register
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
Roles.AddUserToRole(model.UserName, "Registered");
//return RedirectToAction("Index", "Home");
return RedirectToAction("Acceptance", "Account");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Here are the (untouched) validation methods:
#region Status Codes
private static string ErrorCodeToString(MembershipCreateStatus createStatus)
{
// See http://go.microsoft.com/fwlink/?LinkID=177550 for
// a full list of status codes.
switch (createStatus)
{
case MembershipCreateStatus.DuplicateUserName:
return "User name already exists. Please enter a different user name.";
case MembershipCreateStatus.DuplicateEmail:
return "A user name for that e-mail address already exists. Please enter a different e-mail address.";
etc.
By default, only usernames must be unique. If you want unique email addresses as well, then you must set that in the Web.config entry for the MembershipProvider.
something like
<membership>
<providers>
<add name="AspNetSqlMembershipProvider" [...] requiresUniqueEmail="true" [...] />
</providers>
</membership>
The only thing that comes to mind is making sure that model.Email has a value. Other thing you can check is look at the default membership provider tables in SQL server to check which values are being stored.

Resources