'razor pages culture value is lost after redirecting to identity pages

I have created razor pages web application with multiple language support, it is reading the first part of URL and tries to show the content with the corresponding language, e.g. localhost:5001/ka or /en or /es . everything is working, except when I am trying to navigate to the page which requires an authorized user, it redirects to the login page but "request loses culture value", and it looks like the first part from the URL is identified as a culture identifier ("Identity" at this case).

for example, when i am trying to open this URL "localhost:5001/ka/dashboard/posts/create" with unauthorized browser, it redirects to login page with this link "localhost:5001/Identity/Account/Login?ReturnUrl=%2Fka%2Fdashboard%2Fposts%2Fcreate"

Here is my configuration parts:

    services.Configure<RequestLocalizationOptions>(options =>
            {
                var supportedCultures = new[]
                {
                    new CultureInfo("ka")
                        { NumberFormat = new NumberFormatInfo { CurrencyDecimalSeparator = "." } },
                    new CultureInfo("en")
                };

                options.DefaultRequestCulture = new RequestCulture("ka", "ka");
                options.SupportedCultures = supportedCultures;
                options.SupportedUICultures = supportedCultures;

                options.RequestCultureProviders.Insert(0,
                    new RouteDataRequestCultureProvider { Options = options });
                options.RequestCultureProviders.Insert(1,
                    new CustomRequestCultureProvider(context =>
                        Task.FromResult(new ProviderCultureResult("ka", "ka"))));
            });

    services.AddRazorPages(options =>
                    {    ...some code here...
                        //Localization
                        options.Conventions.Add(new CultureTemplatePageRouteModelConvention());
                    })

Configure part

     var localizationOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>()?.Value;
app.UseRequestLocalization(localizationOptions);

Model convention part

public class CultureTemplatePageRouteModelConvention : IPageRouteModelConvention
    {
        public void Apply(PageRouteModel model)
        {
            foreach (var selector in model.Selectors.ToList())
            {
                if (!selector.AttributeRouteModel.Template.EndsWith("Index") &&
                    !selector.AttributeRouteModel.Template.StartsWith("Identity"))
                    model.Selectors.Add(new SelectorModel
                    {
                        AttributeRouteModel = new AttributeRouteModel
                        {
                            Order = -1,
                            Template = AttributeRouteModel.CombineTemplates("{culture=ka}",
                                selector.AttributeRouteModel.Template)
                        }
                    });
                if (selector.AttributeRouteModel.Template.StartsWith("Identity"))
                    model.Selectors.Add(new SelectorModel
                    {
                        AttributeRouteModel = new AttributeRouteModel
                        {
                            Order = -1,
                            Template = AttributeRouteModel.CombineTemplates("{culture=ka}",
                                selector.AttributeRouteModel.Template.Substring("Identity/".Length))
                        }
                    });
            }
        }
    }


Solution 1:[1]

There is two different scenarios here!

1. Manually redirecting to authentication pages:

  • Configure all url's to include the culture param.
  • CookieRequestCultureProvider is enabled by default unless you remove it manually. But you need to set the culture cookie value manually when switching the culture. So setup your language navigation to point to a common page handler as below (read more here):
/// <summary>
/// Set culture cookie value
/// </summary>
/// <param name="cltr">Culture name</param>
/// <param name="returnUrl">The url to return to after setting the cookie</param>
public IActionResult OnGetSetCultureCookie(string cltr, string returnUrl)
{
    Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(cltr)),
        new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
    );

    return LocalRedirect(returnUrl);
}

2. Automatically redirecting to authentication pages:

When the user is automatically redirected to any login, logout, access denied or redirect to page, the underlaying infrastructure takes the page url from the default authentication cookie, and by default it has no culture parameter.

So in order to provide culture param to these url's you need to override the default authentication cookie events.

public class CustomCookieAuthEvents : CookieAuthenticationEvents
{
    private readonly string _defCulture;

    public CustomCookieAuthEvents(
            IWebHostEnvironment hostEnvironment,
            IOptions<RequestLocalizationOptions> options)
    {
        _defCulture = options.Value.DefaultRequestCulture.Culture.Name;
    
    }

    public override Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context)
    {
        var culture = context.HttpContext.GetRouteValue("culture") ?? _defCulture;
        context.RedirectUri = $"/{culture}/Identity/Account/Login";
        return base.RedirectToLogin(context);
        }

    public override Task RedirectToLogout(RedirectContext<CookieAuthenticationOptions> context)
    {
        var culture = context.HttpContext.GetRouteValue("culture") ?? _defCulture;
        context.RedirectUri = $"/{culture}/Identity/Account/Login";
        return base.RedirectToLogout(context);
    }

    public override Task RedirectToAccessDenied(RedirectContext<CookieAuthenticationOptions> context)
    {
        var culture = context.HttpContext.GetRouteValue("culture") ?? _defCulture;
        context.RedirectUri = $"/{culture}/Identity/Account/Login";
        return base.RedirectToAccessDenied(context);
    }

    public override Task RedirectToReturnUrl(RedirectContext<CookieAuthenticationOptions> context)
    {
        var culture = context.HttpContext.GetRouteValue("culture") ?? _defCulture;
        context.RedirectUri = $"/{culture}/Identity/Account/Login";
            return base.RedirectToReturnUrl(context);
        }
    }

Then register in startup:

services.AddCookieAuth<CustomCookieAuthEvents>(Configuration);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 LazZiya