IdentityServer4 Localization using ui_locales and the query string

This post is part 2 from the previous post IdentityServer4 Localization with the OIDC Implicit Flow where the localization was implemented using a shared cookie between the applications. This has its restrictions, due to the cookie domain constraints and this post shows how the oidc optional parameter ui_locales can be used instead, to pass the localization between the client application and the STS server.

Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow

The ui_locales, which is an optional parameter defined in the OpenID standard, can be used to pass the localization from the client application to the server application in the authorize request. The parameter is passed as a query string value.

A custom RequestCultureProvider class is implemented to handle this. The culture provider checks for the ui_locales in the query string and sets the culture if it is found. If it is not found, it checks for the returnUrl parameter. This is the parameter returned by the IdentityServer4 middleware after a redirect from the /connect/authorize endpoint. The provider then searches for the ui_locales in the parameter and sets the culture if found.

Once the culture has been set, a localization cookie is set on the server and added to the response. The will be used if the client application/user tries to logout. This is required because the culture cannot be set for the endsession endpoint.

using Microsoft.AspNetCore.Localization;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.WebUtilities;
using System.Linq;

namespace IdentityServerWithIdentitySQLite
{
    public class LocalizationQueryProvider : RequestCultureProvider
    {
        public static readonly string DefaultParamterName = "culture";

        public string QureyParamterName { get; set; } = DefaultParamterName;

        /// <inheritdoc />
        public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException(nameof(httpContext));
            }

            var query = httpContext.Request.Query;
            var exists = query.TryGetValue("ui_locales", out StringValues culture);

            if (!exists)
            {
                exists = query.TryGetValue("returnUrl", out StringValues requesturl);
                // hack because Identityserver4 does some magic here...
                // Need to set the culture manually
                if (exists)
                {
                    var request = requesturl.ToArray()[0];
                    Uri uri = new Uri("http://faketopreventexception" + request);
                    var query1 = QueryHelpers.ParseQuery(uri.Query);
                    var requestCulture = query1.FirstOrDefault(t => t.Key == "ui_locales").Value;

                    var cultureFromReturnUrl = requestCulture.ToString();
                    if(string.IsNullOrEmpty(cultureFromReturnUrl))
                    {
                        return NullProviderCultureResult;
                    }

                    culture = cultureFromReturnUrl;
                }
            }

            var providerResultCulture = ParseDefaultParamterValue(culture);

            // Use this cookie for following requests, so that for example the logout request will work
            if (!string.IsNullOrEmpty(culture.ToString()))
            {
                var cookie = httpContext.Request.Cookies[".AspNetCore.Culture"];
                var newCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));

                if (string.IsNullOrEmpty(cookie) || cookie != newCookieValue)
                {
                    httpContext.Response.Cookies.Append(".AspNetCore.Culture", newCookieValue);
                }
            }

            return Task.FromResult(providerResultCulture);
        }

        public static ProviderCultureResult ParseDefaultParamterValue(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return null;
            }

            var cultureName = value;
            var uiCultureName = value;

            if (cultureName == null && uiCultureName == null)
            {
                // No values specified for either so no match
                return null;
            }

            if (cultureName != null && uiCultureName == null)
            {
                uiCultureName = cultureName;
            }

            if (cultureName == null && uiCultureName != null)
            {
                cultureName = uiCultureName;
            }

            return new ProviderCultureResult(cultureName, uiCultureName);
        }
    }
}

The LocalizationQueryProvider can then be added as part of the localization configuration.

services.Configure<RequestLocalizationOptions>(
options =>
{
	var supportedCultures = new List<CultureInfo>
		{
			new CultureInfo("en-US"),
			new CultureInfo("de-CH"),
			new CultureInfo("fr-CH"),
			new CultureInfo("it-CH")
		};

	options.DefaultRequestCulture = new RequestCulture(culture: "de-CH", uiCulture: "de-CH");
	options.SupportedCultures = supportedCultures;
	options.SupportedUICultures = supportedCultures;

	var providerQuery = new LocalizationQueryProvider
	{
		QureyParamterName = "ui_locales"
	};

	options.RequestCultureProviders.Insert(0, providerQuery);
});

The client application can add the ui_locales parameter to the authorize request.

let culture = 'de-CH';
if (this.locale.getCurrentCountry()) {
   culture = this.locale.getCurrentLanguage() + '-' + this.locale.getCurrentCountry();
}

this.oidcSecurityService.setCustomRequestParameters({ 'ui_locales': culture});

this.oidcSecurityService.authorize();

The localization will now be sent from the client application to the server.

https://localhost:44318/account/login?returnUrl=%2Fconnect%2Fauthorize? …ui_locales%3Dfr-CH


Links:

Shared Localization in ASP.NET Core MVC

https://github.com/IdentityServer/IdentityServer4

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization

https://github.com/robisim74/angular-l10n

IdentityServer4 Localization with the OIDC Implicit Flow

http://openid.net/specs/openid-connect-core-1_0.html

5 comments

  1. […] Shared Localization in ASP.NET Core MVC IdentityServer4 Localization using ui_locales and the query string → .widget.widget_media_image { overflow: hidden; }.widget.widget_media_image img { height: […]

  2. […] IdentityServer4 Localization using ui_locales and the query string – Damien Bowden […]

  3. […] IdentityServer4 Localization using ui_locales and the query string (Damien Bowden) […]

  4. The ParseDefaultParamterValue could be shortened to just:

    public static ProviderCultureResult ParseDefaultParamterValue(String value)
    {
    return !String.IsNullOrWhiteSpace(value) ? new ProviderCultureResult(value, value) : null;
    }

    1. thanks, I’ll fix this

      Greetings Damien

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.