So I am currently using a ViewBag set in the login to determine if they can see admin only stuff. This is done this way because Roles.CreateRole, Membership.CreateUser and Roles.AddUserToRole is disabled because we use ModelFirst ASP.net.
public ActionResult Login(LoginModel model, string returnUrl)
{
ViewBag.Admin = false;
if (model.IsValid(model.UserName, model.Password))
{
ViewBag.Admin = (bool)model.currentLoggedInEmployee.IsAdmin;
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Login data is incorrect!");
return View(model);
}
}
Then we simply use:
#if (ViewBag.Admin == true) {
<li>#Html.ActionLink("Administration", "Index", "Administration")</li>
}
to only show these buttons to admins. This works.
Now what we want, is to make sure only administrators can run some functions, by doing something similar to the normal
[Authenticate(Roles="Admin")]
[HttpPost]
public ActionResult Create(FormCollection collection)
{
// TODO: Add insert logic here
}
But because we don't have any "Roles" we can not do it like this. We need to use the ViewBag.Admin value to authorize people to use these functions. Question is, how can this be done?
I would recommend rolling your own AuthorizeAttribute and from there you can determine whether or not the current logged in user is an admin or not.
When you create your authentication cookie add some additional information (i.e. the admin flag) e.g.
public ActionResult Login(LoginModel model, string returnUrl)
{
if (model.IsValid(model.UserName, model.Password))
{
var ticket = new FormsAuthenticationTicket(1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(30),
model.RememberMe,
model.currentLoggedInEmployee.IsAdmin, // user data
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
// Redirect back to original URL.
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Login data is incorrect!");
return View(model);
}
}
Create a custom authorize attribute to authenticate the logged in user against the role e.g.
public class AdminOnlyAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Current.User.Identity.IsAuthenticated)
{
var ticket = ((FormsIdentity)User.Identity).Ticket;
return (bool)ticket.UserData;
}
else
{
return false;
}
}
}
Then decorate your action as:
[AdminOnly]
[HttpPost]
public ActionResult Create(FormCollection collection)
{
// TODO: add insert logic here
}
Related
I'm developing a simple Custom Role-based Web Application using ASP.Net MVC, In my login Action, I'm creating a Profile session as below:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
using (HostingEnvironment.Impersonate())
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
var employeeProfile = AccountBal.Instance.GetEmployee(loginId);
Session["Profile"] = employeeProfile;
FormsAuthentication.SetAuthCookie(model.UserName, true);
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", #"The user name or password provided is incorrect.");
return View(model);
}
}
And I'm checking this or using this session in all Controller Actions as below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateOrEdit(MyModel model)
{
var employee = (Employee) Session["Profile"];
if (employee == null)
return RedirectToAction("Login", "Account");
if (ModelState.IsValid)
{
// Functionality goes here....
}
}
Is there any way I can move this piece of session checking code in a base class or centralized class? so that, I do not need to check it every time in a Controller Actions instead I will access the properties directly
say,
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateOrEdit(MyModel model)
{
var employee = _profileBase.GetCurrentProfile();
if (employee == null)
return RedirectToAction("Login", "Account");
if (ModelState.IsValid)
{
// Functionality goes here....
}
}
Create a base controller that contains your GetCurrentProfile method to retrieve current user profile like
public class BaseController : Controller
{
public Employee GetCurrentProfile()
{
return (Employee)Session["Profile"];
}
public bool SetCurrentProfile(Employee emp)
{
Session["Profile"] = emp;
return true;
}
}
And inherit your desired controller with above BaseController and access your GetCurrentProfile method like below
public class HomeController : BaseController
{
public ActionResult SetProfile()
{
var emp = new Employee { ID = 1, Name = "Abc", Mobile = "123" };
//Set your profile here
if (SetCurrentProfile(emp))
{
//Do code after set employee profile
}
return View();
}
public ActionResult GetProfile()
{
//Get your profile here
var employee = GetCurrentProfile();
return View();
}
}
GetCurrentProfile and SetCurrentProfile directly available to your desired controller because we directly inherit it from BaseController.
You may usetry/catch in above code snippet.
Try once may it help you
I write a new ASP.NET MVC 5 application and I have some problems with the authentication. I want to have two registration and login forms (for users and for companies). I use the basic table ApplicationUser for Users and make my own table CompaniesAccountModel for companies. But the problem comes when I use the UserManager and SignInManager. I can't modify them to work with CompaniesAccountModel. Here you are some code.
[AllowAnonymous]
public ActionResult CompanyRegister()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult CompanyRegister([Bind(Include = "CompanyName, Password, Email, ConfirmPassword")] CompanyAccountModel model)
{
if (ModelState.IsValid)
{
db.CompanyAccountModels.Add(model);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
// If we got this far, something failed, redisplay form
return View(model);
}
and
[AllowAnonymous]
public ActionResult CompanyLogin(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CompanyLogin(CompanyLoginViewModel 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.CompanyName, model.Password, model.RememberMe, shouldLockout: false);
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 want to use the UserManager and SignInManager for the companies registration and login. If someone have an idea how to do this, it would be well.
You could easily customize authentication process for your company users. And use it side by side with existing method for ordinary users. Consider this example as a clue:
public ActionResoult CompanyLogin(CompanyLoginViewModel model, string returnUrl)
{
// imaging you have own company manager, completely independent from identity
// you could check validity of company by own preferred logic
if(_companyManager.IsValid(model))
{
// company is valid, going to authenticate
var ident = new ClaimsIdentity(
new[]
{
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, model.CompanyName),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
// an optional claim you could omit this
new Claim(ClaimTypes.Name, model.CompanyName),
// add this role to differentiate from ordinary users
new Claim(ClaimTypes.Role, "Company"),
// you could even add some role
new Claim(ClaimTypes.Role, "AnotherRole"),
// and so on
},
DefaultAuthenticationTypes.ApplicationCookie);
// Identity is sign in user based on claim don't matter
// how you generated it Identity
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
// auth is succeed,
return RedirectToAction("MyAction");
}
ModelState.AddModelError("", "We could not authorize you :(");
return View();
}
Since we injected our logic to Identity, we don't need to do extra thing at all.
[Authorize]
public ActionResult MySecretAction()
{
// all authorized users could use this method don't matter how has been authenticated
// we have access current user principal by calling also
// HttpContext.User
}
[Authorize(Roles="Company")]
public ActionResult MySecretAction()
{
// just companies have accesses to this method
}
Also if both ApplicationUser and Company classes share lots in common you could just extend Company from ApplicationUser. By doing so you don't need to write extra login method. Same login works for both. But if for any reason you don't want inherit Company from ApplicationUser my above solution more desirable.
I don't want to repeat myself. That is, I don't want the same code in two different controllers.
I always start from a default mvc5 web app project. That project has a Register ActionMethod in an AccountController:
//
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Say I have a CampaignController and I want to register a user when he/she is on that page, fills out his/her username and pass and clicks the send form/submit button. What is the best thing to do in the ActionMethod of that form/controller?
Yes, I want to have the registerform in two or more places.
What is the best way to accomplish this in mvc 5?
Create a logic layer
put the register logic in that logic layer
add the logic layer as reference in the web layer
hit the function in the logic layer from both actionresults :)
In your example at least... I would call the actions of your AccountController from your Campaign views.
For me it working like a charm after setting the current ControllerContext to AccountControllerContext
//This is employee controller class
public ActionResult Create([Bind(Include = "EmployeeId,FirstName,LastName,DOJ,DOB,Address,City,State,Mobile,Landline,ReportsTo,Salary")] Employee employee)
{
if (ModelState.IsValid)
{
AccountController accountController = new AccountController();
accountController.ControllerContext = this.ControllerContext;
//accountController.UserManager;
var userId = accountController.RegisterAccount(new RegisterViewModel { Email = "temp#temp.com", Password = "Pravallika!23" });
if (!string.IsNullOrEmpty(userId))
{
employee.UserId = userId;
employee.CreatedBy = User.Identity.GetUserId();
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
}
//customized method in AccountController
public string RegisterAccount(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = UserManager.Create(user, model.Password);
//to add roles
//UserManager.AddToRole(user.Id, "Admin");
if (result.Succeeded)
{
return user.Id;
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed
return null;
}
I have the following methods responsible for login authentication:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel loginModel, string returnUrl)
{
if (ModelState.IsValid)
{
//if (Membership.ValidateUser(loginModel.UserName, loginModel.Password))
var session = RMWebClientBL.Sessions.Login(loginModel.UserName, loginModel.Password);
if(session != null && !session.IsFailed && session.SessionId != Guid.Empty)
{
SetAuthCookie(loginModel, session);
RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View();
}
private void SetAuthCookie(LoginModel loginModel, DomainObjects.Sessions.SessionDetails session)
{
// create encryption cookie
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,
loginModel.UserName,
DateTime.Now,
//TODO: make it configurable!!!!
DateTime.Now.AddMinutes(20),
loginModel.RememberMe,
session.SessionId.ToString());
// add cookie to response stream
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
if (authTicket.IsPersistent)
{
authCookie.Expires = authTicket.Expiration;
}
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
}
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
However when i am trying to login "User.Identity.IsAuthenticated" is still false after setting the cookie, BUT it seems that i am logged in because if i am clikcing on our logo which redirects me to the Homepage i am authenticated.
why i am not being able to redirect after logging in?
Found the solution it was a silly thing after all:
the problem was on the following method:
public ActionResult Login(LoginModel loginModel, string returnUrl)
{
if (ModelState.IsValid)
{
//if (Membership.ValidateUser(loginModel.UserName, loginModel.Password))
var session = RMWebClientBL.Sessions.Login(loginModel.UserName, loginModel.Password);
if(session != null && !session.IsFailed && session.SessionId != Guid.Empty)
{
SetAuthCookie(loginModel, session);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View();
}
as you can see i've added "return" to
return RedirectToLocal(returnUrl);
the problem was that the "return" keyword missing so i always got to the last row which is :
return View();
thats way i was always getting back to my login page while not addressing the right page.
Just by looking it doesn't look like you're using the standard MembershipProvider that is included in .NET, as it is this provider that sets the IsAuthenticated flag. It appears you using your own provider but just using the FormsAuthenticationTicket part only.
Try use the following method instead of encrypting and create your own cookie.
FormsAuthentication.SetAuthCookie(userAuth, model.RememberMe);
The method doesn't just encrypt the ticket, but it also create a cookie, add it to the response and some other things behind the scene. I am using this in my project and it's working perfectly fine.
i have created the controller :
[Authorize]
[AcceptVerbs(HttpVerbs.Delete)]
public ActionResult Delete(int id)
{
try
{
db.DeleteObject(db.AEROLINEA.FirstOrDefault(x => x.AEROLINEAID == id));
db.SaveChanges();
}
catch { /* TODO:Display message*/ }
return View();
}
if i execute in firebug the next javascript anyone logged could delete an airline even if he doesnt have permissions to delete
var action = "/Airline/Delete/" + recordId;
var request = new Sys.Net.WebRequest();
request.set_httpVerb("DELETE");
request.set_url(action);
request.add_completed(deleteCompleted);
request.invoke();
HOw can avoid this issue???
You can filter the the roles:
Example:
[Authorize(Roles="Admin")]
[AcceptVerbs(HttpVerbs.Delete)]
public ActionResult Delete(int id)
{
try
{
db.DeleteObject(db.AEROLINEA.FirstOrDefault(x => x.AEROLINEAID == id));
db.SaveChanges();
}
catch { /* TODO:Display message*/ }
return View();
}
Or use the AntiforgeryToken with a juicy salt at the View..
[Authorize] without parameters allows you to indicate that a user must be logged in. You also can specify users/roles, authorized to access your action