From 8fe66052508412827315416d488421fbf3179f60 Mon Sep 17 00:00:00 2001 From: Dennis Haney Date: Thu, 2 Nov 2023 16:38:43 +0700 Subject: [PATCH 1/2] feat: add i18n helpers --- README.md | 40 +++++++++++ .../I18nHelpers/OidcClaimsCultureProvider.cs | 68 +++++++++++++++++++ .../I18nHelpers/OidcClaimsCultureProvider.cs | 56 +++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs create mode 100644 src/Catglobe.Openiddict.Contrib.Server/I18nHelpers/OidcClaimsCultureProvider.cs diff --git a/README.md b/README.md index 186f567..1fb6bf6 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,31 @@ Blazor login page example: } ``` +# I18n helpers + +Helpers to make it trivial to implement a client that pulls the language and culture info from the server. + +```csharp +host.UseRequestLocalization(o => { + var cultures = ...; + o.AddSupportedCultures(cultures) + .AddSupportedUICultures(cultures) + .SetDefaultCulture(cultures[0]); + //insert before the final default provider (the AcceptLanguageHeaderRequestCultureProvider) + o.RequestCultureProviders.Insert(o.RequestCultureProviders.Count - 1, new OidcClaimsCultureProvider {Options = o}); +}); +``` + +Notice, this requires the server sets the culture info in the claims. +See below for example, or see `OidcClaimsCultureProviderHelper.AddClaims` for details. + +You also need to copy it from the server given claims to the local claims. E.g. if using `StoreRemoteAuthInSchemeAsync`: + +```csharp +yourContext.StoreRemoteAuthInSchemeAsync(..., (identity, remote)=>OidcClaimsCultureProviderHelper.CopyClaims(identity, remote)))) +``` + + ## Http helpers ### HttpClient for authentication code flow @@ -400,3 +425,18 @@ services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) o.ReturnStatusCodesOnAuthFailuresForApiCalls(); }); ``` + +### I18n helper + +Add OIDC standard claims for culture (`locale`) and the non-standard `ui-locales` for the ui-culture. + +```csharp +public abstract class ApplyCultureToClaims(...) : OidcAccessGranterBase(...) +{ + protected override Task> SetClaimsAndGetScopes(...) + { + OidcClaimsCultureProviderHelper.AddClaimsFromCurrentCulture(identity); + return base.SetClaimsAndGetScopes(...); + } +} +``` \ No newline at end of file diff --git a/src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs b/src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs new file mode 100644 index 0000000..169f9c8 --- /dev/null +++ b/src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Localization; +using System.Security.Claims; + +namespace Openiddict.Contrib.Client.I18nHelpers; + +/// +/// Pull the claim from the user and use that as the culture. +/// Also respects the non-standard "ui-locales" claim if present, in which case is interpreted as the culture for formatting numbers, dates etc. +/// +/// host.UseRequestLocalization(o => { +/// var cultures = ...; +/// o.AddSupportedCultures(cultures) +/// .AddSupportedUICultures(cultures) +/// .SetDefaultCulture(cultures[0]); +/// //insert before the final default provider (the AcceptLanguageHeaderRequestCultureProvider) +/// o.RequestCultureProviders.Insert(o.RequestCultureProviders.Count - 1, new OidcClaimsCultureProvider {Options = o}); +/// }); +/// +/// +public class OidcClaimsCultureProvider : RequestCultureProvider +{ + /// + public override Task DetermineProviderCultureResult(HttpContext httpContext) => Task.FromResult(GetCultureFromClaims(httpContext)); + + private static ProviderCultureResult? GetCultureFromClaims(HttpContext ctx) + { + var userCulture = ctx.User.GetClaim(OpenIddictConstants.Claims.Locale); + var userUiCulture = ctx.User.GetClaim(OpenIddictConstants.Parameters.UiLocales) ?? userCulture; + if (userUiCulture == null) goto noneFound; + + return new(userCulture ?? userUiCulture, userUiCulture); + noneFound: + return null; + } + + /// + /// Add appropriate claims for the given culture to the identity. + /// You probably want to use during login callback instead. + /// + /// Identity to populate + /// Culture to use in BCP47 [RFC5646] format. + public static void AddClaims(ClaimsIdentity identity, string? culture) + { + if (culture != null) identity.AddClaim(new Claim(OpenIddictConstants.Claims.Locale, culture.Replace('_', '-'))); + } + + /// + /// Add standard claims for the given culture to the identity, plus the custom "ui-locales". + /// You would usually use this if you are loading the culture info from db or similar. + /// You probably want to use during login callback instead. + /// + /// Identity to populate + /// Culture to use in BCP47 [RFC5646] format. + /// UI Culture to use in BCP47 [RFC5646] format. Ignored if same as culture. + public static void AddClaims(ClaimsIdentity identity, string? culture, string? uiCulture) + { + AddClaims(identity, culture); + if (uiCulture != null && uiCulture != culture) identity.AddClaim(new(OpenIddictConstants.Parameters.UiLocales, uiCulture)); + } + + /// + /// Copy appropriate claims for the given culture to the identity from remote. + /// + /// Identity to populate. + /// Remote claims. + public static void CopyClaims(ClaimsIdentity identity, ClaimsPrincipal remote) => AddClaims(identity, remote.GetClaim(OpenIddictConstants.Claims.Locale), remote.GetClaim(OpenIddictConstants.Parameters.UiLocales)); + +} \ No newline at end of file diff --git a/src/Catglobe.Openiddict.Contrib.Server/I18nHelpers/OidcClaimsCultureProvider.cs b/src/Catglobe.Openiddict.Contrib.Server/I18nHelpers/OidcClaimsCultureProvider.cs new file mode 100644 index 0000000..b298c35 --- /dev/null +++ b/src/Catglobe.Openiddict.Contrib.Server/I18nHelpers/OidcClaimsCultureProvider.cs @@ -0,0 +1,56 @@ +using System.Globalization; +using System.Security.Claims; + +namespace Openiddict.Contrib.Server.I18nHelpers; + +/// +/// Set the claim from the user and use that as the culture. +/// +/// host.UseRequestLocalization(o => { +/// var cultures = ...; +/// o.AddSupportedCultures(cultures) +/// .AddSupportedUICultures(cultures) +/// .SetDefaultCulture(cultures[0]) +/// .RequestCultureProviders = new List<IRequestCultureProvider> { +/// new QueryStringRequestCultureProvider {Options = o}, +/// new CookieRequestCultureProvider {Options = o}, +/// new SoapClaimsCultureProvider {Options = o}, +/// new OidcClaimsCultureProvider {Options = o}, +/// new AcceptLanguageHeaderRequestCultureProvider {Options = o}, +/// }; +/// }); +/// +/// +public class OidcClaimsCultureProviderHelper +{ + /// + /// Infer culture from current culture and add appropriate claims to the identity. + /// You would usually use this if you already have infrastructure in place to set the culture. + /// + /// Identity to populate + public static void AddClaimsFromCurrentCulture(ClaimsIdentity identity) => AddClaims(identity, CultureInfo.CurrentCulture.Name, CultureInfo.CurrentUICulture.Name); + + /// + /// Add standard claims for the given culture to the identity. + /// You would usually use this if you are loading the culture info from db or similar. + /// + /// Identity to populate + /// Culture to use in BCP47 [RFC5646] format. + public static void AddClaims(ClaimsIdentity identity, string? culture) + { + if (culture != null) identity.AddClaim(new(OpenIddictConstants.Claims.Locale, culture)); + } + + /// + /// Add standard claims for the given culture to the identity, plus the custom "ui-locales". + /// You would usually use this if you are loading the culture info from db or similar. + /// + /// Identity to populate + /// Culture to use in BCP47 [RFC5646] format. + /// UI Culture to use in BCP47 [RFC5646] format. Ignored if same as culture. + public static void AddClaims(ClaimsIdentity identity, string? culture, string? uiCulture) + { + AddClaims(identity, culture); + if (uiCulture != null && uiCulture != culture) identity.AddClaim(new(OpenIddictConstants.Parameters.UiLocales, uiCulture)); + } +} \ No newline at end of file From bcfe1369f40850759159400f139b265b3b2ae761 Mon Sep 17 00:00:00 2001 From: Dennis Haney Date: Thu, 2 Nov 2023 16:43:24 +0700 Subject: [PATCH 2/2] fix: RequestCultureProvider is net 6+ --- .../I18nHelpers/OidcClaimsCultureProvider.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs b/src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs index 169f9c8..9c78f6b 100644 --- a/src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs +++ b/src/Catglobe.Openiddict.Contrib.Client/I18nHelpers/OidcClaimsCultureProvider.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Localization; +#if NET +using Microsoft.AspNetCore.Localization; using System.Security.Claims; namespace Openiddict.Contrib.Client.I18nHelpers; @@ -65,4 +66,5 @@ public static void AddClaims(ClaimsIdentity identity, string? culture, string? u /// Remote claims. public static void CopyClaims(ClaimsIdentity identity, ClaimsPrincipal remote) => AddClaims(identity, remote.GetClaim(OpenIddictConstants.Claims.Locale), remote.GetClaim(OpenIddictConstants.Parameters.UiLocales)); -} \ No newline at end of file +} +#endif