Search code examples
c#asp.net-mvcasp.net-identityowinhttp-redirect

Owin: OnApplyRedirect called several times and Create incorrect RedirectUri


I use CookieAuthentication in my application with owin and set redirect url on OnApplyRedirect like the following code:

 app.UseCookieAuthentication(new CookieAuthenticationOptions
 {
     ExpireTimeSpan = TimeSpan.FromDays(30),
     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
     LoginPath = new PathString("/account/sign-in"),
     //LogoutPath = new PathString("/account/log-out"),
     ReturnUrlParameter = "returnTo",
     CookieName = "BIR",
     Provider = new CookieAuthenticationProvider()
     {
         OnValidateIdentity = SmObjectFactory.Container.GetInstance<IAppUserManager>().OnValidateIdentity(),
         OnApplyRedirect = c =>
         {
             if (!c.Request.IsAjaxCall())
             {
                 c.Response.Redirect(c.RedirectUri);
             }
         }
     }
 });

my problem is c.RedirectUri value, i set break point and trace my code after do this i understand that OnApplyRedirect called serveral time.

In the First call RedirectUri is:

http://localhost:7537/account/sign-in?returnTo=%2Fadmin-panel

In the Second call RedirectUri is:

http://localhost:7537/account/sign-in?returnTo=%2Faccount%2Fsign-in%3FreturnTo%3D%252Fadmin-panel

And more ...

In pre call old url add in new url. I try to solve this problem, search and research in another and current site but do not find an answer, why OnApplyRedirect call several times? Configuration method in Startup.cs class Only once is called. other details :


Solution

  • Using the provided auth into, I was able to reproduce the problem by commenting out the [AllowAnonymous] on the SignIn action of a simple auto generated OWIN project.

    So chances are that your situation is due to the sign in action requiring authentication while being used for anonymous access, thus causing an infinite loop of redirects which will fail.

    In the following controller where authorization is needed to access its admin panel would cause the issue you are experiencing.

    [Authorize]
    [RoutePrefix("account")]
    public class AccountController : Controller {
        [Route("sign-in")]        
        public ActionResult Signin(string returnTo) {            
            ViewBag.ReturnTo = returnTo;
            return View(new LoginViewModel { RememberMe = true });
        }    
    
        [Route("admin-panel")]
        public Action AdminPanel() {
            return View();
        }
    }
    

    All sign in, account verification and password recovery actions should be tagged with [AllowAnonymous] attribute to allow anonymous access if they are within [Authorize] controllers

    [Authorize]
    [RoutePrefix("account")]
    public class AccountController : Controller {
        [AllowAnonymous]
        [Route("sign-in")]        
        public ActionResult Signin(string returnTo) {            
            ViewBag.ReturnTo= returnTo;
            return View(new LoginViewModel { RememberMe = true });
        }
    
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        [Route("sign-in")]   
        public async Task<ActionResult> Signin(LoginViewModel model, string returnTo) {
            //...
        }
    
        [Route("admin-panel")]
        public Action AdminPanel() {
            return View();
        }
    }
    

    or should be moved to controllers that are not tagged with [Authorize] attribute.

    [Authorize]
    public class AccountController : Controller {
        [Route("account/admin-panel")]
        public Action AdminPanel() {
            return View();
        }
    }
    
    public class AuthenticationController : Controller {
        [Route("account/sign-in")]        
        public ActionResult Signin(string returnTo) {            
            ViewBag.ReturnTo= returnTo;
            return View(new LoginViewModel { RememberMe = true });
        }
    
        [HttpPost]
        [ValidateAntiForgeryToken]
        [Route("account/sign-in")]   
        public async Task<ActionResult> Signin(LoginViewModel model, string returnTo) {
            //...
        }
    }