I am learning ASP.NET MVC and I have never worked with the .Net Authorisation, and Membership tool before.
I am creating an application that will let users log in and animals they own so it will have a number of tables
In tutorials I have looked at the membership and authorisation tool seems to create a new database for the login data. How would this work with my own custom tables?
Thanks
It's really easy to do, if you have custom tables. But in this case you have to check the password manually.
Here are few simple steps.
First, add forms authorization to your web.config file:
<authentication mode="Forms">
<forms loginUrl="~/login" defaultUrl="/" name=".ASPXFORMSAUTH" protection="All" slidingExpiration="true" path="/" timeout="50000000" />
</authentication>
Make sure you have controller properly configured in RouteConfig.cs
routes.MapRoute("Login", "login", new { controller = "Login", action = "Login" });
Make your own password validation function, based on information stored in your existing tables:
public bool ValidatePassword(string email, string password)
{
bool isValid = false;
// TODO: put your validation logic here
return isValid;
}
Note that you can use username or email. I prefer email, because users never forget their emails, but often have several usernames.
Create your Login controller:
public class LoginController : Controller
{
[HttpGet]
public ActionResult Login()
{
if(Request.IsAuthenticated)
{
return View("AlreadyLoggedIn");
}
return View();
}
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel viewModel)
{
if(ModelState.IsValid)
{
var isPasswordValid = ValidatePassword(viewModel.Email, viewModel.Password);
if(isPasswordValid)
{
FormsAuthentication.SetAuthCookie(viewModel.Email, true);
// now the user is authenticated
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("password", "Invalid password");
}
}
return View(viewModel);
}
}
View model is pretty easy:
public class LoginViewModel
{
[Required(ErrorMessage = "Please type your email")]
[EmailAddress(ErrorMessage = "Please provide correct email address")]
public string Email { get; set; }
[Required(ErrorMessage = "Please type your password")]
public string Password { get; set; }
}
And if you want to restrict access to some of your pages, use Authorize attribute:
public class AnotherController : Controller
{
[Authorize, HttpGet]
public ActionResult Index()
{
...
}
}
To summarize, you just need to put all your database login verification logic to ValidatePassword function, and that's it!
Related
I am using asp.net mvc 5 and using custom form authetication. When I try to login it login perfectly but when I try to logoff it redirect me to logiurl which I have set in webconfig file which is give below.
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="30" />
</authentication>
Rather than take me to the page for which redirection is defined in Logoff action method as shown below.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Logoff()
{
_formsAuth.SignOut();
return RedirectToAction("Index", "Home");
}
Everything is working fine in chrome browser but it is not working in firefox.
this method as been deprecated, tho it is useful for testing, i'm not using it anymore and rather use the already implement template which I customize to my own, when it comes to security, customers comes first.
that being said, I'm left with unanswered question about classes and models that you use.
Using the forms authentication feature requires calls to two static methods of the System.Web.Security.FormsAuthentication class:
The Authenticate method validates credentials supplied by the user.
The SetAuthCookie method adds a cookie to the response to the
browser, so that users do not need to authenticate every time they
make a request.
public class FormsAuthProvider : IAuthProvider {
public bool Authenticate(string username, string password){
bool result = FormsAuthentication.Authenticate(username, password);
if (result) {
FormsAuthentication.SetAuthCookie(username, false);
}
return result;
}
public class AccountController : Controller {
IAuthProvider authProvider;
public AccountController(IAuthProvider auth) {
authProvider = auth;
}
public ViewResult Login() {
return View();
}
[HttpPost]
public ActionResult Login(LoginViewModel model, string returnUrl) {
if (ModelState.IsValid) {
if (authProvider.Authenticate(model.UserName, model.Password)) {
return Redirect(returnUrl ?? Url.Action("Index", "Admin"));
} else {
ModelState.AddModelError("", "Incorrect username or password");
return View();
}
} else {
return View();
}
}
}
I'm developing an ASP.NET MVC 4 Application, wherein I need every user to be redirected to his custom page upon login. The users are obtained from the UserProfile class which I have refactored into a separate class file. How do I modify the Redirect To method in the Login (post) Action in a ASP.NET MVC 4 Internet Project to get this functionality? Further how do I pass this data to a User controller that can display information related to this specific user.
I'm using simple Membership as it comes out of the box in an internet application template in ASP.NET MVC 4.
I'm guessing you're talking about this piece of code in the MVC4 template? I'm doing something very similar - upon login, I redirect the user to a page called Index.cshtml listed under the Account controller :
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, model.RememberMe))
{
return RedirectToAction("Index", "Account");
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError(string.Empty, LocalizedText.Account_Invalid_User_Or_Password);
return View(model);
}
For user specific data, why not just extend the UsersContext.cs class in the Classes folder, then use WebSecurity.CurrentUserId to retrieve the information that pertains to that user?
Extended UsersContext class :
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
public bool IsPromotional { get; set; }
public bool IsAllowShare { get; set; }
}
This is the Index() action on the Account controller that they get redirected to upon login. Here I just call the users context, new up an AccountModel that's bound to the Index.cshtml page, set those attributes in the model, then return the View with the model we've built :
public ActionResult Index()
{
//New up the account model
var account = new AccountModel();
try
{
//Get the users context
var CurrentUserId = WebSecurity.CurrentUserId;
var context = new UsersContext();
var thisUser = context.UserProfiles.First(p => p.UserId == CurrentUserId);
//Set the name
account.Name = thisUser.Name;
//Set the user specific settings
account.IsAllowShare = thisUser.IsAllowShare;
account.IsPromotional = thisUser.IsPromotional;
}
catch (Exception exception)
{
_logger.Error(exception, "Error building Account Model");
}
return View(account);
}
It may not be exactly what you're looking for, but that should get you moving in the right direction.
How do you check the value that has been entered in an ASP.NET MVC #Html.TextBox and compare it with a value in the database? I want to make an easy login, and want to see if the value that has been entered in the textbox is the same as that in the database
<tr><td>Username</td><td>:</td><td>#Html.TextBox("username", new { #value = ViewBag.username })</td></tr>
I tried things like creating a viewbag and then taking it to the controller, but it didnt seem to work.
Create a viewmodel(a simple class) for this specific UI
public class LoginViewModel
{
[Required]
public string UserName { set;get;}
[Required]
[DataType(DataType.Password)]
public string Password { set;get;}
}
Now in your GET action, create an object of this class and send to your view.
public ActionResult Login()
{
var vm=new LoginViewMode();
return View(vm);
}
Now in our login view(Login.cshtml)which is strongly typed to our LoginViewModel, we will use the TextBoxFor html helper method to render the textboxes for our UserName and Password fields.
#model LoginViewModel
#using(Html.Beginform())
{
UserName
#Html.TextBoxFor(x=>x.UserName);
Password
#Html.TextBoxFor(x=>x.Password)
<input type="submit" />
}
This will render a form which has action attribute value set to /YourCotnroller/Login. now we need to have an HttpPost action method to handle the form posting
[HttpPost]
public ActionResult Login(LoginViewModel model)
{
if(ModelState.IsValid)
{
string uName=model.UserName;
string pass=model.Password.
//Now you can use the above variables to check it against your dbrecords.
// If the username & password matches, you can redirect the user to
// another page using RedirecToAction method
// return RedirecToAction("UserDashboard")
}
return View(model);
}
public ActionResult UserDashboard()
{
//make sure you check whether user is logged in or not
// to deny direct access without login
return View();
}
Try like this:
Define model property in your Model class:
public class Login{
[Required]
[Remote("IsUserNameAvaliable", "Home")]
public string username{get;set;}
[Required]
public string password{get;set;}
}
The Remote attribute that is placed will find the Method/Action with IsUserNameAvaliable in the controller name Home.
The remote attribute servers for this purpose in MVC.
public JsonResult IsUserNameAvaliable(string username)
{
//Check if there are any matching records for the username name provided
if (_dbEntity.Users.Any(c => c.UserName == username))
{
//If there are any matching records found
return Json(true, JsonRequestBehavior.AllowGet);
}
else
{
string userID = String.Format(CultureInfo.InvariantCulture,
"{0} is not available.", username);
return Json(userID, JsonRequestBehavior.AllowGet);
}
}
Now in your view strongly type the textbox
#model Application.Models.Login
#Html.TextBoxFor(m=>m.username)
#Html.ValidationMessageFor(m=>m.username)
Donot forget to include jquery validation scripts.
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
I have create one simple page in MVC, which is given in startup project of MVC by .net framework with slight modification in that.
I have created two models
Login
Register
Create two controllers.
LoginController
RegisterController.
Then i have use both of them to display in my home page (like facebook just an example)
Model code:
Login Model:
public class Login
{
private string email;
[Required]
public string Email
{
get { return email; }
set { email = value; }
}
private string password;
[Required]
public string Password
{
get { return password; }
set { password = value; }
}
}
Register Model
public class Register
{
private string email;
[Required]
public string Email
{
get { return email; }
set { email = value; }
}
private string password;
[Required]
public string Password
{
get { return password; }
set { password = value; }
}
private string name;
[Required]
public string Name
{
get { return name; }
set { name = value; }
}
}
View of Login and Register both is created using "Create" Option.
Both contains
<input type="submit" value="Create" />
Now, both views are on my other page, home page.
#Html.Action("Index", "Login")
#Html.Action("Index", "Register")
Both display okay, but when i click on any of the "create" button of any of the view, it also gets fire the event in controller for the other one.
My controller code....Logincontroller.
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(Login lgobj)
{
System.Threading.Thread.Sleep(2000);
string email = lgobj.Email;
string password = lgobj.Password;
return View();
}
RegisterController:
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(Register model)
{
return View();
}
Can anyone please specify the reason, or what is missing in my code?
If not clear, please let me know, i will be describe more.
Try changing your Home View to this:
#using (Html.BeginForm())
{
#Html.Action("Index", "Login")
}
#using (Html.BeginForm())
{
#Html.Action("Index", "Register")
}
What I think is happening is that your submit is calling a POST to the wrong controller, as there is no distinct difference of forms.
Ok so you have a page (a View) that displays two ActionLinks. Once clicked, these two ActionLinks brings you to the LoginController or the RegisterController (depending on the link you’ve clicked). In both cases, you end up inside the Index() ActionResult of the appropriate Controller…so far so good!
Now…once inside the Index() you simply call return View() but I don’t see any code showing that these Views are strongly typed! Try the following:
LoginController:
public ActionResult Index()
{
var model = new Login();
return View(model);
}
RegisterController:
public ActionResult Index()
{
var model = new Register();
return View(model);
}
Assuming you’ve created two strongly typed views (Index.cshtml inside the Login folder and Index.cshtml inside the Register folder) make sure each view has an appropriate form and a submit button.
Little change to what mentioned by #Shark,
We need to mention the action and controller to which indivisual form will post data, If we will not specify both the form will post to the page url.
#using (Html.BeginForm("Index","Login"))
{
#Html.Action("Index", "Login")
}
#using (Html.BeginForm("Index","Register"))
{
#Html.Action("Index", "Register")
}
In this scenario, ( as commented in my first answer)
We need to use a Dynamic view page. (More Information)
Follow following steps:
Create DynamicViewPage type:
public class DynamicViewPage : ViewPage
{
public new dynamic Model { get; private set; }
protected override void SetViewData(ViewDataDictionary viewData)
{
base.SetViewData(viewData);
Model = ViewData.Model;
}
}
Your Controller will look like
public ActionResult Account(string returnUrl)
{
LoginModel loginmodel = null;//Initialize Model;
RegistrationModel registrationModel = null ;//Initialize Model;
.
.
.
.
.
return View("Account", new
{
Login = loginmodel,
Register = registrationModel
});
your View should Inherit from
Inherits="DynamicViewPage"
Now #Model.Login will give you Loginmodel
#Model.Register will give you RegisterModel
It should work as you expected.........
I have a the following methods in an MVC Controller which redirect to the login page when a user is not logged in.
[Authorize]
public ActionResult Search() {
return View();
}
[Authorize]
public ActionResult Edit() {
return View();
}
Is there a quick/easy/standard way to redirect the second action to a different login page other than the page defined in the web.config file?
Or do I have to do something like
public ActionResult Edit() {
if (IsUserLoggedIn)
return View();
else
return ReturnRedirect("/Login2");
}
I think it is possible by creating a custom authorization filter:
public class CustomAuthorization : AuthorizeAttribute
{
public string LoginPage { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.Redirect(LoginPage);
}
base.OnAuthorization(filterContext);
}
}
In your action:
[CustomAuthorization(LoginPage="~/Home/Login1")]
public ActionResult Search()
{
return View();
}
[CustomAuthorization(LoginPage="~/Home/Login2")]
public ActionResult Edit()
{
return View();
}
Web.config based forms authentication does not have such a functionality built-in (this applies to both WinForms and MVC). You have to handle it yourself (either through an HttpModule or ActionFilter, the method you mentioned or any other method)
I implemented the accepted answer by user434917 and even though I was being redirected correctly, I was also receiving the error "Server cannot set status after HTTP headers have been sent." in the server log. After searching, I found this post (answer by Mattias Jakobsson) that solved the problem. I combined the answers to get this solution.
Create a custom authorization filter:
using System.Web.Mvc;
using System.Web.Routing;
namespace SomeNamespace.CustomFilters
{
public class CustomAuthorization : AuthorizeAttribute
{
public string ActionValue { get; set; }
public string AreaValue { get; set; }
public string ControllerValue { get; set; }
public override void OnAuthorization(AuthorizationContext context)
{
base.OnAuthorization(context);
if (context.HttpContext.User.Identity.IsAuthenticated == false)
{
var routeValues = new RouteValueDictionary();
routeValues["area"] = AreaValue;
routeValues["controller"] = ControllerValue;
routeValues["action"] = ActionValue;
context.Result = new System.Web.Mvc.RedirectToRouteResult(routeValues);
}
}
}
}
Then on your controller, use the customer attribute.
[CustomAuthorization(ActionValue = "actionName", AreaValue = "areaName", ControllerValue = "controllerName")]
public class SomeControllerController : Controller
{
//DO WHATEVER
}
Yeah pretty easy! Lets say you have 2 different type of users. First typenormal users, the other one is administrators. You would like to make them login from different pages. You also want them to be able to access different ActionResults.
First you have add two different schemes. In these schemes you will define your different login pages and other options you want.
in startup.cs
services.AddAuthentication("UserSceheme").AddCookie("UserScheme", config =>
{
config.LoginPath = "/UsersLogin/Login/";
config.Cookie.Name = "UsersCookie";
});
services.AddAuthentication("AdminScheme").AddCookie("AdminScheme", config =>
{
config.LoginPath = "/AdminLogin/Login/";
config.Cookie.Name = "AdminsCookie";
});
Then you will define two policies. Here I called them UserAccess and AdminAccess Each policy will use the sceheme that I point.
In startup.cs just after schemes add those below.
services.AddAuthorization(options =>
{
options.AddPolicy("UserAccess", policy =>
{
policy.AuthenticationSchemes.Add("UserScheme");
policy.RequireAuthenticatedUser();
});
options.AddPolicy("AdminAccess", policy =>
{
policy.AuthenticationSchemes.Add("AdminScheme");
policy.RequireAuthenticatedUser();
});
});
The last thing we have to do is again telling the scheme we want to use when login.
var userPrincipal = new ClaimsPrincipal(new[] {
new ClaimsIdentity(loginClaims, "ServiceCenter")
});
HttpContext.SignInAsync("AdminScheme",userPrincipal);
Thats it! Now we can use these just like this; This will redirect you to the users login page.
[Authorize(Policy = "UserAccess")]
public IActionResult Index()
{
return View();
}
If you have some places that you want both user types to be able to access all you have to do;
[Authorize(Policy = "AdminAccess")]
[Authorize(Policy = "UserAccess")]
public IActionResult Index()
{
return View();
}
And finally for log-out you also have to point the scheme
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync("AdminScheme");
return View();
}
Thats it!