From 873809358755f8f917fb4c685caea4663b4a243c Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 7 Jul 2026 13:57:16 -0400 Subject: [PATCH 1/8] Added Claimtypes const --- .../AccessControl/ResourceAccessType.cs | 10 +-- src/Gemstone.Security/GemstoneClaimTypes.cs | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 src/Gemstone.Security/GemstoneClaimTypes.cs diff --git a/src/Gemstone.Security/AccessControl/ResourceAccessType.cs b/src/Gemstone.Security/AccessControl/ResourceAccessType.cs index baeca622..98880c56 100644 --- a/src/Gemstone.Security/AccessControl/ResourceAccessType.cs +++ b/src/Gemstone.Security/AccessControl/ResourceAccessType.cs @@ -94,21 +94,17 @@ public static bool HasAccessTo(this ClaimsPrincipal user, string resourceType, s { ThrowIfNotValid(access); - const string AllowClaim = "Gemstone.ResourceAccess.Allow"; - const string DenyClaim = "Gemstone.ResourceAccess.Deny"; - const string BaseClaim = "Gemstone.ResourceAccess.Default"; - if (access == ResourceAccessType.None) return false; string claimValue = $"{resourceType} {resourceName} {access}"; bool IsDenied() => - user.HasClaim(DenyClaim, claimValue); + user.HasClaim(GemstoneClaimTypes.DenyClaim, claimValue); bool IsAllowed() => - user.HasClaim(AllowClaim, claimValue) || - user.HasClaim(BaseClaim, $"{access}"); + user.HasClaim(GemstoneClaimTypes.AllowClaim, claimValue) || + user.HasClaim(GemstoneClaimTypes.BaseClaim, $"{access}"); return !IsDenied() && IsAllowed(); } diff --git a/src/Gemstone.Security/GemstoneClaimTypes.cs b/src/Gemstone.Security/GemstoneClaimTypes.cs new file mode 100644 index 00000000..06a18e71 --- /dev/null +++ b/src/Gemstone.Security/GemstoneClaimTypes.cs @@ -0,0 +1,63 @@ +//****************************************************************************************************** +// GemstoneClaimTypes.cs - Gbtc +// +// Copyright © 2026, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 10/16/2019 - J. Ritchie Carroll +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.Runtime.CompilerServices; + +namespace Gemstone.Security +{ + /// + /// The Claim Types used bu the namespace + /// + public static class GemstoneClaimTypes + { + /// + /// Holds the unique identifier for the User. + /// + public const string UserIdentity = "Gemstone.UserIdentity"; + /// + /// Holds the unique identifier for the Authentication Provider. + /// + public const string ProviderIdentity = "Gemstone.ProviderIdentity"; + + /// + /// Is used in Matrching Claims to match any user, regardless of claims they have + /// + public const string AllUsers = "Gemstone.AllUsers"; + + /// + /// Allows a user to access a resource. + /// + public const string AllowClaim = "Gemstone.ResourceAccess.Allow"; + + /// + /// Denies a user access to a resource. + /// + public const string DenyClaim = "Gemstone.ResourceAccess.Deny"; + + /// + /// Allows access to the value as the default access level. + /// + public const string BaseClaim = "Gemstone.ResourceAccess.Default"; + + } +} From 92acb58a4dc2ad1c71f8150e9954edcfd5b5aebc Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 7 Jul 2026 13:57:39 -0400 Subject: [PATCH 2/8] Moved All users Claims into IAuthbuilder --- .../IAuthenticationBuilder.cs | 15 +++++++-------- .../OAuthAuthenticationProvider.cs | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs b/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs index 669af0dd..a2e2d02c 100644 --- a/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs +++ b/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs @@ -96,9 +96,6 @@ public IEnumerable GetProviderIdentities() public IEnumerable GetAssignedClaims(string providerIdentity, ClaimsPrincipal principal) { - const string ProviderIdentityClaim = "Gemstone.ProviderIdentity"; - const string UserIdentityClaim = "Gemstone.UserIdentity"; - IAuthenticationProvider? provider = ProviderLookup(providerIdentity); if (provider is null) @@ -106,11 +103,13 @@ public IEnumerable GetAssignedClaims(string providerIdentity, ClaimsPrinc string userIdentity = provider.GetIdentity(principal); - IEnumerable providerClaims = Setup - .GetProviderClaims(providerIdentity) - .Join(principal.Claims, ToKey, ToKey, (providerClaim, _) => providerClaim.Assigned) - .Prepend(new(UserIdentityClaim, userIdentity)) - .Prepend(new(ProviderIdentityClaim, providerIdentity)); + IEnumerable principalClaims = principal.Claims + .Append(new(GemstoneClaimTypes.AllUsers,string.Empty)); + + IEnumerable providerClaims = Setup.GetProviderClaims(providerIdentity) + .Join(principalClaims, ToKey, ToKey, (providerClaim, _) => providerClaim.Assigned) + .Prepend(new(GemstoneClaimTypes.UserIdentity, userIdentity)) + .Prepend(new(GemstoneClaimTypes.ProviderIdentity, providerIdentity)); return providerClaims; } diff --git a/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs b/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs index b5266466..afa14dda 100644 --- a/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs +++ b/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs @@ -127,7 +127,7 @@ public string GetIdentity(ClaimsPrincipal principal) .Claims .Select(claim => claim.Type) .Distinct() - .Select(type => new ClaimType(type)).Prepend(new ClaimType("Gemstone.AllUsers")).ToArray(); + .Select(type => new ClaimType(type)).ToArray(); string? identity = principal .FindFirst(Options.UserIdClaim ?? "sub")? @@ -158,7 +158,7 @@ private static ClaimType[] ClaimTypes { get; set; - } = [new ClaimType("Gemstone.AllUsers")]; + } = [new ClaimType(GemstoneClaimTypes.AllUsers), new (GemstoneClaimTypes.UserIdentity)]; // Static Methods From 3b743199ee999d1200d6a04eeef7ec53b08d187f Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Fri, 10 Jul 2026 16:29:10 -0400 Subject: [PATCH 3/8] Added APIAutheticationProvider --- .../APIAuthenticationProvider.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs diff --git a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs new file mode 100644 index 00000000..109a3017 --- /dev/null +++ b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs @@ -0,0 +1,85 @@ +//****************************************************************************************************** +// APIAUthenticationProvider.cs - Gbtc +// +// Copyright © 2026, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 07/09/2026 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Gemstone.Security.AuthenticationProviders; + +/// +/// Options for the class. +/// +public class APIAuthenticationProviderOptions +{ + /// + /// Function that validates a Token + /// + public Func? ValidateToken { get; set; } + +} + +public class APIToken +{ + public string Name { get; set; } + public DateTime Expiration { get; set; } + + public Claim[] Claims { get; set; } +} + + +public class APIAuthenticationProvider +{ + private readonly RequestDelegate _next; + private readonly APIAuthenticationProviderOptions Settings; + public APIAuthenticationProvider(RequestDelegate next, APIAuthenticationProviderOptions options) + { + _next = next; + Settings = options; + } + public async Task InvokeAsync(HttpContext context) + { + if (context.Request.Headers.TryGetValue("Authorization", out var authHeader)) + { + string bearerToken = authHeader.ToString(); + + if (bearerToken.StartsWith("Bearer ", System.StringComparison.OrdinalIgnoreCase)) + { + string token = bearerToken.Substring("Bearer ".Length).Trim(); + APIToken? resolvedToken = Settings.ValidateToken?.Invoke(token); + if (resolvedToken == null || resolvedToken.Expiration < DateTime.UtcNow) + { + await _next(context); + return; + } + ClaimsIdentity identity = new("APIAuthentication"); + identity.AddClaim(new(ClaimTypes.Name, resolvedToken.Name)); + identity.AddClaims(resolvedToken.Claims); + context.User = new(identity); + await _next(context); + } + } + } +} + From 19d9aeb8a204b57017d48b373dcdb419814bbd7f Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Tue, 4 Aug 2026 15:13:24 -0400 Subject: [PATCH 4/8] Cleanup --- .../IAuthenticationBuilder.cs | 9 +++++---- src/Gemstone.Security/GemstoneClaimTypes.cs | 19 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs b/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs index a2e2d02c..3cf2fc16 100644 --- a/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs +++ b/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs @@ -103,15 +103,16 @@ public IEnumerable GetAssignedClaims(string providerIdentity, ClaimsPrinc string userIdentity = provider.GetIdentity(principal); - IEnumerable principalClaims = principal.Claims + IEnumerable providerClaims = principal.Claims .Append(new(GemstoneClaimTypes.AllUsers,string.Empty)); - IEnumerable providerClaims = Setup.GetProviderClaims(providerIdentity) - .Join(principalClaims, ToKey, ToKey, (providerClaim, _) => providerClaim.Assigned) + IEnumerable assignedClaims = Setup + .GetProviderClaims(providerIdentity) + .Join(providerClaims, ToKey, ToKey, (mapping, _) => mapping.Assigned) .Prepend(new(GemstoneClaimTypes.UserIdentity, userIdentity)) .Prepend(new(GemstoneClaimTypes.ProviderIdentity, providerIdentity)); - return providerClaims; + return assignedClaims; } private static (string, string) ToKey((Claim Match, Claim) tuple) diff --git a/src/Gemstone.Security/GemstoneClaimTypes.cs b/src/Gemstone.Security/GemstoneClaimTypes.cs index 06a18e71..fa53a0f4 100644 --- a/src/Gemstone.Security/GemstoneClaimTypes.cs +++ b/src/Gemstone.Security/GemstoneClaimTypes.cs @@ -21,43 +21,42 @@ // //****************************************************************************************************** -using System.Runtime.CompilerServices; - namespace Gemstone.Security { /// - /// The Claim Types used bu the namespace + /// The claim types used by the namespace. /// public static class GemstoneClaimTypes { /// - /// Holds the unique identifier for the User. + /// Assigned claim that holds the unique identifier for the User. /// public const string UserIdentity = "Gemstone.UserIdentity"; + /// - /// Holds the unique identifier for the Authentication Provider. + /// Assigned claim that holds the unique identifier for the Authentication Provider. /// public const string ProviderIdentity = "Gemstone.ProviderIdentity"; /// - /// Is used in Matrching Claims to match any user, regardless of claims they have + /// Implicit claim that masquerades as a provider claim and applies + /// to any user principal regardless of what claims they have. /// public const string AllUsers = "Gemstone.AllUsers"; /// - /// Allows a user to access a resource. + /// Assigned claim that allows a user to access a resource. /// public const string AllowClaim = "Gemstone.ResourceAccess.Allow"; /// - /// Denies a user access to a resource. + /// Assigned claim that denies a user access to a resource. /// public const string DenyClaim = "Gemstone.ResourceAccess.Deny"; /// - /// Allows access to the value as the default access level. + /// Assigned claim that allows access to the value as the default access level. /// public const string BaseClaim = "Gemstone.ResourceAccess.Default"; - } } From 72eaeb080064c628dd41ab2c0dc8bf46c5a5edcb Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Tue, 4 Aug 2026 15:13:54 -0400 Subject: [PATCH 5/8] Change APIAuthenticationProvider from a middleware to a handler --- .../APIAuthenticationHandler.cs | 129 ++++++++++++++++++ .../APIAuthenticationProvider.cs | 85 ------------ 2 files changed, 129 insertions(+), 85 deletions(-) create mode 100644 src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs delete mode 100644 src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs diff --git a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs new file mode 100644 index 00000000..6849cf22 --- /dev/null +++ b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs @@ -0,0 +1,129 @@ +//****************************************************************************************************** +// APIAuthenticationHandler.cs - Gbtc +// +// Copyright © 2026, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 07/09/2026 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Gemstone.Security.AuthenticationProviders; + +/// +/// Options for the class. +/// +public class APIAuthenticationProviderOptions : AuthenticationSchemeOptions +{ + /// + /// Function that parses and validates an API token. + /// + public Func? ValidateToken { get; set; } +} + +/// +/// Represents metadata associated with an API token. +/// +public class APIToken +{ + /// + /// Gets or sets the name of the API user. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets the time at which the token expires. + /// + public DateTime Expiration { get; set; } + + /// + /// Gets or sets the list of claims assigned to the API user. + /// + public Claim[] Claims { get; set; } = []; +} + +/// +/// Represents an authentication handler for API users. +/// +public class APIAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + : AuthenticationHandler(options, logger, encoder) +{ + private const string HttpAuthenticationScheme = "Bearer"; + private const string AuthenticationType = "APIAuthentication"; + + private string AuthorizationHeader => Request.Headers.Authorization.ToString(); + + /// + /// Parses the Authorization header and API token. + /// + /// The result of authentication. + protected override Task HandleAuthenticateAsync() + { + AuthenticateResult result = Authenticate(); + return Task.FromResult(result); + } + + /// + /// Returns a 401 Unauthorized response with the WWW-Authenticate header. + /// + protected override Task HandleChallengeAsync(AuthenticationProperties properties) + { + Response.Headers.WWWAuthenticate = HttpAuthenticationScheme; + return base.HandleChallengeAsync(properties); + } + + private AuthenticateResult Authenticate() + { + string prefix = $"{HttpAuthenticationScheme} "; + + if (!AuthorizationHeader.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + return AuthenticateResult.NoResult(); + + string token = AuthorizationHeader[prefix.Length..].Trim(); + APIToken? resolvedToken; + + try + { + resolvedToken = Options.ValidateToken?.Invoke(token); + } + catch (Exception ex) + { + return AuthenticateResult.Fail(ex); + } + + if (resolvedToken is null) + return AuthenticateResult.NoResult(); + + if (resolvedToken.Expiration < DateTime.UtcNow) + return AuthenticateResult.Fail("Token expired"); + + ClaimsIdentity identity = new(AuthenticationType); + identity.AddClaim(new(ClaimTypes.Name, resolvedToken.Name, Options.ClaimsIssuer)); + identity.AddClaims(resolvedToken.Claims); + + ClaimsPrincipal principal = new(identity); + AuthenticationTicket ticket = new(principal, Scheme.Name); + return AuthenticateResult.Success(ticket); + } +} diff --git a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs deleted file mode 100644 index 109a3017..00000000 --- a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationProvider.cs +++ /dev/null @@ -1,85 +0,0 @@ -//****************************************************************************************************** -// APIAUthenticationProvider.cs - Gbtc -// -// Copyright © 2026, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this -// file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 07/09/2026 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Gemstone.Security.AuthenticationProviders; - -/// -/// Options for the class. -/// -public class APIAuthenticationProviderOptions -{ - /// - /// Function that validates a Token - /// - public Func? ValidateToken { get; set; } - -} - -public class APIToken -{ - public string Name { get; set; } - public DateTime Expiration { get; set; } - - public Claim[] Claims { get; set; } -} - - -public class APIAuthenticationProvider -{ - private readonly RequestDelegate _next; - private readonly APIAuthenticationProviderOptions Settings; - public APIAuthenticationProvider(RequestDelegate next, APIAuthenticationProviderOptions options) - { - _next = next; - Settings = options; - } - public async Task InvokeAsync(HttpContext context) - { - if (context.Request.Headers.TryGetValue("Authorization", out var authHeader)) - { - string bearerToken = authHeader.ToString(); - - if (bearerToken.StartsWith("Bearer ", System.StringComparison.OrdinalIgnoreCase)) - { - string token = bearerToken.Substring("Bearer ".Length).Trim(); - APIToken? resolvedToken = Settings.ValidateToken?.Invoke(token); - if (resolvedToken == null || resolvedToken.Expiration < DateTime.UtcNow) - { - await _next(context); - return; - } - ClaimsIdentity identity = new("APIAuthentication"); - identity.AddClaim(new(ClaimTypes.Name, resolvedToken.Name)); - identity.AddClaims(resolvedToken.Claims); - context.User = new(identity); - await _next(context); - } - } - } -} - From 98f6ee7c65aca9b867bdfa4fbab01f0bb1e156d9 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Tue, 4 Aug 2026 15:21:20 -0400 Subject: [PATCH 6/8] Disallow base claims on API users --- src/Gemstone.Security/AccessControl/ResourceAccessType.cs | 8 +++++++- .../AuthenticationProviders/APIAuthenticationHandler.cs | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Gemstone.Security/AccessControl/ResourceAccessType.cs b/src/Gemstone.Security/AccessControl/ResourceAccessType.cs index 98880c56..b92fd2aa 100644 --- a/src/Gemstone.Security/AccessControl/ResourceAccessType.cs +++ b/src/Gemstone.Security/AccessControl/ResourceAccessType.cs @@ -23,6 +23,7 @@ using System; using System.Security.Claims; +using Gemstone.Security.AuthenticationProviders; namespace Gemstone.Security.AccessControl; @@ -104,11 +105,16 @@ bool IsDenied() => bool IsAllowed() => user.HasClaim(GemstoneClaimTypes.AllowClaim, claimValue) || - user.HasClaim(GemstoneClaimTypes.BaseClaim, $"{access}"); + (!user.IsAPIUser() && user.HasClaim(GemstoneClaimTypes.BaseClaim, $"{access}")); return !IsDenied() && IsAllowed(); } + private static bool IsAPIUser(this ClaimsPrincipal user) + { + return user.Identity?.AuthenticationType == APIAuthenticationHandler.AuthenticationType; + } + private static void ThrowIfNotValid(ResourceAccessType access) { switch (access) diff --git a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs index 6849cf22..10b0cd4b 100644 --- a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs +++ b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs @@ -69,8 +69,12 @@ public class APIToken public class APIAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : AuthenticationHandler(options, logger, encoder) { + /// + /// Authentication type used for API authentication. + /// + public const string AuthenticationType = "APIAuthentication"; + private const string HttpAuthenticationScheme = "Bearer"; - private const string AuthenticationType = "APIAuthentication"; private string AuthorizationHeader => Request.Headers.Authorization.ToString(); From 758941809b9f1bfd11af9753bc45eaa4b0fa6749 Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 4 Aug 2026 16:13:25 -0400 Subject: [PATCH 7/8] Fixed Gemstone.AllUsers type bug --- .../OAuthAuthenticationProvider.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs b/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs index afa14dda..0484811d 100644 --- a/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs +++ b/src/Gemstone.Security/AuthenticationProviders/OAuthAuthenticationProvider.cs @@ -122,10 +122,12 @@ public OAuthAuthenticationProvider() /// public string GetIdentity(ClaimsPrincipal principal) { - if (ClaimTypes.Length == 1) + if (ClaimTypes is null) ClaimTypes = principal .Claims .Select(claim => claim.Type) + .Prepend(GemstoneClaimTypes.UserIdentity) + .Prepend(GemstoneClaimTypes.AllUsers) .Distinct() .Select(type => new ClaimType(type)).ToArray(); @@ -140,7 +142,7 @@ public string GetIdentity(ClaimsPrincipal principal) /// public IEnumerable GetClaimTypes() { - return ClaimTypes; + return ClaimTypes ?? [new ClaimType(GemstoneClaimTypes.AllUsers), new(GemstoneClaimTypes.UserIdentity)]; } /// @@ -154,11 +156,11 @@ public IEnumerable FindClaims(string claimType, string searchTex #region [ Static ] // Static Properties - private static ClaimType[] ClaimTypes + private static ClaimType[]? ClaimTypes { get; set; - } = [new ClaimType(GemstoneClaimTypes.AllUsers), new (GemstoneClaimTypes.UserIdentity)]; + } = null; // Static Methods @@ -256,7 +258,7 @@ public static IServiceCollection AddOAuthAuthenticationProvider(this IServiceCol /// The collection of services. public static IServiceCollection AddOAuthAuthenticationProvider(this IServiceCollection services, string identity, Action configure) { - return services.AddKeyedTransient(identity, (_, _) => + return services.AddKeyedSingleton(identity, (_, _) => { OAuthAuthenticationProviderOptions options = new(); configure(options); From 9c6891420c91cb16be56dccb1e1f54d37d51efd7 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Tue, 4 Aug 2026 16:50:05 -0400 Subject: [PATCH 8/8] Add extension methods for configuring APIAuthenticationHandler --- .../APIAuthenticationHandler.cs | 58 ++++++++++++++++++- .../IAuthenticationBuilder.cs | 2 +- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs index 10b0cd4b..8ded5475 100644 --- a/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs +++ b/src/Gemstone.Security/AuthenticationProviders/APIAuthenticationHandler.cs @@ -34,7 +34,7 @@ namespace Gemstone.Security.AuthenticationProviders; /// /// Options for the class. /// -public class APIAuthenticationProviderOptions : AuthenticationSchemeOptions +public class APIAuthenticationOptions : AuthenticationSchemeOptions { /// /// Function that parses and validates an API token. @@ -66,8 +66,8 @@ public class APIToken /// /// Represents an authentication handler for API users. /// -public class APIAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) - : AuthenticationHandler(options, logger, encoder) +public class APIAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + : AuthenticationHandler(options, logger, encoder) { /// /// Authentication type used for API authentication. @@ -131,3 +131,55 @@ private AuthenticateResult Authenticate() return AuthenticateResult.Success(ticket); } } + +/// +/// Extension methods for . +/// +public static class APIAuthenticationHandlerExtensions +{ + /// + /// Adds the API authentication handler to the application. + /// + /// The builder used to configure authentication + /// The builder used to configure authentication. + public static AuthenticationBuilder AddAPIAuthentication(this AuthenticationBuilder builder) + { + return builder.AddAPIAuthentication(options => { }); + } + + /// + /// Adds the API authentication handler to the application. + /// + /// The builder used to configure authentication + /// Action to configure the + /// The builder used to configure authentication. + public static AuthenticationBuilder AddAPIAuthentication(this AuthenticationBuilder builder, Action configureOptions) + { + return builder.AddAPIAuthentication("api", configureOptions); + } + + /// + /// Adds the API authentication handler to the application. + /// + /// The builder used to configure authentication + /// The name of the scheme + /// Action to configure the + /// The builder used to configure authentication. + public static AuthenticationBuilder AddAPIAuthentication(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + { + return builder.AddAPIAuthentication(authenticationScheme, null, configureOptions); + } + + /// + /// Adds the API authentication handler to the application. + /// + /// The builder used to configure authentication + /// The name of the scheme + /// The display name of the scheme + /// Action to configure the + /// The builder used to configure authentication. + public static AuthenticationBuilder AddAPIAuthentication(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action configureOptions) + { + return builder.AddScheme(authenticationScheme, displayName, configureOptions); + } +} diff --git a/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs b/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs index 3cf2fc16..41367416 100644 --- a/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs +++ b/src/Gemstone.Security/AuthenticationProviders/IAuthenticationBuilder.cs @@ -104,7 +104,7 @@ public IEnumerable GetAssignedClaims(string providerIdentity, ClaimsPrinc string userIdentity = provider.GetIdentity(principal); IEnumerable providerClaims = principal.Claims - .Append(new(GemstoneClaimTypes.AllUsers,string.Empty)); + .Append(new(GemstoneClaimTypes.AllUsers, string.Empty)); IEnumerable assignedClaims = Setup .GetProviderClaims(providerIdentity)