SaaS CMS has officially launched! Learn more now.

Jafet Valdez
Sep 20, 2018
  6858
(2 votes)

Curing your Redirect woes when using ASP.NET Identity and UISignInManager.SignIn()

Hello World.

In this post I'll show you how to modify UISignInManager so that it does not fully rely on the ReturnUrl query parameter for redirects, enabling us to use whatever redirection logic we may desire and/or require!

The context

I recently ran into a thread here on World where a fellow developer had some issues with users being redirected to the sites' root page (”/”) after signing them in through a call to UISignInManager.SignIn().

The intent was to redirect the users to a specific Url after signing them in. Sounds reasonable.

But the issue with redirecting to another url after calling SignIn() is that the SignIn method already sets a redirect location on the response, which means that you can try to redirect as much as you want afterwards. The user will still be redirected to whatever the SignIn() method decided to redirect to.

And what does the SignIn()-method decide to redirect to? The ReturnUrl in the query parameters (for example ”/Login?ReturnUrl=/ALockedPage”).

This is fine in most cases where you, for example, get redirected to the login page for trying to access a page where users need to be authenticated. But what happens if you don’t have a ReturnUrl query paremeter set? Well it will simply redirect to ”/” – in other words, the sites’ root page. This may not be what you want in all cases, especially if you have a custom login page with custom redirection logic.

Note: This SignIn() redirect also overrides redirects that you do in the OnResponseSignIn Action in your OWIN configuration (a.k.a. Startup.cs).

So how do we solve this?

Well, we create our own UISignInManager of course!

In this approach we'll modify the SignIn method so that it doesn't automatically redirect to sites' root page if we do not have a ReturnUrl set.

    public class MyApplicationUISignInManager<TUser> : ApplicationUISignInManager<TUser> where TUser : IdentityUser, IUIUser, new()
    {

        protected ApplicationSignInManager<TUser> _signInManager;

        public MyApplicationUISignInManager(ServiceAccessor<ApplicationSignInManager<TUser>> signInManager) : base(signInManager)
        {
            _signInManager = signInManager();
        }

        public override bool SignIn(string providerName, string userName, string password)
        {

            if (HasReturnUrl()) // Use the default implementation if we have a ReturnUrl set
                return base.SignIn(providerName, userName, password);

            return _signInManager.SignIn(userName, password, null);
        }

        private bool HasReturnUrl()
        {
            return HttpContext.Current?.Request.QueryString?.Get("ReturnUrl") != null;   
        }


        /// <summary>
        /// Creates an instance of MyApplicationUISignInManager. Mainly for setting up the IoC Container.
        /// </summary>
        /// <param name="options"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public new static UISignInManager Create(IdentityFactoryOptions<UISignInManager> options, IOwinContext context)
        {
            return new MyApplicationUISignInManager<TUser>(context.Get<ApplicationSignInManager<TUser>>);
        }
    }

In this case, we only want to modify what happens if we do not have a ReturnUrl. So if we have a query parameter named ReturnUrl, we just use the standard UISignInManager code to handle that case. Otherwise we sign the user in ourselves without specifying a return url.

Now we need to setup our IoC to use our new UISignInManager.

This can be done in your Startup.cs file, just after you call the built in AddCmsAspNetIdentity() where all the default Managers and Providers are setup.

    // Example Startup.cs class
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.AddCmsAspNetIdentity<ApplicationUser>();

            app.CreatePerOwinContext<UISignInManager>(MyApplicationUISignInManager<ApplicationUser>.Create); // <-- Add this line after calling app.AddCmsAspNetIdentity

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Util/Login.aspx"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity =
                        SecurityStampValidator
                            .OnValidateIdentity<ApplicationUserManager<User>, User>(
                                TimeSpan.FromMinutes(30),
                                (manager, user) => manager.GenerateUserIdentityAsync(user)),
                    OnApplyRedirect = context => context.Response.Redirect(context.RedirectUri),
                    OnResponseSignOut = context => context.Response.Redirect("/ByeWorld"),
                    OnResponseSignIn = context => context.Response.Redirect("/HelloWorld") // This now works too! Yay!
                }
            });
        }
    }

Note: In my example I call a static Create method that lives in my own implementation of UISignInManager, this is so that it’s done in a similar fashion to how the others defaults are setup in case you set them up manually instead of calling AddCmsAspNetIdentity().

Now you’re set and can start redirecting to whatever you want, either through the OnResponseSignIn action or manually after signing in users through UISignInManager.SignIn()! 😊

Sep 20, 2018

Comments

Please login to comment.
Latest blogs
Optimizely SaaS CMS Concepts and Terminologies

Whether you're a new user of Optimizely CMS or a veteran who have been through the evolution of it, the SaaS CMS is bringing some new concepts and...

Patrick Lam | Jul 15, 2024

How to have a link plugin with extra link id attribute in TinyMce

Introduce Optimizely CMS Editing is using TinyMce for editing rich-text content. We need to use this control a lot in CMS site for kind of WYSWYG...

Binh Nguyen Thi | Jul 13, 2024

Create your first demo site with Optimizely SaaS/Visual Builder

Hello everyone, We are very excited about the launch of our SaaS CMS and the new Visual Builder that comes with it. Since it is the first time you'...

Patrick Lam | Jul 11, 2024

Integrate a CMP workflow step with CMS

As you might know Optimizely has an integration where you can create and edit pages in the CMS directly from the CMP. One of the benefits of this i...

Marcus Hoffmann | Jul 10, 2024