Vulnerability in EPiServer.Forms

Try our conversational search powered by Generative AI!

Jafet Valdez
Sep 20, 2018
  6379
(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
Google Read Aloud Reload Problems

Inclusive web experiences greatly benefit from accessibility features such as Google Read Aloud. This tool, which converts text into speech, enable...

Luc Gosso (MVP) | Dec 4, 2023 | Syndicated blog

Google Read Aloud Reload Problems

Inclusive web experiences greatly benefit from accessibility features such as Google Read Aloud. This tool, which converts text into speech, enable...

Luc Gosso (MVP) | Dec 4, 2023 | Syndicated blog

Import Blobs and Databases to Integration Environments

In this blog, we are going to explore some new extensions to the Deployment API in DXP Cloud Services, specifically the ability to import databases...

Elias Lundmark | Dec 4, 2023

Join the Work Smarter Webinar: Working with the Power of Configured Commerce (B2B) Customer Segmentation December 7th

Join this webinar and learn about customer segmentation – how to best utilize it, how to use personalization to differentiate segmentation and how...

Karen McDougall | Dec 1, 2023

Getting Started with Optimizely SaaS Core and Next.js Integration: Creating Content Pages

The blog post discusses the creation of additional page types with Next.js and Optimizely SaaS Core. It provides a step-by-step guide on how to...

Francisco Quintanilla | Dec 1, 2023 | Syndicated blog

Stop Managing Humans in Your CMS

Too many times, a content management system becomes a people management system. Meaning, an organization uses the CMS to manage all the information...

Deane Barker | Nov 30, 2023