Don't miss out Virtual Happy Hour this Friday (April 26).

Try our conversational search powered by Generative AI!

Jafet Valdez
Sep 20, 2018
  6706
(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
Solving the mystery of high memory usage

Sometimes, my work is easy, the problem could be resolved with one look (when Iā€™m lucky enough to look at where it needs to be looked, just like th...

Quan Mai | Apr 22, 2024 | Syndicated blog

Search & Navigation reporting improvements

From version 16.1.0 there are some updates on the statistics pages: Add pagination to search phrase list Allows choosing a custom date range to get...

Phong | Apr 22, 2024

Optimizely and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog

Azure AI Language ā€“ Abstractive Summarisation in Optimizely CMS

In this article, I show how the abstraction summarisation feature provided by the Azure AI Language platform, can be used within Optimizely CMS to...

Anil Patel | Apr 18, 2024 | Syndicated blog

Fix your Search & Navigation (Find) indexing job, please

Once upon a time, a colleague asked me to look into a customer database with weird spikes in database log usage. (You might start to wonder why I a...

Quan Mai | Apr 17, 2024 | Syndicated blog