Force a user to re-enter credentials before submit - asp.net-mvc

Using MVC5, i have an application which a user must be logged into, and then can perform standard actions on some data (create, edit, delete).
I would like to add a credentials prompt, whenever a certain task if performed. So say for example a user is editing a row of data. I want them to be prompted to enter their login credentials again when they hit the Save button, before the row is updated. To be clear, they are ALREADY logged in, i just want to force them to re-confirm their credentials before being allowed to save.
How can i do this in the controller? I want a seperate screen/popup to show, asking for username and password (which will then be checked to ensure correct user credentials) before allowing update of the data.
I looked at creating a new method in the controller, which is passed a username and password, which looks after checking the users credentials again. But how do I go about calling this from the Edit screen, when I also need a popup to appear? Do i go down the route of adding a hidden div on the Edit view, which shows when the user clicks the Save button, and it then calls the method?

Generally, you're expected to attempt a solution, first. Then, if you run into specific issues, you can ask a question about those specific issues. I will tell you that this should be relatively straight-forward. All you need is for the user to re-enter their password. Just add a password input to your edit form and bind it to something on your view model, or you can simply bind it directly to an action parameter, in addition to your view model:
[HttpPost]
public ActionResult MyAction(MyViewModel model, string password)
If you want it to be done in a popup, simply include the popup HTML within the form (so the that the input in the popup will be part of the form) or you'll need to use JavaScript to set another input within the form, which would be bound to either a view model property or action param. Either way, the point is that the password should be posted along with the rest of the form data.
Once inside your post action, you can verify the password by manually:
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var verifyPassword = UserManager.PasswordHasher.VerifyHashedPassword(user.PasswordHash, password);
if (verifyPassword == PasswordVerificationResult.Failed)
{
ModelState.AddModelError("", "Password incorrect.");
// If password is incorrect, ModelState will be invalid now
}
if (ModelState.IsValid)
{
// save posted data
}

It sounds like you'd ideally want an action which you can call asynchronously from the client. While this can take the form of a standard MVC controller action, you may want to consider building this into a Web API controller (Generally we would use Web API controllers to serve up non-HTML responses). You can read more about Web API in many places on the web so I won't go into that now, but let's say you have a HttpResponseMessage result method which looks like this:
[HttpPost]
public HttpResponseMessage CheckCredentials(string username, string password)
{
// Check user credentials and return either one of the following results:
// If credentials valid
return Request.CreateResponse(HttpStatusCode.OK);
// If not valid
return Request.CreateErrorResponse(HttpStatusCode.BadRequest);
}
Using this pattern you could return a '200 OK' response for valid credentials and a '400 Bad Request' for invalid credentials.
As you already stated, you could have the HTML content required for authentication prompt hidden on the page. When the user performs an action which requires authentication, you could render the popup. When the user submits the popup you could fire off an asynchronous request to the Web API endpoint which you created earlier. Depending on the response you get back, you either proceed with the task or prompt for credentials again with an error message.
Obviously as you'd be sending user credentials over a we request, make sure you're making use of HTTPS.
EDIT:
As Chris mentioned below, this solution leaves your 'quick check' in the hands of the client. While this is fine when you simply want to provide a way to stop the user from easily carrying out an action without re-entering their credentials, you should not rely entirely on it.
You could store the username and password as hidden fields and include them with your main synchronous POST. This would allow you to check that the user entered valid credentials from the server.

Related

Allow a user to login/signin between actions

I have a requirement, where we allow a user to access a URL without logging in until a certain point.
For example:
OnlineBooking/Services. They can select the services, this populates the viewmodel and then brings up a confirm view OnlineBooking/Confirm allowing a user to add an email address etc. Which then generates a ViewModel.
My question is, how can I check the user exists, if it does. Redirect to the login view (Account Controller - Login Action), allow them to login, then redirect back to this action without losing the viewmodel in this action? This may not even be possible, if not how can I achieve this?
Thanks for any advice.
Example:
public async Task<IActionResult> Confirm(BookingViewModel bookingViewModel)
{
try
{
var matchedUser = await _userManager.FindByEmailAsync(bookingViewModel.Email);
if (matchedUser == null) //User does not have an existing account, so register them.
{
//This is fine
}
else
{
//Need to redirect to login, then back to here without losing the viewmodel
}
}
My question is, how can I check the user exists, if it does. Redirect to the login view (Account Controller - Login Action), allow them to login, then redirect back to this action without losing the viewmodel in this action
My personal preference would be to not even redirect the user. If you need them to login, popup a dialog asking them to login. Once the user is logged in (ajax), enable the button to continue..
Not sure if it's the best practice, but what about storing the View Model in the session before you redirect to the login page? Then at the beginning of the confirm action, check if that session variable exists.
Session variables can be set like this:
Session["MyViewModel"] = viewModel;
and retrieved like so:
MyViewModel viewModel = (MyViewModel)Session["MyViewModel"]

how to avoid using TempData

I'm new to asp.net mvc, so please bear with me.
I'm using TempData when I redirect to another Action and I don't want to "dirty" the URL with information. For example:
[AllowAnonymous]
public ActionResult ConfirmationEmailSent()
{
if (TempData["Username"] != null)
{
ViewBag.Username = TempData["Username"];
return View("ConfirmationEmailSent");
}
return View("Error");
}
So far so good, the user gets a simple and innocent looking web page with some message containing his username. But if the user hits the 'refresh' button then he gets the "Error" view, because TempDate is unavailable.
I would like to have the ability to redirect to action with information not presented in the querystring and also that if the user hits the refresh button then he gets just the same page.
Any ideas how to do this? (without session)
Thank you.
What you need is a way to have information persist across more than one request in a way that is associated with the browser. QueryString and Session are your two best options.
If the view is the result of a form post, you could make it a hidden input, but the user will get a prompt when they refresh (do you want to resubmit form?), but this is not good, as you should be doing a Post/Redirect/Get (PRG).
Erick
It sounds like cookies would do what you want. Then just delete them when you're done (or don't set an expiration date on them, in which case the browser will delete them for you when the session completes).

Reposting data through a generic method that is independent of form model in ASP MVC

This is the client's request: in some cases there are several forms that don't require authentication or it takes too long for a logged user to finish completing a form and the session expires. In theses cases he wants to retain the data when the user submits the form by serializing and storing it in a SQL table and then, after the user (re)logs in, he is redirected to the respective form and that is repopulated with the data retrieved from the database and deserialized.
The problem is that he wants all the logic of the storing, retrieving and resending the data in the authorization code block. I know how to do the storing, serialization, retrieving, deserialization of data and the user redirection to the respective page, but I don't know hoe to make it generic so that it works for every model on every form.
The client does not want any code for this task done in the form action method. For example:
[HttpPost]
[Authorize]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
post.CreatedBy = (Guid)Membership.GetUser().ProviderUserKey;
post.CreateTime = DateTime.Now;
repo.Add(post);
repo.Save();
return RedirectToAction("Index");
}
else
{
return View(post);
}
}
As you can see he wants to keep it as clean as possible. He had this suggestion:
The server receives a HTTP request in RAW text format. Using this text, it builds the objects (RequestContext, FormCollection collection, etc. etc.). So, you should be able to build in a hook and e.g. save the raw request. After succesfull login, this previous raw text of the HTTP request could be injected in the handling.
I don't really know how to do that or even if it is possible in MVC.
If someone can help I'll be extremely grateful.
Thanks,
ABTeam
The proper way to do this is to capture the user's progress in the database, and provide a mechanism for returning them to the next step in the process. This can be done with a ?step=n parameter in the URL. If the user gets logged off, they can log back in and be returned to the correct step in the process.
Your client's request for doing this in the authorization code block is not an appropriate use of functionality. That's not the purpose of the authorization block, and attempting to do business logic there, in the manner that the client describes, will almost certainly compromise security and result in unmaintainable code.
The authorization block is not the right place for it, but you may be able to do something fairly generic with action filters.
Load the saved data in OnActionExecuting. I'm not sure if you'll be able to get it passed to the action method as a parameter, but at the least you should be able to add it into ViewData so it can be used as a starting point for generating the model for the form page.
Not sure if the model will be available for saving before ActionExecuting, but if not the model as it exists after the action method runs should be an appropriate alternative.

Sharing data between Actions and application behavior

This is my application's current workflow for user registration.
register form ( GET /register )
submit the form ( POST /register )
after success, redirect route to "/registerSuccess"
in (GET /registerSuccess ) view, I want to show the message like "username has been registered successfully".
Controller Actions:
public ActionResult Register()
{
return View();
}
[HttpPost]
public ActionResult Register(string name, string password, string confirmPassword)
{
TempData["registered_user"] = name;
return RedirectToRoute("RegisterSuccess");
}
public ActionResult RegisterSuccess()
{
return View();
}
RegisterSuccess View
<h2><%: TempData["registered_user"] %> has been registered successfully.</h2>
It is working fine except the username is empty when user refresh the page (GET /registerSuccess).
Is it normal behavior in other applications? or Should I do something about it? :) (It is my pet project and requirements come from me :P )
update: Actually, there is one more step which is registered user required admin's approval. So, I can't let user log in after successful register. I'm currently using cookie-based FormsAuthentication to track logged in user.
I prefer to use PRG pattern rather than showing flash message in the submit form.
that is normal behaviour.. when the user 'refreshes' the page is requested directly not through your Register function so registered_user key is gone by then. When you do the initial registration you should use a session marker (say a cookie) to track a successfully logged in user.
Using cookies tutorial is a good place to start
If you were to use HTTP authentication (say Basic Auth) then the browser submits the username and password (in clear text for basic auth) with every request that sends out a 401 Unauthorized (see HTTP/1.0 Protocol)
Hmm...
Normally you should think of a SSO solution, that holds your User's authentication (token).
then you would just need to display the current authenticated user (as a completed registration normally logs in the user)
other solution would be, to add the user in the url, so that the success page has all parameters.
/registerSuccess/<username>
(and maybe check, that the logged in user is just registered, and is the same)
for your own fun project, i would do the second one. ist just plain simple.

How to create a view that is not accessible directly but can only be redirected to?

I'm currently working on the user registration in my project. After the registration is done I wish to show some confirmation to the user. I decided to create another view. That's fine.
Now, if after the registration I just return the view like:
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
return View ("ConfirmationView");
}
}
Everything is working as desired. No changed url in the title bar. But... If I click the refresh button, the browser will submit the data from the form again which I do not want.
Then I decided to create a separate action, but that means it will produce a new url in the address bar. I do not want the user to click refresh now because this view will not be able to sensibly display the confirmation information again. Is there any way to make an action not accessible directly? Or at least any way to determine whether it was called directly or by redirection? In the latter case I would just take the user away from that page to maybe the home page.
Any way to accomplish this?
So I found the solution myself.
One can use TempData to detect the repeated or external action calls.
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
TempData["RedirectCall"] = true;
return RedirectToAction ("Confirmation");
}
[AcceptVerbs (HttpVerbs.Get)]
public ActionResult Confirmation ()
{
if (TempData["RedirectCall"] == null)
return RedirectToAction ("StartPage", "Home");
return View ();
}
}
Nice and simple. :)
One way to solve your problem is to attach a guid or similar type of "random" data to a user session, and check for a valid session when the page is requested. If there is none, you redirect to a page saying that this url is not available at the moment, and that the user will soon be redirected (and then redirect to home after 5 seconds or so using js).
Roughly it would work like this:
When the user is registered, a session cookie is created with for example a GUID. The GUID is also stored in a database table, in which you have one column for the UserID primary key and one for the GUID. You also create an authentication cookie, thus logging the user on to your site.
When all datacalls etc are done, the user has been successfully registered and so on, you redirect to the confirmation page.
When the confirmation page is loaded, the user is automatically logged on (because you created the authentication cookie in step 1). You can then check for a row in the UserID-GUID table corresponding to the logged on user.
a) If there is such a row, you delete the row, and display the confirmation page with all the information.
b) If there is no such row, you display the error message and redirect. As you deleted the row when you showed the message the first time, the user will not be able to access the confirmation page again.
Note: If you use this approach (or some other that makes the confirmation page available only once) you should make sure that it is clearly stated on the confirmation page that the user won't be able to access that page again.
if(Request.UrlReferrer == null)
{
return RedirectToAction("Index");
}

Resources