6

I am working on an ASP.Net Mvc 3 application using FormsAuthentication with a custom MembershipProvider (so I do have some control over what the provider returns).

The requirements mandate a 2-step authentication process (username and password followed by secret question). A user should not be able to access any of the "secure" sections of the site without passing both steps. Please don't mention whether this is multi-factor security or not, I already know.

Please provide a recommendation on how to best accomplish this task.

Here are some considerations:

  • I am allowed (architecturally) to use session - would prefer not to.
  • I would prefer to use the out-of the box [Authorize] ActionFilter for Controllers providing secure content.
  • The people in charge would like for the url for the 2 steps to be the same: i.e. www.contoso.com/login/. In my attempts at least, this has caused some minor-but-not-insignificant issues when users enter an incorrect answer in the second step (they are not officially logged in, but I need to ensure that I am still working against the half-authenticated user's secret question/answer).

Thanks.

2 Answers 2

4

Use a custom View Model in conjunction with hidden form fields. Just make sure it's all done over https.

ViewModel

public LoginForm
{
    public string UserName { get; set; }
    public string Password { get; set; }

    public int SecretQuestionId { get; set; }
    public string SecretQuestion { get; set; }
    public string SecretQuestionAnswer { get; set; }
}

Action Methods

public ActionResult Login()
{
    var form = new LoginForm();
    return View(form);
}

[HttpPost]
public ActionResult Login(LoginForm form)
{
    if (form.SecretQuestionId == 0)
    {
        //This means that they've posted the first half - Username and Password
        var user = AccountRepository.GetUser(form.UserName, form.Password);
        if (user != null)
        {
            //Get a new secret question
            var secretQuestion = AccountRepository.GetRandomSecretQuestion(user.Id);
            form.SecretQuestionId = secretQuestion.Id;
            form.SecretQuestion = secretQuestion.QuestionText;
        }
    }
    else
    {
        //This means that they've posted from the second half - Secret Question
        //Re-authenticate with the hidden field values
        var user = AccountRepository.GetUser(form.UserName, form.Password);
        if (user != null)
        {
            if (AccountService.CheckSecretQuestion(form.SecretQuestionId, form.SecretQuestionAnswer))
            {
                //This means they should be authenticated and logged in
                //Do a redirect here (after logging them in)
            }
        }
    }

    return View(form);
} 

View

<form>
    @if (Model.SecretQuestionId == 0) {
        //Display input for @Model.UserName
        //Display input for @Model.Password
    }
    else {
        //Display hidden input for @Model.UserName
        //Display hidden input for @Model.Password
        //Display hidden input for @Model.SecretQuestionId
        //Display @Model.SecretQuestion as text
        //Display input for @Model.SecretQuestionAnswer
    }
</form>

If you're not happy with sending the username and password back to the view in hidden fields to re-authenticate and make sure they're not cheating... you could create a HMAC or something like that to test.

Btw, this question seems like a few questions rolled into one... so just answered how to do 2-step authentication with one view / action method.

Sign up to request clarification or add additional context in comments.

1 Comment

sorry if it was more than 1 question, I just wanted to provide the greatest detail around the constraints. This definitely solves my issue, thanks!
0

I would probably do something where the first step makes them enter a username and password. Check it, if its good, move them along to an authorize flagged view that asks them to put in the answer to the question. If they fail that, sign them out, boot them out, whatever. I don't think this is possible in one view, unless you render a partial view and if they leave without finishing the authentication process, you sign them out and clear their cookie.

----EDIT----- On second thought, you could do a partial view, just dont formsauth sign them in until they complete the second part of the partial view. Some psuedo code:

public ActionResult Login(){
  get username and password off the view
  if its valid
     render a partial view that asks for the secret answer
     if thats valid
       forms auth login
     else
       try again, get booted, or whatever
   else
      get booted, try again, whatever
}

2 Comments

I understand this is pseudocode, but it is not possible for me to render a partialview and then check if it's valid in the same flow. Also, does the username and password neccessarily exist on the form? (yes if its step 1 but what about on step 2?). Finally, I understand that the method declares a return of ActionResult but what does it mean for this to return both a ViewResult representing the username/password and a PartialResult for the security question?
good point - look here: stackoverflow.com/questions/5437745/… you might be able to leverage that

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.