Custom redirection in ASP.NET MVC 4 - asp.net

I'm trying to build a two-step custom registration for users in my ASP.NET MVC 4 application. This involves redirecting users after registration to the Edit view in the User Controller. I've tweaked the code in the stock Account Controller template to achieve this.
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Edit", "User", new { id = WebSecurity.CurrentUserId });
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
return View(model);
}
However when I run this I get a 404 on the address bar it shows
http://localhost:17005/User/Edit/-1
Which means it's going into the Edit action of user controller, but then ending up with a Id of -1. Any help would be appreciated.

Membership.GetUser().ProviderUserKey
Alternatively you could not pass a user id to the edit action and have the code use the currently logged in user.
RedirectToAction("Edit", "User");
In your user controller
[Authorize]
public ActionResult Edit()
{
object userId = Membership.GetUser().ProviderUserKey;
}
Please excuse brevity as I am currently on my mobile

Related

adding a custom rule in default asp.net login system

Hi guys I'm working on a project in which we can disable our users. There is a column name status in user table that can be true or false. Now I want to add a new rule in default asp.net user login that if a user is disable(his column name status equals false), should not able to login with an error message that "Admin have disabled your account".
I have looked in,
ManageController.cs
IdentityConfig.cs
ManageViewModles.cs
IdentityModel.cs
but I didn't get any clue. How can I add this rule in my asp.net MVC-5 application
You could define a verification with an async function in your login view controller and call it on Login button press.
Good place to start: here
EDIT
Here some code sample you could have in your HomeController->Index Action. Note that this isn't async but you could implement an async action with your db call:
[HttpGet]
public ActionResult Index()
{
//Verification of user
//if true
return View();
//else false
return ErrorView(); //View that contains the error message
}
In login method of account controller i achieved it using this code.
I updated code in my switch(result) of case SignInStatus.Success:
case SignInStatus.Success:
var userID = SignInManager.AuthenticationManager.AuthenticationResponseGrant.Identity.GetUserId();
AspNetUser res = db.AspNetUsers.Where(x => x.Id == userID).Select(x => x).FirstOrDefault();
if (res.Status == "False")
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
// return RedirectToAction("Index", "Home");
ModelState.AddModelError("", "Admin has disabled your account.");
return View(model);
}
//remaining code here

How to show a successful login popup message when the successful login can go to a redirect url?

I have the following login post action in the controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[Auditing]
public async Task<ActionResult> Login(LoginModel details, string returnUrl)
{
if (ModelState.IsValid)
{
AppUser user = await UserManager.FindAsync(details.Name,
details.Password);
if (user == null)
{
ModelState.AddModelError("", "Invalid name or password.");
}
else
{
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
ident.AddClaims(LocationClaimsProvider.GetClaims(ident));
ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident));
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties
{
IsPersistent = false
}, ident);
//Persist login into DB upon successful login
Loginrecord login = new Loginrecord();
login.Username = user.UserName;
login.SessionId = HttpContext.Session.SessionID;
Session["sessionid"] = HttpContext.Session.SessionID;
login.Date = DateTime.Now;
SQLLoginrecord sqlLogin = new SQLLoginrecord();
sqlLogin.PutOrPostLogin(login);
//End addition
return Redirect(returnUrl);
}
}
ViewBag.returnUrl = returnUrl;
return View(details);
}
Since a successful login from this action can go to any authorization-requiring page the user types the URL for in the address bar of the browser, how can I show a popup message that indicates a successful login? If I were to go with the ViewBag approach and add a Success variable, do I have to access that ViewBag from every View (including some that do not have a shared Layout) of the application that requires authentication?
Redirect method is going to issue a 302 response to the browser with the new url in the location header and browser will make a totally new Http request for that url. So ViewBag is not going to help you.
You might consider using TempData (which works for the next call) to share data from your Login action method to any other action method. You can write an action filter which checks the TempData item and set the approriate ViewBag entry so that you can do whatever you want in your other views.
public class LoginMsg : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var msg=filterContext.Controller.TempData["LoginMsg"] as string;
if (!String.IsNullOrEmpty(msg))
{
filterContext.Controller.ViewBag.LoginMsg= msg;
}
base.OnActionExecuting(filterContext);
}
}
Now in your Login class, before redirecting, set the TempData
public async Task<ActionResult> Login(LoginModel details, string returnUrl)
{
// do stuff
TempData["LoginMsg"]="Logged in successfully";
return Redirect(returnUrl);
}
Now decorate your other action methods (or you can do it on controller level) with our action filter.
[LoginMsg]
public ActionResult Dashboard()
{
return View();
}
In your view or the _Layout, you can access the ViewBag item and call the code to the trigger your popup control.
<script>
var msg = "#ViewBag.Msg";
if (msg.length) {
// alert(msg);
// Call the code to fire the popup now
// yourMagicPopupPlugin.Popup(msg);
}
</script>

Anti-Forgery Token was meant for a different claims-based user

I am working on a logout feature in the application we are using ASP.NET Identity login. I can login successfully but when I logout and then try to login again I get the following message:
The provided anti-forgery token was meant for a different claims-based user than the current user.
Here is my logout code:
public ActionResult Logout()
{
SignInManager.Logout();
return View("Index");
}
**SignInManager.cs**
public void Logout()
{
AuthenticationManager.SignOut();
}
After the user press the logout button he is taken to the login screen. The url still says "http://localhost:8544/Login/Logout". Since we are on the login screen maybe it should just say "http://localhost:8544/Login".
What worked for me was switching the order of the middlewares used. Add first app.UseAuthentication() and then the antiforgery stuff. This is how I did it:
app.UseAuthentication();
app.Use(next => ctx =>
{
var tokens = antiforgery.GetAndStoreTokens(ctx);
ctx.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
return next(ctx);
});
Doing it the other way around creates a token that is not meant for authenticated users.
You are returning a View, rather than calling RedirectToAction(). So what is happening is the view is running under the context of the logout request, where the user is still logged in. They won't be logged out until the request finishes.
So, try
public ActionResult Logout()
{
SignInManager.Logout();
return RedirectToAction("Index", "Home");
}
I found that users were experiencing this issue when they would submit the login page when already authenticated. I replicated this error by:
Opening two tabs when logged in,
Logging out from one,
Reloading both,
Logging in to one,
Trying to log in with the other. The error occurred before entry to the POST: /Account/Login action.
The majority of my users use the web app on a mobile device, so it made sense that they had bookmarked the login page and pulled it up and submitted when they had a tab opened in the background already logged in. I also surmised that sometimes they would have a dormant tab loaded with the login form and just pull that tab up and submit.
I realize that there are many ways to solve this issue. I solved this with two changes:
I added a check on User.Identity.IsAuthenticated to my "GET: /Account/Login" action:
if (User.Identity.IsAuthenticated)
{
try
{
return RedirectToLocal(returnUrl);
}
catch
{
return RedirectToAction("index", "Home");
}
}
In my controller I created a "check if logged in" action:
[AllowAnonymous]
public JsonResult CheckLogedIn()
{
try
{
return Json(new { logged_in = User.Identity.IsAuthenticated }, JsonRequestBehavior.AllowGet);
}
catch
{
return Json(new { logged_in = false }, JsonRequestBehavior.AllowGet);
}
}
And I called it repeatedly in the view to redirect all open login forms away from the login page when already logged in:
<script type="text/javascript">
setInterval(function () {
$.ajax({
url: '#Url.Action("CheckLogedIn", "Account")',
type: "GET",
}).done(function (data) {
if (data.logged_in) {
window.location = '/';
}
});
}, 5000);
</script>
This worked well for me. Hope it helps you.
Try this:
public ActionResult Logout()
{
AuthenticationManager.SignOut();
Session.Abandon();
return RedirectToAction("Index");
}
That will reload your login page which will provide you a new CSRF token.
I've been getting this same error on the login for a LONG time now, but haven't been able to work out why. Finally I found it, so I'm posting it here (although it's a slightly different cause) in case someone else has it.
This was my code:
//
// GET: /login
[OutputCache(NoStore = true, Location = System.Web.UI.OutputCacheLocation.None)]
public ActionResult Login()
{
return View();
}
//
// POST: /login
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
if (!ModelState.IsValid)
{
return View(model);
}
//etc...
This worked fine for 99.99% of the logins, but every now & then I got the above-mentioned error, although I couldn't reproduce it, until now.
The error only happens when someone clicks the login button twice in quick succession. However, if I remove the AuthenticationManager.SignOut line in the Login action, then it's fine. I'm not sure why I put that line in there, but it's causing the issue - and removing it fixes the problem.
I didn't have the AuthenticationManager.SignOut command as Sean mentioned in my Login method. I was able to reproduce by clicking on the login button more than once before hte next View loads. I disabled the Login button after the first click to prevent the error.
<button type="submit" onclick="this.disabled=true;this.form.submit();"/>
Try this:
public ActionResult Login(string modelState = null)
{
if (modelState != null)
ModelState.AddModelError("", modelState );
return View();
}
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model)
{
AuthenticationManager.SignOut();
return RedirectToAction("Login", "Controller", new { modelState = "MSG_USER_NOT_CONFIRMED" });
}

Redirect users according to their Identity Role - ASP MVC 4 and 5

Hi I have an ASP MVC Application that using the MVC 5 Identity Roles, for the sake of simplicity I have 2 Identity Roles ("Admin" and "Staff"). Users in Role Admin can access the Admin Panel where they can create another users, and Users in Staff Role can only access Staff View.
I have no problem in assigning users to roles and apply [Authorise] to Controllers.
I want to redirect Users users to their relative Views after success log ins, so if a user is in Admin role, get's automatically redirected to admin panel or view and if a user in staff page redirected to staff view.
How do I apply this in my Login Controller? Thanks
If you're using UserManager and SignInManager (as per the default template) in your Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SignIn(SignInViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
ApplicationUser user = await UserManager.FindAsync(model.Email, model.Password);
// Redirect to User landing page on SignIn, according to Role
if ((UserManager.IsInRole(user.Id, "User")))
{
return RedirectToAction("Index", "User");
}
if ((UserManager.IsInRole(user.Id, "Administrator")))
{
return RedirectToAction("Index", "Administrator");
}
return View(model);
// etc below - code to taste
case SignInStatus.LockedOut:
return View(model);
case SignInStatus.RequiresVerification:
return View(model);
case SignInStatus.Failure:
default:
return View(model);
}
}
You can simply redirect them from your login action method. So the pseudocode would be like below.
if (User.Role == "Admin")
{
//send him to Admin controller and index action
return RedirectToAction("ActionName","Controllername");
}
else if (User.Role == "Staff")
{
//send him to staff controller and index action
return RedirectToAction("ActionName","Controllername");
}
else
{
// if neither role show default public page
return RedirectToAction("ActionName","Controllername");
}
The comments are just to give you idea, It might vary in your application
if(UserType.Admin)
{
return RedirectToAction("HomeAdmin","Admin") // Admin Home
}
else if(UserType.Staff)
{
return RedirectToAction("HomeStaff","Staff")//Staff Home
}
And in your action methods
[Authorize]// Use this to authorize
public ActionResult HomeStaff()
{
}
[That's how I solved it, I didn't find any UserType or User.Role as stated in above solutions]

About Response.Redirect, FormsAuthentication and MVC

I want to understand the difference in the behavior of a program when we call FormsAuthentication.RedirectFromLoginPage vs. when we call Response.Redirect(FormsAuthentication.GetRedirectUrl()) and manually redirect.
Please see the comments below.
I have a LoginController/Index (two actions, one for HttpGet and one for HttpPost). The View of this controller represents the application's login page.
I also have a home page or landing page, i.e. the page that the user must be taken to after a successful login. This is represented in my application by the HomeController's Index action and the ~Views/Home/Index.cshtml view.
I have presented three scenarios. I understand scenario 1 and I expect it to work the way it does, but I noted a difference in scenarios 2 and 3.
Scenario 1
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control won't reach here
// as expected and the user will be taken to the view that is served
// by the HomeController::Index() action. If it is by convention, it will
// be the ~Views/Home/Index.cshtml view. This is fine.
return View();
}
}
}
Scenario 2
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
Response.Redirect(FormsAuthentication.GetRedirectUrl(loginViewModel.UserName,
loginViewModel.RememberMe));
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control still reaches here
// as expected. And as expected, it renders the same View, i.e. the View
// associated with the controller we are in, which is ~Views/Login/Index,
// which represents the login page. This is wrong. I shouldn't redirect here.
// I understand this. My question here is two fold:
// 1) I am simply trying to understand the difference in behaviors of the three
// scenarios described in this question.
// 2) Given this, the right way would be to not use Response.Redirect here but instead
// use RedirectToAction. However, if I wanted to use Response.Redirect, what should
// I do?
return View();
}
}
}
Scenario 3
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
FormsAuthentication.RedirectFromLoginPage(loginViewModel.UserName,
loginViewModel.RememberMe);
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control still reaches here
// as expected. However, magically, somehow, even though the statement below
// suggests that the user must be taken to the View of the same controller and
// action that we are currently in, i.e. the View of the LoginController::Index()
// action, i.e. the ~Views/Login/Index.cshtml, it magically takes me to the
// ~Views/Home/Index.cshtml instead, which is what is specified as the LoginPage
// attribute of the <authentication>/<forms> element in the web.config.
// I want to know how this happens.
return View();
}
}
}
Update
I am at my wit's end now. Now, even Scenario 1 that uses RedirectToAction is calling the Index() action on the LoginController class.
The actual difference is that FormsAuthentication.RedirectFromLoginPage() sets cookies and then makes redirects but FormsAuthentication.GetRedirectUrl() only returns redirect url.
The funny thing is that implementation of FormsAuthentication.GetRedirectUrl() is like this:
public static String GetRedirectUrl(String userName, bool createPersistentCookie)
{
if (userName == null)
return null;
return GetReturnUrl(true);
}
So actually userName and createPersistentCookie parameters are completely ignored. You must call FormsAuthentication.SetAuthCookie( userName, true/false ) manually before calling GetRedirectUrl.
Agree with Vasily.
RedirectFromLoginPage issues an authentication ticket and places it in the default cookie using the SetAuthCookie method.
You can read something about this behavior here.
If you want to have a better control over the cookie creation you should (encryption, expiration, extending the principal) you should create the cookie yourself.
I explained the whole process here and here.

Resources