David
David

Reputation: 1774

Authentication cookie not set in browser using Owin in MVC 5

This is something through which I just struggled for ages, and I now have the answer so I'll ask and answer this one.

The problem is due to me setting up authentication on a new MVC 5 site, which is to integrate with an existing, very custom membership database.

I basically need to replicate the FormsAuthentication functionality to get a user signed in for the session. The system also has an auto-login feature that uses a key/value in the url, in addition to the normal username/password approach.

I've split the controller code up as follows:

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult Login(LoginViewModel model, string returnUrl)
    {                           
        if (ModelState.IsValid)
        {
            var loginResult = _authenticationService.Login(model.Email, model.Password, returnUrl);

            if (loginResult.Succeeded)
            {
                _loginFacade.DoLogin(loginResult, model.RememberMe);
            }
            else
            {
                ModelState.AddModelError("", "Invalid username or password.");
            }
        }

        return View(model);
    }

    [AllowAnonymous]
    public ActionResult Autologin(string key, string value)
    {

        if (ModelState.IsValid)
        {
            var loginResult = _authenticationService.AutoLogin(key, value);

            if (loginResult.Succeeded)
            {
                _loginFacade.DoLogin(loginResult, false);
            }
            else
            {
                ModelState.AddModelError("", "error message goes here");
            }
        }

        return View("Login");
    }

The call to _authenticationService.Login is fine. There's no problem there.

In the call to DoLogin, I was doing various things to create the authentication properties, to then be able to sign the new user in, but no matter what I did, the user would not be signed in in the browser:

public void DoLogin(LoginResult<Agent> loginResult, bool rememberMe)
    {
        string redirectUrl;
        if (loginResult.Succeeded)
        {
            var claimsIdentity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, loginResult.User.Email),     
                new Claim(ClaimTypes.Role, "User"),
                new Claim(ClaimTypes.NameIdentifier, loginResult.User.Id.ToString()),
                new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", loginResult.User.Id.ToString())
            }, DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);

            var authProperties = new AuthenticationProperties
            {
                IsPersistent = rememberMe,
            };
            if (rememberMe)
            {
                authProperties.ExpiresUtc = DateTime.UtcNow.AddMonths(1);
            }
            authProperties.Dictionary.Add("Id", loginResult.User.Id.ToString());

            var authentication = _context.GetOwinContext().Authentication;
            authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
            authentication.SignIn(authProperties, claimsIdentity);

            DoStuff();
            redirectUrl = GetRedirectUrl();
        }
        else
        {
            redirectUrl = GetOtherRedirectUrl();
        }
        _context.Response.Redirect(redirectUrl, true);
    }

I'll post the solution as an answer

Upvotes: 5

Views: 4391

Answers (1)

David
David

Reputation: 1774

The initial clue to the answer occurred when I skipped over the Response.Redirect line. The code worked in that situation.

Basically,

        _context.Response.Redirect(redirectUrl, true);

needs to be

        _context.Response.Redirect(redirectUrl, false);

Unlike FormsAuthentication, which actually sets the cookies when you write your authentication code, AuthenticationManager creates an AuthenticationResponseGrant or AuthenticationResponseRevoke object.

So the authentication cookies are not set at the time of calling authentication.SignIn(authProperties, claimsIdentity); They're called later, so ending the response using Response.Redirect will kill the thread before the cookies get added onto the response object. By changing the endResponse parameter to false, the cookies get applied to the response object as expected.

Upvotes: 9

Related Questions