mardi 23 février 2016

MVC 5 - Identity - Unexpected Redirect to LogIn page

I was following up this great tutorial from Tims Persistance Ignorance with Asp.NET Identity and Patterns Everything was working fine until I found I've got an error when rendering a partial View with Ajax. In this case, App redirect to LogOff and then, of course, to Login View. I can't trace the problem, I couldn't find anything wrong

My Startup class looks like this

   public void ConfigureAuth(IAppBuilder app)
    {
        // Enable the application to use a cookie to store information for the signed in user
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login")
        });
        // Use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

This is the controller

       [HttpPost]
    public ActionResult IndexMedico(MedicoVM vm)
    {

        if (vm.ConsultorioSeleccionadoId != 0)
        {
           Session["ConsultorioId"] = vm.ConsultorioSeleccionadoId;
        }
        else
        {
            if (Session["ConsultorioId"] != null)
            {
                vm.ConsultorioSeleccionadoId = Int64.Parse(Session["ConsultorioId"].ToString());
            }

        }
        vm.Medico = CurrentUser.Medico;
        TurnosManager manager = new TurnosManager(unitofwork);
        manager.Manage(vm);
        vm.UsuarioEsMedico = CurrentUser.EsMedico;


        if(Request.IsAjaxRequest())
        {
            return PartialView("_PanelMedico", vm);
        }

        return View(vm);
    }

The problem ocurred after return PartialView. And the partial View is this

@model TurnosMedicos.Domain.Business.ViewModels.MedicoVM


<div id="panellistado" class="panel panel-primary">
<div class="panel panel-body">
    <div class="panel panel-title">
        <span id="dias" class="panel">
            @switch(@Model.DiaSeleccionadoId)
            {
                case 1:
                        <b>TURNOS PARA HOY</b>
                        break;
                case 2:
                        <b>TURNOS PARA MAÑANA</b>
                        break;
                case 3:
                        <b>TURNOS TODA LA SEMANA</b>
                        break;
            }
        </span>
    </div>

    <!-- Esto es para actualizar el Summary con la cantidad de turnos. -->
    <div id="PacientesHoy" name="PacientesHoy" style="visibility: hidden">
        @Model.CantidadPacientes
    </div>
    <!-- Fin de Div para actualizar el Summary-->

    <table class="table">
        <thead>
            <tr>
                <th>FECHA</th>
                <th>HORA</th>
                <th class="col-md-1">PACIENTE</th>
                <th class="col-md-1">RANKING</th>
                <th>ACCIONES</th>
                <th></th>
            </tr>
        </thead>



        @foreach (var t in Model.Medico.TurnosDisponibles)
        {
            <tr>
                <th>@t.Fecha.ToShortDateString()</th>
                <th>@t.Hora</th>
                <th class="col-md-1">
                    @if (t.Paciente != null && t.Estado == TurnosMedicos.Entities.EstadoTurno.Reservado)
                    {
                        <button type="button" class="btn btn-sm btn-info"
                                data-toggle="popover" title="Teléfono"
                                data-content="@t.Paciente.TelefonosRapido()">
                            <i class="fa fa-user">&nbsp;</i>   @t.Paciente.ToString() &nbsp;
                        </button>

                    }
                    else
                    {
                        <a onclick="ObtenerTurnoId(@t.Id);" href="#" data-toggle="modal" data-target="#NuevoTurnoModal"><span class="glyphicon glyphicon-plus-sign"></span> &nbsp; Agregar Paciente</a>
                    }

                </th>
                <th class="col-md-1">
                    @if(t.Paciente != null)
                    {
                        if (t.Paciente.Ranking == 100)
                        {
                            <i title="@t.Paciente.Ranking %" class="fa fa-thumbs-up fa-2x btn-success"></i>
                        }
                        if (t.Paciente.Ranking < 100 && t.Paciente.Ranking >= 75)
                        {
                            <i title="@t.Paciente.Ranking %"  class="fa fa-arrow-circle-left fa-2x btn-info"></i>
                        }
                        if (t.Paciente.Ranking < 75 && t.Paciente.Ranking >= 50)
                        {
                            <i title="@t.Paciente.Ranking %"  class="fa fa-arrow-circle-down fa-2x btn-warning"></i>
                        }
                        if (t.Paciente.Ranking < 50)
                        {
                            <i title="@t.Paciente.Ranking %" class="fa fa-thumbs-down fa-2x btn-danger"></i>
                        }
                    }
                </th>
                <th>
                    <a title="Anular este turno y ponerlo NO Disponible" class="fa fa-remove fa-2x btn btn-danger" onclick="AnularTurno(@t.Id);"></a>
                    @if (t.Paciente != null && t.Estado == TurnosMedicos.Entities.EstadoTurno.Reservado)
                    {
                        <a title="Paciente Ausente" class="fa fa-user-times fa-2x btn" onclick="PacienteAusente(@t.Id);"><span class="glyphicon glyphicon-"></span>&nbsp; </a>
                        <a title="Cancelar y Reprogramar" class="fa fa-random fa-2x btn" onclick="CancelarTurno(@t.Id);"><span class="glyphicon glyphicon-"></span>&nbsp; </a>
                    }
                </th>
            </tr>
        }
    </table>
</div>
<div id="NuevoTurnoModal" class="modal fade" role="form">
    <div class="modal-dialog">
        <!-- Contenido Modal -->
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title">NUEVO TURNO</h4>
            </div>

                <div class="modal-body">
                    <p>Ingrese el número de documento del paciente</p>
                    <input id="txtDNI" type="text" />
                </div>

            <div class="modal-footer">
                <button id="btnAsignar" type="button" onclick="TurnoExpress()" class="btn btn-default" data-dismiss="modal">Asignar</button>
            </div>
        </div>
    </div>
</div>

<div class="panel panel-body">
    <div class="panel panel-heading">TURNOS DISPONIBLES</div>

</div>

The ViewModel I am using is

namespace TurnosMedicos.Domain.Business.ViewModels
{
public class MedicoVM: BaseViewModel
{
    public MedicoVM()
    {
        Medico = new Medico();
    }

    public Medico Medico { get; set; }

    public int DiaSeleccionadoId { get; set; }
    public Int64 ConsultorioSeleccionadoId { get; set; }

    public int CantidadPacientes { get; set; }

}

public enum Dias
{
    Hoy = 1,
    Maniana = 2,
    Semana = 3,
    ProximaSemana = 4,
    Todo = 5
}

}

The Account Controller

public class AccountController : Controller
{
    private readonly UserManager<IdentityUser, Guid> _userManager;

    public AccountController(UserManager<IdentityUser, Guid> userManager)
    {
        _userManager = userManager;

    }


    // GET: /Account/Login
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        ViewBag.ReturnUrl = returnUrl;
        var vm = new LoginViewModel();
        return View(vm);

    }

    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            var user = await _userManager.FindAsync(model.Email, model.Password);
            if (user != null)
            {
                await SignInAsync(user, model.RememberMe);

                if (user.EsMedico)
                {
                    model.UsuarioEsMedico = true;
                }
                else
                {
                    model.UsuarioEsMedico = false;
                }
                return RedirectToLocal(returnUrl);
            }
            else
            {
                ModelState.AddModelError("", "Invalid username or password.");
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

    //
    // GET: /Account/Register
    [AllowAnonymous]
    public ActionResult Register()
    {
        var vm = new RegisterViewModel();
        vm.CanalesCaptacion.Clear();
        vm.CanalesCaptacion.Add("Búsqueda en Internet (google, yahoo, etc)");
        vm.CanalesCaptacion.Add("Me lo recomendo un amigo/colega");
        vm.CanalesCaptacion.Add("Recibí un Email de promoción");
        vm.CanalesCaptacion.Add("Lo ví en Facebook");
        vm.CanalesCaptacion.Add("Expomedical");
        vm.CanalesCaptacion.Add("Otras redes sociales (Linkedin, etc)");
        vm.CanalesCaptacion.Add("Twitter");
        vm.CanalesCaptacion.Add("Visitador medico");
        vm.CanalesCaptacion.Add("Por mi médico");
        vm.CanalesCaptacion.Add("Otro");
        return View(vm);
    }

    //
    // POST: /Account/Register
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            TurnosMedicos.UI.Web.Models.IdentityUser user = new TurnosMedicos.UI.Web.Models.IdentityUser();
            user.Email = model.Email;
            user.UserName = model.UserName;
            user.CanalSeleccionado = model.CanalSeleccionado;
            user.Nombres = model.Nombres;
            user.Apellido = model.Apellido;
            user.EsMedico = model.EsMedico;
            user.Matricula = model.Matricula;
            user.NroDocumento = model.NroDocumento;
            user.FechaNacimiento = model.FechaNacimiento;

            var result = await _userManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                RegistrarMedicoOPaciente(user);
                await SignInAsync(user, isPersistent: false);
                return RedirectToAction("Index", "Home");
            }
            else
            {
                AddErrors(result);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

    private void RegistrarMedicoOPaciente(IdentityUser user)
    {
        if(user.EsMedico)
        {
            user.Medico = new TurnosMedicos.Entities.Medico();
            user.Medico.Apellido = user.Apellido;
            user.Medico.Email = user.Email;
            user.Medico.Nombres = user.Nombres;
            user.Medico.Matricula = user.Matricula;
            user.Medico.FechaNacimiento = user.FechaNacimiento;
            user.Medico.NroDocumento = user.NroDocumento;
            user.Medico.PrecioTurno = ObtenerPrecioDefault();

        }

        //Todos somos pacientes

        user.Paciente = new TurnosMedicos.Entities.Paciente();
        user.Paciente.Apellido = user.Apellido;
        user.Paciente.Nombres = user.Nombres;
        user.Paciente.Email = user.Email;
        user.Paciente.FechaNacimiento = user.FechaNacimiento;
        user.Paciente.NroDocumento = user.NroDocumento;

        _userManager.Update(user);
    }

    private decimal ObtenerPrecioDefault()
    {
        decimal precio = 0;
        if(System.Configuration.ConfigurationManager.AppSettings["Precio"] != null)
        {
            precio = decimal.Parse(System.Configuration.ConfigurationManager.AppSettings["Precio"].ToString());
        }
        return precio;
    }

    //
    // POST: /Account/Disassociate
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Disassociate(string loginProvider, string providerKey)
    {
        ManageMessageId? message = null;
        IdentityResult result = await _userManager.RemoveLoginAsync(getGuid(User.Identity.GetUserId()), new UserLoginInfo(loginProvider, providerKey));
        if (result.Succeeded)
        {
            message = ManageMessageId.RemoveLoginSuccess;
        }
        else
        {
            message = ManageMessageId.Error;
        }
        return RedirectToAction("Manage", new { Message = message });
    }

    //
    // GET: /Account/Manage
    public ActionResult Manage(ManageMessageId? message)
    {
        ViewBag.StatusMessage =
            message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
            : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
            : message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
            : message == ManageMessageId.Error ? "An error has occurred."
            : "";
        ViewBag.HasLocalPassword = HasPassword();
        ViewBag.ReturnUrl = Url.Action("Manage");
        return View();
    }

    //
    // POST: /Account/Manage
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Manage(ManageUserViewModel model)
    {
        bool hasPassword = HasPassword();
        ViewBag.HasLocalPassword = hasPassword;
        ViewBag.ReturnUrl = Url.Action("Manage");
        if (hasPassword)
        {
            if (ModelState.IsValid)
            {
                IdentityResult result = await _userManager.ChangePasswordAsync(getGuid(User.Identity.GetUserId()), model.OldPassword, model.NewPassword);
                if (result.Succeeded)
                {
                    return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess });
                }
                else
                {
                    AddErrors(result);
                }
            }
        }
        else
        {
            // User does not have a password so remove any validation errors caused by a missing OldPassword field
            ModelState state = ModelState["OldPassword"];
            if (state != null)
            {
                state.Errors.Clear();
            }

            if (ModelState.IsValid)
            {
                IdentityResult result = await _userManager.AddPasswordAsync(getGuid(User.Identity.GetUserId()), model.NewPassword);
                if (result.Succeeded)
                {
                    return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess });
                }
                else
                {
                    AddErrors(result);
                }
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

    //
    // POST: /Account/ExternalLogin
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult ExternalLogin(string provider, string returnUrl)
    {
        // Request a redirect to the external login provider
        return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
    }

    //
    // GET: /Account/ExternalLoginCallback
    [AllowAnonymous]
    public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
        if (loginInfo == null)
        {
            return RedirectToAction("Login");
        }

        // Sign in the user with this external login provider if the user already has a login
        var user = await _userManager.FindAsync(loginInfo.Login);
        if (user != null)
        {
            await SignInAsync(user, isPersistent: false);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            // If the user does not have an account, then prompt the user to create an account
            ViewBag.ReturnUrl = returnUrl;
            ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
            return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = loginInfo.DefaultUserName });
        }
    }

    //
    // POST: /Account/LinkLogin
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LinkLogin(string provider)
    {
        // Request a redirect to the external login provider to link a login for the current user
        return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account"), User.Identity.GetUserId());
    }

    //
    // GET: /Account/LinkLoginCallback
    public async Task<ActionResult> LinkLoginCallback()
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
        if (loginInfo == null)
        {
            return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
        }
        var result = await _userManager.AddLoginAsync(getGuid(User.Identity.GetUserId()), loginInfo.Login);
        if (result.Succeeded)
        {
            return RedirectToAction("Manage");
        }
        return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
    }

    //
    // POST: /Account/ExternalLoginConfirmation
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
    {
        if (User.Identity.IsAuthenticated)
        {
            return RedirectToAction("Manage");
        }

        if (ModelState.IsValid)
        {
            // Get the information about the user from the external login provider
            var info = await AuthenticationManager.GetExternalLoginInfoAsync();
            if (info == null)
            {
                return View("ExternalLoginFailure");
            }
            var user = new IdentityUser() { UserName = model.UserName };
            var result = await _userManager.CreateAsync(user);
            if (result.Succeeded)
            {
                result = await _userManager.AddLoginAsync(user.Id, info.Login);
                if (result.Succeeded)
                {
                    await SignInAsync(user, isPersistent: false);
                    return RedirectToLocal(returnUrl);
                }
            }
            AddErrors(result);
        }

        ViewBag.ReturnUrl = returnUrl;
        return View(model);
    }

    //
    // POST: /Account/LogOff
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LogOff()
    {
        AuthenticationManager.SignOut();
        Session["Usuario"] = null;
        return RedirectToAction("Index", "Home");
    }

    //
    // GET: /Account/ExternalLoginFailure
    [AllowAnonymous]
    public ActionResult ExternalLoginFailure()
    {
        var vm = new BaseViewModel();
        return View(vm);
    }

    [ChildActionOnly]
    public ActionResult RemoveAccountList()
    {
        var linkedAccounts = _userManager.GetLogins(getGuid(User.Identity.GetUserId()));
        ViewBag.ShowRemoveButton = HasPassword() || linkedAccounts.Count > 1;
        return (ActionResult)PartialView("_RemoveAccountPartial", linkedAccounts);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && _userManager != null)
        {
            _userManager.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Helpers
    // Used for XSRF protection when adding external logins
    private const string XsrfKey = "XsrfId";

    private IAuthenticationManager AuthenticationManager
    {
        get
        {
            return HttpContext.GetOwinContext().Authentication;
        }
    }

    private async Task SignInAsync(IdentityUser user, bool isPersistent)
    {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
        var identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
        AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
    }

    private void AddErrors(IdentityResult result)
    {
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError("", error);
        }
    }

    private bool HasPassword()
    {
        var user = _userManager.FindById(getGuid(User.Identity.GetUserId()));
        if (user != null)
        {
            return user.PasswordHash != null;
        }
        return false;
    }

    public enum ManageMessageId
    {
        ChangePasswordSuccess,
        SetPasswordSuccess,
        RemoveLoginSuccess,
        Error
    }

    private ActionResult RedirectToLocal(string returnUrl)
    {
        if (Url.IsLocalUrl(returnUrl))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return RedirectToAction("Index", "Home");
        }
    }

    private class ChallengeResult : HttpUnauthorizedResult
    {
        public ChallengeResult(string provider, string redirectUri)
            : this(provider, redirectUri, null)
        {
        }

        public ChallengeResult(string provider, string redirectUri, string userId)
        {
            LoginProvider = provider;
            RedirectUri = redirectUri;
            UserId = userId;
        }

        public string LoginProvider { get; set; }
        public string RedirectUri { get; set; }
        public string UserId { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
            if (UserId != null)
            {
                properties.Dictionary[XsrfKey] = UserId;
            }
            context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
        }
    }

    private Guid getGuid(string value)
    {
        var result = default(Guid);
        Guid.TryParse(value, out result);
        return result;
    }
    #endregion
}

And here I use a CurrentUser, that is defined in Session variable, created in HomeController

        Entities.UserInfo.User _user = null;


    public Entities.UserInfo.User CurrentUser
    {
        get
        {
            if (Session["Usuario"] == null)
            {
                _user = repositoryUser.FindByUserName(User.Identity.GetUserName());
                Session["Usuario"] = _user;
                return _user;
            }
            return (Entities.UserInfo.User)Session["Usuario"];

        }
    }

Just before calling the logOff action, I can check HttpContext.User.Identity and the property "IsAuthenticated" is True and the name propery has correct value.

Any Idea?

Aucun commentaire:

Enregistrer un commentaire