User isn't authenticated after successful login

Viewed 215

I created a ASP.NET MVC Application with the default auto-generated login/password template from Visual Studio.

I have some [Authorize] actions which correctly redirect me to the Login form if I'm not logged in. When I try to log with a wrong password I get an error as expected. Then when I try a correct username and password, I don't get any error but I still can't access the [Authorize] action. The application continuously redirect me at the Login form because the Return_url is my [Authorize] action. I added a displaying of the User.Identity.Name and it's always blank.

The first thing I did was to change the Login.cshtml like this :

@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <div style="text-align:center; margin-top:10px">
        <h3>Log In</h3>
    </div>
    <hr />
    <div style="margin-left:10px">
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    </div>
    <div style="margin-left:50px">
        <div class="form-group">
            @Html.LabelFor(m => m.User, new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.User, new { placeholder = "Username", @class = "form-control" })
                @Html.ValidationMessageFor(m => m.User, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                @Html.PasswordFor(m => m.Password, new { placeholder = "Password", @class = "form-control" })
                @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <div class="checkbox">
                    @Html.CheckBoxFor(m => m.RememberMe)
                    @Html.LabelFor(m => m.RememberMe)
                </div>
            </div>
        </div>
    </div>
    <div style="margin-left:25px">
        <div class="form-group">
            <div class="col-md-12">
                <input type="submit" value="Connect" class="btn btn-primary btn-lg btn-block" />
            </div>
        </div>
    </div>
}

And I also modified the LoginViewModel like this :

public class LoginViewModel
{
    [Required]
    [Display(Name = "User")]
    [DataType(DataType.Text)]
    public string User { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }
}

After that, I modified my ActionResult Login called on HttpPOST request :

// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    var result = SignInManager.CustomPasswordSignIn(model.User, model.Password, model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case CustomSignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case CustomSignInStatus.Admin:
            return RedirectToLocal(returnUrl);
        case CustomSignInStatus.LockedOut:
            return View("Lockout");
        case CustomSignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, model.RememberMe });
        case CustomSignInStatus.Satellite_fail:
            ModelState.AddModelError("", "Satellite failed.");
            return View(model);
        case CustomSignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Incorrect login or password.");
            return View(model);
    }

Because I needed more Login situations (Success, Admin, LockedOut, RequiresVerification, Satellite_fail, Failure) I created a CustomSignInStatus :

public enum CustomSignInStatus
    {
        //
        // Summary:
        //     Sign in was successful
        Success = 0,
        //
        // Summary:
        //     User is locked out
        LockedOut = 1,
        //
        // Summary:
        //     Sign in requires addition verification (i.e. two factor)
        RequiresVerification = 2,
        //
        // Summary:
        //     Sign in failed
        Failure = 3,
        //
        // Summary:
        //     Satellite sign in failed
        Satellite_fail = 4,
        //
        // Summary:
        //     Admin sign in
        Admin = 5
    }

Then I had to implement my own CustomPasswordSignIn to use my CustomSignInStatus like this :

public CustomSignInStatus CustomPasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout)
        {
            string hashpwd = Tools.HashData(password);

            if (userName == "admin" && hashpwd == ConfigurationManager.AppSettings["ADMIN"]) return CustomSignInStatus.Admin;

            using (IDal dal = new Dal())
            {
                int sat = dal.GetSatellite();

                if (sat == -1) return CustomSignInStatus.Satellite_fail;

                if (dal.CheckLogs(userName, hashpwd, sat)) return CustomSignInStatus.Success;
            }

            return CustomSignInStatus.Failure;
        }

This method hashes the input password, verifies if the user is the admin, then tries to verify the Satellite and the couple login/password.

At this point, the authentification seems to be working but no session is created, the user isn't authenticated and so doesn't have access to the [Authorize] actions. I think I have to add the session creation somewhere but I can't find what is missing in my code. How can I authenticate my user after a successful login ?

2 Answers

I found out how to solve my problem :

In the AccountController, at the Login Action and inside the switch from the result of the call of SignInManager.CustomPasswordSignIn, I edited the cases like that :

case CustomSignInStatus.Admin:
    adm = true;
    goto case CustomSignInStatus.Success;
case CustomSignInStatus.Success:

    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, model.User)
    };
    if (adm) claims.Add(new Claim(ClaimTypes.Role, "Administrator"));
    var id = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);

    var ctx = Request.GetOwinContext();
    var authenticationManager = ctx.Authentication;
    authenticationManager.SignIn(id);

    return RedirectToLocal(returnUrl);

Now my user is correctly identified and has access to the [Authorize] Actions.

if you are using FormsAuthentication then you need to use SetAuthCookie in FormsAuthentication when the user is validated like this

case CustomSignInStatus.Success:
FormsAuthentication.SetAuthCookie(model.User, model.RememberMe);
return RedirectToLocal(returnUrl);
Related