MVC 5 How to define Owin LoginPath with localized routes

23,032

Solution 1

I exactly had the same problem and figured out a way to overcome this limitation.

In the CookieAuthenticationOptions options, there is a "Provider" property which is initialized with the CookieAuthenticationProvider. This implements a method called ApplyRedirect and a delegate OnApplyRedirect. My first idea was to overwrite this ApplyRedirect and implement the required logic to handle localized routes. But unfortunately it can't be overriden. Passing my logic to OnApplyRedirect causes to overwrite the default behavior. You theoretically can grab the source of this behavior, copy it into your project and modify it to your needs, but this is obviously not a good practice. First, I decided to make a wrapper around the CookieAuthenticationProvider with two extension points using delegates and preserving the default behavior except of the url that is used - or easier, work around the wrapper (thx to lafi).

Then in the auth configuration I added my custom logic to the provider:

public void ConfigureAuth(IAppBuilder app)
{
    UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);

    CookieAuthenticationProvider provider = new CookieAuthenticationProvider();

    var originalHandler = provider.OnApplyRedirect;

    //Our logic to dynamically modify the path (maybe needs some fine tuning)
    provider.OnApplyRedirect = context =>
    {
        var mvcContext = new HttpContextWrapper(HttpContext.Current);
        var routeData = RouteTable.Routes.GetRouteData(mvcContext);

        //Get the current language  
        RouteValueDictionary routeValues = new RouteValueDictionary();
        routeValues.Add("lang", routeData.Values["lang"]);

        //Reuse the RetrunUrl
        Uri uri = new Uri(context.RedirectUri);
        string returnUrl = HttpUtility.ParseQueryString(uri.Query)[context.Options.ReturnUrlParameter];
        routeValues.Add(context.Options.ReturnUrlParameter, returnUrl);

        //Overwrite the redirection uri
        context.RedirectUri = url.Action("login", "account", routeValues);
        originalHandler.Invoke(context);
    };

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString(url.Action("login", "account")),
        //Set the Provider
        Provider = provider
    });
}

See also this code:

Hope it fits your needs.

UPDATE: For less confusion, I updated my answer to use @Lafis enhancement, not using a wrapper class to apply the extended behavior. Please also give credit to @Lafis when upvoting.

Solution 2

To enhance @martinoss answer, you may reach the same result without implementing a wrapper. Just copy the original handler, assign a new one that implements your redirection logic to modify context.RedirectionUri, and at the end call the original handler.

CookieAuthenticationProvider provider = new CookieAuthenticationProvider();

var originalHandler = provider.OnApplyRedirect;
provider.OnApplyRedirect = context =>
{
    //insert your logic here to generate the redirection URI
    string NewURI = "....";
    //Overwrite the redirection uri
    context.RedirectUri = NewURI;
    originalHandler.Invoke(context);
};

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
   AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
   LoginPath = new PathString(url.Action("Login", "Account")),
   Provider = provider
});

Solution 3

How about this:

var cao = new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider { OnApplyRedirect = ApplyRedirect }
        };
app.UseCookieAuthentication(cao);

and

  private static void ApplyRedirect(CookieApplyRedirectContext context)
    {

        UrlHelper _url = new UrlHelper(HttpContext.Current.Request.RequestContext);
        String actionUri = _url.Action("Login", "Account", new { });
        context.Response.Redirect(actionUri);
    }

Solution 4

Without taking too much of responsibility on the url format etc, you can do something like the following

public static void Configuration(IAppBuilder app)
{
    UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString(url.Action("LogOn", "Account", new { area = "Account" })),
        Provider = new CookieAuthenticationProvider
        {
            OnApplyRedirect = context => context.Response.Redirect(context.RedirectUri.Replace(CultureHelper.GetDefaultCulture(), Thread.CurrentUiCulture.Name))
        }
    });
}

Solution 5

I improved Sentinel answer, to keep return url:

private static void ApplyRedirect(CookieApplyRedirectContext context)
        {
            //use this way to keep return url
            var loginUrl = context.RedirectUri.Insert(
                context.RedirectUri.IndexOf("/Account/Login"),
                "/" + CultureHelper.GetCurrentCulture());

            context.Response.Redirect(loginUrl);
        }
Share:
23,032

Related videos on Youtube

s0nica
Author by

s0nica

Italian expat, passionate about modern web development and sci-fi literature

Updated on July 09, 2022

Comments

  • s0nica
    s0nica almost 2 years

    I have a MVC 5 website with localized routes defined as

    routes.MapRoute(
                name: "Default",
                url: "{culture}/{controller}/{action}/{id}",
                defaults: new { culture = CultureHelper.GetDefaultCulture(), controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
    

    Where the default culture results in "en-US".


    The problem arises when on startup I have to define the login url using the LoginPath property, that is set once and it will always use the provided value, e.g. the default culture if "/en-Us/Account/Login" is the specified value. I then tried to use the UrlHelper class in the hope of experience some magic but the result is obviously the same:

    var httpContext = HttpContext.Current;
            if (httpContext == null) {
              var request = new HttpRequest("/", "http://example.com", "");
              var response = new HttpResponse(new StringWriter());
              httpContext = new HttpContext(request, response);
            }
    
            var httpContextBase = new HttpContextWrapper(httpContext);
            var routeData = new RouteData();
            var requestContext = new RequestContext(httpContextBase, routeData);
            UrlHelper helper = new UrlHelper(requestContext);
    
            var loginPath = helper.Action("Login", "Account");
    
            // 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(loginPath)
            });
    

    My question is: is there a way to hack this mechanism to dynamically retrieve the current culture or am I forced to set the current culture into a cookie and, when I'm redirected to the login page, use the cookie value to set the current culture before rendering the page?

    Thanks

  • s0nica
    s0nica about 10 years
    Hi, thanks for your answer! I'll try it out in the next days and I'll let you know!
  • s0nica
    s0nica about 10 years
    I used a merge of both @martinoss and your solutions and I got it working, so I decided to accept martinoss' reply as the accepted answer and to upvote yours as well. Thanks to the both of you guys :)
  • martinoss
    martinoss about 10 years
    You are right, the wrapper is not necessary. I updated my solution accordingly.
  • Lafi
    Lafi about 10 years
    This does not answer the question. You are setting the path once and that's it. There is no way to determine the path dynamically on each request as what the question is asking for.
  • Steffen Mangold
    Steffen Mangold about 10 years
    @Lafi: your comment is not true. This is dynamic and does exact what the question is asking for... it dynamtic put the custom route path to the Url (for example /en-en/Controller/Action)
  • HCL
    HCL about 10 years
    @Steffen: This does not work, the unescapeActionUri is set once (at app startup). I don't see, how this would be dynamically.
  • Gone Coding
    Gone Coding over 8 years
    Much simpler, cleaner and certainly works. This answer just lead to a solution which saved my day! +1
  • Sentinel
    Sentinel over 8 years
    Nice one TBA, one or two of your posts have saved mine in the past too!
  • abzarak
    abzarak over 8 years
    perfect. what of nicest snippets ever saw. saved a lot of time for me. and... @lafi 's answer is enhancing your answer (as he said) :)
  • abzarak
    abzarak over 8 years
    this is executed once on startup not on every request. wrong answer
  • Steffen Mangold
    Steffen Mangold over 8 years
    ok I have exact this code in your webportal (company public and produktive). It worked exactly like requested. Dynamic and stable...
  • oneNiceFriend
    oneNiceFriend almost 8 years
    Unfortunately doesnt work, The error is:An exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll but was not handled in user code Additional information: Specified argument was out of the range of valid values.
  • oneNiceFriend
    oneNiceFriend almost 8 years
    I added new { ReturnUrl = context.Request.Uri.PathAndQuery } and It gives me Return Url as well. Thanks
  • nrod
    nrod over 4 years
    Although I did not use your code exactly, it did point me to a possible solution. Thank you!