7

How do I force a user to reauthenticate before performing an Action in MVC?

We're using Windows authentication. There are some actions that we want to make sure are being performed by the user (and prevent other users from performing those actions if the user forgot to lock their workstation).

Ideally I'd just be able to write an attribute that extends Authorize:

namespace AuthTest.Controllers
{
    [Authorize(Roles="MyApp")]
    public class HomeController : Controller
    {    
        public ActionResult Index()
        {
             // A regular action
            return View();
        } 

        [ReAuthenticate]
        public ActionResult CriticalAction()
        {
            // Do something important
            return View();
        }
     }
}

It appears that I can force the user to re-enter their credentials by having the custom ReAuthenticate attribute issue a HTTP 401 response in the AuthorizeCore method. However, this required some trickery since Html.ActionLink was sending two requests:

protected override bool AuthorizeCore(HttpContextBase httpContext)
{
    bool ok = base.AuthorizeCore(httpContext);
    if (!ok) return false;

    if (httpContext.Session["ReAuthCnt"] == null)
    {
        httpContext.Session["ReAuthCnt"] = 1;
        return false;
    }
    else if ((int) httpContext.Session["ReAuthCnt"] < 2)
    {
        httpContext.Session["ReAuthCnt"] = (int)httpContext.Session["ReAuthCnt"] + 1;
        return false;
    }
    else
    {
        httpContext.Session["ReAuthCnt"]  = 0;
        return true;
    }
}

Is there a better way to accomplish the re-authorization?

  • What do you mean by reauthenticate? – Rowan Freeman Sep 25 '13 at 00:39
  • Why would you want to? If the user is authenticated then they are, well, authenticated. You could consider OAuth if you can't maintain an authentication. Perhaps if you described your issue a little more we could suggest what to do. – griegs Sep 25 '13 at 00:50
  • I've updated the question with more details about the scenario and what I tried. – user2813199 Sep 25 '13 at 07:48
  • 1
    Hmm i understand what he's trying to achieve, i'm trying to achieve a similar thing. I need to get the user to re-input their network password and authenticate it. The reason for this is i want to digitally sign a PDF with that users authorisation. As mentioned above, pass through authentication is not good enough for certain authorities to accept digital signatures as people often leave workstations unlocked so re-authentication is required for digital signatures. I haven't attempted to solve this yet, i was just looking for some direction, ill post back if i do. –  Nov 11 '13 at 20:58
  • How about authenticating the user again in the web app using ADFS or some federated authentication so that you can use the same credentials despite demanding a re-authentication. – Saravanan Dec 24 '13 at 16:25
  • check out these two: http://stackoverflow.com/questions/2898965/need-users-to-re-authenticate-with-ntlm & http://stackoverflow.com/questions/8773089/require-re-authentication-for-certain-actions – imjosh Dec 31 '13 at 19:01
  • Are you saying that even if the user has already provided their credentials that if they perform a certain action you want them to have to re-enter their credentials? Perhaps another way to look at the problem is to say that for this action if the time at which the user last authenticated is not within the last few seconds, then the action will send a 401 response; otherwise it will execute normally. – Dr. Wily's Apprentice Dec 31 '13 at 21:20
  • You may want to consider the fact that some people will allow their browser to store their credentials for them. You'll probably want to check what effect that has on your implementation. – Dr. Wily's Apprentice Dec 31 '13 at 21:22

4 Answers4

0

If the user is performing a POST, could you add a username and password field to that form and then validate the credentials against Active Directory either in the controller or using an ActionFilter?

mmilleruva
  • 2,110
  • 18
  • 20
0

If you are allowed by the business to actually use a form to reauthenticate them (in other words have a page they type a username and password into) then you can do the following.

ReauthorizeAttribute

// custom page where user does type user/pass
private string revalidateLoginUrl = "/account/reauth";
private bool? hasReauthenticated = false;

protected override bool AuthorizeCore(HttpContextBase httpContext)
{
  var authUrl = HttpContext.Request.Url;
  hasReauthenticated = httpContext.Session[authUrl] as bool?

  // could be just (hasReauthenticated)
  // this look a bit more readable
  return (hasReauthenticated == true);
}

public override void OnAuthorization(AuthorizationContext filterContext) 
{
  if (filterContext == null) 
  {
    throw new ArgumentNullException("filterContext");
  }

  var isAuthorized = AuthorizeCore(filterContext.HttpContext);

  var authUrl = filterContext.HttpContext.Request.Url;
  filterContext.HttpContext.Session[authUrl] = false;

  if (!isAuthorized) 
  {
    HandleUnauthorizedRequest(filterContext);
  }
}

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
  // something like this
  var fullUrl = validateLoginurl + "?returnUrl=" 
    + HttpUtility.UrlEncode(revalidaetLoginUrl);

  filterContext.HttpContext.Response.Redirect(validateLoginUrl);
}

ReauthModel

public class ReauthModel
{
  public string Username { get; set; }
  public string Password { get; set; }
  public string ReturnUrl { get; set; }
}

AccoountController.cs (Validate a username and password against Active Directory?)

using System.DirectoryServices.AccountManagement;

public ActionResult Reauth(string returnUrl)
{
  var model = new ReauthModel();
  model.ReturnUrl = returnUrl;

  return View(model);
}

[ValidateAntiForgeryToken]
public ActionResult Reauth(ReauthModel model)
{
  using(PrincipalContext pc = new 
    PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
  {
    // validate the credentials
    bool isValid = pc.ValidateCredentials("myuser", "mypassword");
    if (isValid)
    {
      Session[model.ReturnUrl] = true;
      return RedirectTolocal(model.ReturnUrl);
    }
  }

  // not authenticated
  return RedirectToAction("?");

  //or
  model.Username = string.Empty;
  model.Passsword = string.Empty;

  return View(model);
}

I think you can imagine based on the ReauthModel what the view would look like.

Note: this should be used in addition to any other authorizeattribute's you are using, not instead of. Since the user is typing the username and password on a website, this needs to use SSL (even if it is internal).

Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
0

I would approach this a different way. It sounds like what you REALLY want is a temporary credential elevation, similar to UAC or Sudo, but on your website. You probably want that if a certain amount of time has passed that the user has to again enter their credentials to access these functions.

The approach I would take is to create a custom IPrincipal that allows you to temporarily add a specific role to the user, and expire that role after a period of time. This would not be tied to the users assigned roles as stored in the database, other than to perhaps say only users with a specific role can be elevated.

This way, you can simply perform a password check, add the role temporarily to the users role list, and then the standard Authorization attribute will let them in.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
-2

you can get idea from here i am doing role base Authorization here

[Authorize(Roles = "admin,superadmin")]//Controler Authorization
        public class ControlController : Controller
        {

            [Authorize(Roles = "superadmin")]//action Authorization
            public ActionResult youraction()
            {
                return View();
            }
      }
Jatinder Sharma
  • 277
  • 1
  • 3
  • 14