diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index f8366e1c5..c36313bb2 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -45,6 +45,9 @@ [Parameter] public string RemoteIPAddress { get; set; } + [Parameter] + public string AuthorizationToken { get; set; } + private bool _initialized = false; private string _display = "display: none;"; private Installation _installation = new Installation { Success = false, Message = "" }; @@ -55,7 +58,7 @@ { SiteState.RemoteIPAddress = RemoteIPAddress; SiteState.AntiForgeryToken = AntiForgeryToken; - InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken); + SiteState.AuthorizationToken = AuthorizationToken; _installation = await InstallationService.IsInstalled(); if (_installation.Alias != null) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index a902d90e7..a9b16b4ba 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -131,7 +131,7 @@ else
- +
- +
- +
- +
diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index b5e015f52..22aa5a507 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -26,6 +26,7 @@ + diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 162b9e2ab..3e972be90 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -30,6 +30,7 @@ public static async Task Main(string[] args) var httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)}; builder.Services.AddSingleton(httpClient); + builder.Services.AddHttpClient("Remote"); builder.Services.AddOptions(); // Register localization services diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index d3f75d3ee..d23c130b5 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -127,10 +127,10 @@ Delete User - Do you want the users to be able to register for an account on the site + Do you want anonymous visitors to be able to register for an account on the site - Allow User Registration? + Allow Registration? Error Saving Settings @@ -309,4 +309,49 @@ User Info Url: + + Optionally provide the audience for the token + + + Audience: + + + Cookie Settings + + + Cookies are usually managed per domain. However you can also choose to have distinct cookies for each site. + + + Cookie Type: + + + Create Token + + + Optionally provide the issuer of the token + + + Issuer: + + + The number of minutes for which a token should be valid + + + Lifetime: + + + If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated. + + + Site Secret: + + + Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future. + + + Token: + + + Token Settings + \ No newline at end of file diff --git a/Oqtane.Client/Services/InstallationService.cs b/Oqtane.Client/Services/InstallationService.cs index 9501a5da7..44441fe85 100644 --- a/Oqtane.Client/Services/InstallationService.cs +++ b/Oqtane.Client/Services/InstallationService.cs @@ -50,14 +50,5 @@ public async Task RegisterAsync(string email) { await PostJsonAsync($"{ApiUrl}/register?email={WebUtility.UrlEncode(email)}", true); } - - public void SetAntiForgeryTokenHeader(string antiforgerytokenvalue) - { - if (!string.IsNullOrEmpty(antiforgerytokenvalue)) - { - AddRequestHeader(Constants.AntiForgeryTokenHeaderName, antiforgerytokenvalue); - } - } - } } diff --git a/Oqtane.Client/Services/Interfaces/IInstallationService.cs b/Oqtane.Client/Services/Interfaces/IInstallationService.cs index 252b16091..84790c637 100644 --- a/Oqtane.Client/Services/Interfaces/IInstallationService.cs +++ b/Oqtane.Client/Services/Interfaces/IInstallationService.cs @@ -42,10 +42,5 @@ public interface IInstallationService /// Task RegisterAsync(string email); - /// - /// Sets the antiforgerytoken header so that it is included on all HttpClient calls for the lifetime of the app - /// - /// - void SetAntiForgeryTokenHeader(string antiforgerytokenvalue); } } diff --git a/Oqtane.Client/Services/RemoteServiceBase.cs b/Oqtane.Client/Services/RemoteServiceBase.cs new file mode 100644 index 000000000..5076d7795 --- /dev/null +++ b/Oqtane.Client/Services/RemoteServiceBase.cs @@ -0,0 +1,147 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Net.Http.Headers; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + public class RemoteServiceBase + { + private readonly SiteState _siteState; + private readonly IHttpClientFactory _httpClientFactory; + + protected RemoteServiceBase(IHttpClientFactory httpClientFactory, SiteState siteState) + { + _siteState = siteState; + _httpClientFactory = httpClientFactory; + } + + private HttpClient GetHttpClient() + { + var httpClient = _httpClientFactory.CreateClient("Remote"); + if (!httpClient.DefaultRequestHeaders.Contains(HeaderNames.Authorization) && _siteState != null && !string.IsNullOrEmpty(_siteState.AuthorizationToken)) + { + httpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + _siteState.AuthorizationToken); + } + return httpClient; + } + + protected async Task GetAsync(string uri) + { + var response = await GetHttpClient().GetAsync(uri); + CheckResponse(response); + } + + protected async Task GetStringAsync(string uri) + { + try + { + return await GetHttpClient().GetStringAsync(uri); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + return default; + } + + protected async Task GetByteArrayAsync(string uri) + { + try + { + return await GetHttpClient().GetByteArrayAsync(uri); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + return default; + } + + protected async Task GetJsonAsync(string uri) + { + var response = await GetHttpClient().GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); + if (CheckResponse(response) && ValidateJsonContent(response.Content)) + { + return await response.Content.ReadFromJsonAsync(); + } + + return default; + } + + protected async Task PutAsync(string uri) + { + var response = await GetHttpClient().PutAsync(uri, null); + CheckResponse(response); + } + + protected async Task PutJsonAsync(string uri, T value) + { + return await PutJsonAsync(uri, value); + } + + protected async Task PutJsonAsync(string uri, TValue value) + { + var response = await GetHttpClient().PutAsJsonAsync(uri, value); + if (CheckResponse(response) && ValidateJsonContent(response.Content)) + { + var result = await response.Content.ReadFromJsonAsync(); + return result; + } + return default; + } + + protected async Task PostAsync(string uri) + { + var response = await GetHttpClient().PostAsync(uri, null); + CheckResponse(response); + } + + protected async Task PostJsonAsync(string uri, T value) + { + return await PostJsonAsync(uri, value); + } + + protected async Task PostJsonAsync(string uri, TValue value) + { + var response = await GetHttpClient().PostAsJsonAsync(uri, value); + if (CheckResponse(response) && ValidateJsonContent(response.Content)) + { + var result = await response.Content.ReadFromJsonAsync(); + return result; + } + + return default; + } + + protected async Task DeleteAsync(string uri) + { + var response = await GetHttpClient().DeleteAsync(uri); + CheckResponse(response); + } + + private bool CheckResponse(HttpResponseMessage response) + { + if (response.IsSuccessStatusCode) return true; + if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound) + { + Console.WriteLine($"Request: {response.RequestMessage.RequestUri}"); + Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}"); + } + + return false; + } + + private static bool ValidateJsonContent(HttpContent content) + { + var mediaType = content?.Headers.ContentType?.MediaType; + return mediaType != null && mediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs index 2b2c1df58..a5e7fd264 100644 --- a/Oqtane.Client/Services/ServiceBase.cs +++ b/Oqtane.Client/Services/ServiceBase.cs @@ -5,24 +5,31 @@ using System.Net.Http.Json; using System.Threading; using System.Threading.Tasks; -using Oqtane.Documentation; using Oqtane.Models; using Oqtane.Shared; namespace Oqtane.Services { - [PrivateApi("Don't show in the documentation, as everything should use the Interface")] public class ServiceBase { - private readonly HttpClient _http; + private readonly HttpClient _httpClient; private readonly SiteState _siteState; - protected ServiceBase(HttpClient client, SiteState siteState) + protected ServiceBase(HttpClient httpClient, SiteState siteState) { - _http = client; + _httpClient = httpClient; _siteState = siteState; } + private HttpClient GetHttpClient() + { + if (!_httpClient.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken)) + { + _httpClient.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken); + } + return _httpClient; + } + // should be used with new constructor public string CreateApiUrl(string serviceName) { @@ -95,24 +102,9 @@ public string CreateAuthorizationPolicyUrl(string url, Dictionary a } } - // note that HttpClient is registered as a Scoped(shared) service and therefore you should not use request headers whose value can vary over the lifetime of the service - protected void AddRequestHeader(string name, string value) - { - RemoveRequestHeader(name); - _http.DefaultRequestHeaders.Add(name, value); - } - - protected void RemoveRequestHeader(string name) - { - if (_http.DefaultRequestHeaders.Contains(name)) - { - _http.DefaultRequestHeaders.Remove(name); - } - } - protected async Task GetAsync(string uri) { - var response = await _http.GetAsync(uri); + var response = await GetHttpClient().GetAsync(uri); CheckResponse(response); } @@ -120,7 +112,7 @@ protected async Task GetStringAsync(string uri) { try { - return await _http.GetStringAsync(uri); + return await GetHttpClient().GetStringAsync(uri); } catch (Exception e) { @@ -134,7 +126,7 @@ protected async Task GetByteArrayAsync(string uri) { try { - return await _http.GetByteArrayAsync(uri); + return await GetHttpClient().GetByteArrayAsync(uri); } catch (Exception e) { @@ -146,7 +138,7 @@ protected async Task GetByteArrayAsync(string uri) protected async Task GetJsonAsync(string uri) { - var response = await _http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); + var response = await GetHttpClient().GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); if (CheckResponse(response) && ValidateJsonContent(response.Content)) { return await response.Content.ReadFromJsonAsync(); @@ -157,7 +149,7 @@ protected async Task GetJsonAsync(string uri) protected async Task PutAsync(string uri) { - var response = await _http.PutAsync(uri, null); + var response = await GetHttpClient().PutAsync(uri, null); CheckResponse(response); } @@ -168,7 +160,7 @@ protected async Task PutJsonAsync(string uri, T value) protected async Task PutJsonAsync(string uri, TValue value) { - var response = await _http.PutAsJsonAsync(uri, value); + var response = await GetHttpClient().PutAsJsonAsync(uri, value); if (CheckResponse(response) && ValidateJsonContent(response.Content)) { var result = await response.Content.ReadFromJsonAsync(); @@ -179,7 +171,7 @@ protected async Task PutJsonAsync(string uri, TValue v protected async Task PostAsync(string uri) { - var response = await _http.PostAsync(uri, null); + var response = await GetHttpClient().PostAsync(uri, null); CheckResponse(response); } @@ -190,7 +182,7 @@ protected async Task PostJsonAsync(string uri, T value) protected async Task PostJsonAsync(string uri, TValue value) { - var response = await _http.PostAsJsonAsync(uri, value); + var response = await GetHttpClient().PostAsJsonAsync(uri, value); if (CheckResponse(response) && ValidateJsonContent(response.Content)) { var result = await response.Content.ReadFromJsonAsync(); @@ -202,7 +194,7 @@ protected async Task PostJsonAsync(string uri, TValue protected async Task DeleteAsync(string uri) { - var response = await _http.DeleteAsync(uri); + var response = await GetHttpClient().DeleteAsync(uri); CheckResponse(response); } @@ -228,7 +220,7 @@ private static bool ValidateJsonContent(HttpContent content) // This constructor is obsolete. Use ServiceBase(HttpClient client, SiteState siteState) : base(http, siteState) {} instead. protected ServiceBase(HttpClient client) { - _http = client; + _httpClient = client; } [Obsolete("This method is obsolete. Use CreateApiUrl(string serviceName, Alias alias) in conjunction with ControllerRoutes.ApiRoute in Controllers instead.", false)] diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index e9c233183..b741cae08 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -526,16 +526,12 @@ public async Task Validate(string password) public string Token() { var token = ""; - var user = _users.GetUser(User.Identity.Name); - if (user != null) + var sitesettings = HttpContext.GetSiteSettings(); + var secret = sitesettings.GetValue("JwtOptions:Secret", ""); + if (!string.IsNullOrEmpty(secret)) { - var sitesettings = HttpContext.GetSiteSettings(); - var secret = sitesettings.GetValue("JwtOptions:Secret", ""); - if (!string.IsNullOrEmpty(secret)) - { - var lifetime = 525600; // long-lived token set to 1 year - token = _jwtManager.GenerateToken(_tenantManager.GetAlias(), user, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), lifetime); - } + var lifetime = 525600; // long-lived token set to 1 year + token = _jwtManager.GenerateToken(_tenantManager.GetAlias(), (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), lifetime); } return token; } @@ -548,7 +544,10 @@ public User Authenticate() if (user.IsAuthenticated) { user.Username = User.Identity.Name; - user.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value); + if (User.HasClaim(item => item.Type == ClaimTypes.NameIdentifier)) + { + user.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value); + } string roles = ""; foreach (var claim in User.Claims.Where(item => item.Type == ClaimTypes.Role)) { diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 726175fb3..b9bd5a949 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -194,7 +194,7 @@ public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCol return services; } - internal static IServiceCollection TryAddHttpClientWithAuthenticationCookie(this IServiceCollection services) + internal static IServiceCollection AddHttpClients(this IServiceCollection services) { if (!services.Any(x => x.ServiceType == typeof(HttpClient))) { @@ -216,6 +216,8 @@ internal static IServiceCollection TryAddHttpClientWithAuthenticationCookie(this }); } + services.AddHttpClient("External"); + return services; } diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index 4b56af10a..3c8288ff0 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -25,7 +25,7 @@ { @(Html.AntiForgeryToken()) - +
diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 254cc96da..eef2c6907 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -20,6 +20,8 @@ using System.Net; using Microsoft.Extensions.Primitives; using Oqtane.Enums; +using Oqtane.Security; +using Oqtane.Extensions; namespace Oqtane.Pages { @@ -30,6 +32,7 @@ public class HostModel : PageModel private readonly ILocalizationManager _localizationManager; private readonly ILanguageRepository _languages; private readonly IAntiforgery _antiforgery; + private readonly IJwtManager _jwtManager; private readonly ISiteRepository _sites; private readonly IPageRepository _pages; private readonly IUrlMappingRepository _urlMappings; @@ -38,13 +41,14 @@ public class HostModel : PageModel private readonly ISettingRepository _settings; private readonly ILogManager _logger; - public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ILogManager logger) + public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ILogManager logger) { _configuration = configuration; _tenantManager = tenantManager; _localizationManager = localizationManager; _languages = languages; _antiforgery = antiforgery; + _jwtManager = jwtManager; _sites = sites; _pages = pages; _urlMappings = urlMappings; @@ -56,6 +60,7 @@ public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILo public string Language = "en"; public string AntiForgeryToken = ""; + public string AuthorizationToken = ""; public string Runtime = "Server"; public RenderMode RenderMode = RenderMode.Server; public int VisitorId = -1; @@ -133,6 +138,17 @@ public IActionResult OnGet() Title = site.Name; ThemeType = site.DefaultThemeType; + // get jwt token for downstream APIs + if (User.Identity.IsAuthenticated) + { + var sitesettings = HttpContext.GetSiteSettings(); + var secret = sitesettings.GetValue("JwtOptions:Secret", ""); + if (!string.IsNullOrEmpty(secret)) + { + AuthorizationToken = _jwtManager.GenerateToken(alias, (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20"))); + } + } + if (site.VisitorTracking) { TrackVisitor(site.SiteId); @@ -247,9 +263,9 @@ private void TrackVisitor(int SiteId) string url = Request.GetEncodedUrl(); string referrer = (Request.Headers[HeaderNames.Referer] != StringValues.Empty) ? Request.Headers[HeaderNames.Referer] : ""; int? userid = null; - if (User.HasClaim(item => item.Type == ClaimTypes.PrimarySid)) + if (User.HasClaim(item => item.Type == ClaimTypes.NameIdentifier)) { - userid = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value); + userid = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value); } // check if cookie already exists diff --git a/Oqtane.Server/Security/JwtManager.cs b/Oqtane.Server/Security/JwtManager.cs index a4a3597ee..9a426fc38 100644 --- a/Oqtane.Server/Security/JwtManager.cs +++ b/Oqtane.Server/Security/JwtManager.cs @@ -10,20 +10,19 @@ namespace Oqtane.Security { public interface IJwtManager { - string GenerateToken(Alias alias, User user, string secret, string issuer, string audience, int lifetime); + string GenerateToken(Alias alias, ClaimsIdentity user, string secret, string issuer, string audience, int lifetime); User ValidateToken(string token, string secret, string issuer, string audience); } public class JwtManager : IJwtManager { - public string GenerateToken(Alias alias, User user, string secret, string issuer, string audience, int lifetime) + public string GenerateToken(Alias alias, ClaimsIdentity user, string secret, string issuer, string audience, int lifetime) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(secret); - var identity = UserSecurity.CreateClaimsIdentity(alias, user); var tokenDescriptor = new SecurityTokenDescriptor { - Subject = new ClaimsIdentity(identity), + Subject = new ClaimsIdentity(user), Issuer = issuer, Audience = audience, Expires = DateTime.UtcNow.AddMinutes(lifetime), @@ -56,7 +55,7 @@ public User ValidateToken(string token, string secret, string issuer, string aud var jwtToken = (JwtSecurityToken)validatedToken; var user = new User { - UserId = int.Parse(jwtToken.Claims.FirstOrDefault(item => item.Type == "id")?.Value), + UserId = int.Parse(jwtToken.Claims.FirstOrDefault(item => item.Type == "nameid")?.Value), Username = jwtToken.Claims.FirstOrDefault(item => item.Type == "name")?.Value }; return user; diff --git a/Oqtane.Server/Security/PrincipalValidator.cs b/Oqtane.Server/Security/PrincipalValidator.cs index e4449a5c2..787afce07 100644 --- a/Oqtane.Server/Security/PrincipalValidator.cs +++ b/Oqtane.Server/Security/PrincipalValidator.cs @@ -28,7 +28,7 @@ public static Task ValidateAsync(CookieValidatePrincipalContext context) var claims = context.Principal.Claims; // check if principal has roles and matches current site - if (!claims.Any(item => item.Type == ClaimTypes.Role) || claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.SiteKey) + if (!claims.Any(item => item.Type == ClaimTypes.Role) || claims.FirstOrDefault(item => item.Type == "sitekey")?.Value != alias.SiteKey) { var userRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRepository)) as IUserRepository; var userRoleRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository; diff --git a/Oqtane.Server/Security/UserPermissions.cs b/Oqtane.Server/Security/UserPermissions.cs index a62c827a6..147f90351 100644 --- a/Oqtane.Server/Security/UserPermissions.cs +++ b/Oqtane.Server/Security/UserPermissions.cs @@ -49,9 +49,9 @@ public User GetUser(ClaimsPrincipal principal) if (user.IsAuthenticated) { user.Username = principal.Identity.Name; - if (principal.Claims.Any(item => item.Type == ClaimTypes.PrimarySid)) + if (principal.Claims.Any(item => item.Type == ClaimTypes.NameIdentifier)) { - user.UserId = int.Parse(principal.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value); + user.UserId = int.Parse(principal.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value); } foreach (var claim in principal.Claims.Where(item => item.Type == ClaimTypes.Role)) { diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index ac33acaf7..c1a2aeb2d 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -70,7 +70,7 @@ public void ConfigureServices(IServiceCollection services) }); // setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) - services.TryAddHttpClientWithAuthenticationCookie(); + services.AddHttpClients(); // register scoped core services services.AddScoped() diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Showcase/screenshot.png b/Oqtane.Server/wwwroot/Modules/Oqtane.Showcase/screenshot.png new file mode 100644 index 000000000..febc733bc Binary files /dev/null and b/Oqtane.Server/wwwroot/Modules/Oqtane.Showcase/screenshot.png differ diff --git a/Oqtane.Shared/Security/UserSecurity.cs b/Oqtane.Shared/Security/UserSecurity.cs index 2aec26a49..70f43f55a 100644 --- a/Oqtane.Shared/Security/UserSecurity.cs +++ b/Oqtane.Shared/Security/UserSecurity.cs @@ -133,8 +133,8 @@ public static ClaimsIdentity CreateClaimsIdentity(Alias alias, User user) if (alias != null && user != null && !user.IsDeleted) { identity.AddClaim(new Claim(ClaimTypes.Name, user.Username)); - identity.AddClaim(new Claim(ClaimTypes.PrimarySid, user.UserId.ToString())); - identity.AddClaim(new Claim(ClaimTypes.GroupSid, alias.SiteKey)); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString())); + identity.AddClaim(new Claim("sitekey", alias.SiteKey)); if (user.Roles.Contains(RoleNames.Host)) { // host users are site admins by default @@ -160,12 +160,12 @@ public static void ResetClaimsIdentity(ClaimsIdentity identity) { identity.RemoveClaim(claim); } - claim = identity.Claims.FirstOrDefault(item => item.Type == ClaimTypes.PrimarySid); + claim = identity.Claims.FirstOrDefault(item => item.Type == ClaimTypes.NameIdentifier); if (claim != null) { identity.RemoveClaim(claim); } - claim = identity.Claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid); + claim = identity.Claims.FirstOrDefault(item => item.Type == "sitekey"); if (claim != null) { identity.RemoveClaim(claim); diff --git a/Oqtane.Shared/Shared/SiteState.cs b/Oqtane.Shared/Shared/SiteState.cs index 2c51d6cd7..cee7546e6 100644 --- a/Oqtane.Shared/Shared/SiteState.cs +++ b/Oqtane.Shared/Shared/SiteState.cs @@ -7,6 +7,7 @@ public class SiteState { public Alias Alias { get; set; } public string AntiForgeryToken { get; set; } // passed from server for use in service calls on client + public string AuthorizationToken { get; set; } // passed from server for use in service calls on client public string RemoteIPAddress { get; set; } // passed from server as cannot be reliable retrieved on client } }