diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs
index 5979118..53162ad 100644
--- a/DI/XDIHelperExtension.cs
+++ b/DI/XDIHelperExtension.cs
@@ -399,31 +399,56 @@ namespace xIds.DI
///
///
///
- ///
public static void AddXIdentityServerAuthentication(
this IServiceCollection services,
- IConfiguration configuration,
- bool addApiKeyAuthentication = false
+ IConfiguration configuration
)
{
//
// Adding Requirements ...
services.AddXIdentityResourceConfiguration(configuration);
+
+ //
+ // Create Authentication Builder ...
+ // var authBuilder = services.AddAuthentication();
//
// Create Authentication Builder ...
- var authBuilder = services.AddAuthentication();
-
- //
- // For Best Support we Have to Add ApiKey Authentication First ...
- if (addApiKeyAuthentication)
+ var authBuilder = services.AddAuthentication(options =>
{
//
- authBuilder
- .AddScheme(
- XAuthenticationScheme.XApiKey.GetStringValue(),
- options => { });
- }
+ // Setting Default Authentication Schema ...
+ // For Handling Smart Schema Forwarder ...
+ options.DefaultScheme = XAuthentication.SMART_SCHEME;
+ options.DefaultChallengeScheme = XAuthentication.SMART_SCHEME;
+ options.DefaultAuthenticateScheme = XAuthentication.SMART_SCHEME;
+ })
+ .AddPolicyScheme(
+ authenticationScheme: XAuthentication.SMART_SCHEME,
+ displayName: XAuthentication.SMART_SCHEME,
+ configureOptions: options =>
+ {
+ //
+ // Configure Forward Default Selector ...
+ options.ForwardDefaultSelector = context =>
+ {
+ //
+ // Retrieve XApiKey Header Key ...
+ var apiKeyHeader = XHeader.ApiKey.GetStringValue();
+
+ //
+ // If Header Contains Key ...
+ if (context.Request.Headers.ContainsKey(apiKeyHeader))
+ {
+ return XAuthenticationScheme.XApiKey.GetStringValue();
+ }
+
+ //
+ // If not ...
+ return XAuthentication.IDENTITY_SERVER_LOCAL_API;
+ };
+ }
+ );
//
// Add JwtBearer by Smart Schema ForwardDefaultSelector ...
@@ -432,26 +457,7 @@ namespace xIds.DI
//
options.SaveToken = true;
options.RequireHttpsMetadata = false;
- // options.ForwardDefault = XAuthentication.IDENTITY_SERVER_LOCAL_API;
-
- //
- // Selecting Authentication Schema Based On Header ...
- options.ForwardDefaultSelector = context =>
- {
- //
- // if Header Contains X-Api-Key use Scheme XApiKey ...
- var apiKeyHeader = XHeader.ApiKey
- .GetStringValue()
- .ToNormalString();
- if (context.Request.Headers.ContainsKey(apiKeyHeader))
- {
- return XAuthenticationScheme.XApiKey.GetStringValue();
- }
-
- //
- // If Not, Use Local Api ...
- return XAuthentication.IDENTITY_SERVER_LOCAL_API;
- };
+ options.ForwardDefault = XAuthentication.IDENTITY_SERVER_LOCAL_API;
})
.AddLocalApi();
}
@@ -505,7 +511,13 @@ namespace xIds.DI
options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy =>
{
//
- policy.AddAuthenticationSchemes(IdentityServerConstants.LocalApi.AuthenticationScheme);
+ policy.AddAuthenticationSchemes(
+ [
+ XAuthenticationScheme.XApiKey.GetStringValue(),
+ IdentityServerConstants.LocalApi.AuthenticationScheme
+ ]);
+
+ //
policy.RequireAuthenticatedUser();
});
});
diff --git a/Helpers/XApiKeyHelper.cs b/Helpers/XApiKeyHelper.cs
index 89c00e3..184fe15 100644
--- a/Helpers/XApiKeyHelper.cs
+++ b/Helpers/XApiKeyHelper.cs
@@ -1,6 +1,8 @@
using System;
using System.Text;
using System.Security.Cryptography;
+using xIdentityModels.Constants;
+using xIdentityHelper;
namespace xIds.Helpers
{
@@ -73,5 +75,35 @@ namespace xIds.Helpers
var hash = ComputeHash(plainKey);
return hash == storedHash;
}
+
+ ///
+ /// Retrieve All ApiKey Scopes ...
+ ///
+ ///
+ public static XApiKeyScope[] GetXApiKeyScopes()
+ {
+ //
+ return [
+ XApiKeyScope.Read,
+ XApiKeyScope.Write,
+ XApiKeyScope.Manage,
+ XApiKeyScope.Admin,
+ ];
+ }
+
+ ///
+ /// Retrieve All ApiKey Policies ...
+ ///
+ ///
+ public static string[] GetXApiKeyPolicies()
+ {
+ return [
+ XPolicies.ApiKeyAccess,
+ XPolicies.ApiKeyReadAccess,
+ XPolicies.ApiKeyWriteAccess,
+ XPolicies.ApiKeyAdminAccess,
+ XPolicies.ApiKeyManageAccess,
+ ];
+ }
}
}
\ No newline at end of file
diff --git a/Providers/XApiKeyAuthenticationHandler.cs b/Providers/XApiKeyAuthenticationHandler.cs
index 9f36f61..26aa9ac 100644
--- a/Providers/XApiKeyAuthenticationHandler.cs
+++ b/Providers/XApiKeyAuthenticationHandler.cs
@@ -1,3 +1,5 @@
+using System.Linq;
+using xIds.Helpers;
using IdentityModel;
using xIds.Interfaces;
using xCommons.Constants;
@@ -5,10 +7,12 @@ using xCommons.Extensions;
using System.Threading.Tasks;
using System.Security.Claims;
using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Http;
using xIdentityService.Constants;
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
namespace xIds.Providers
@@ -41,18 +45,49 @@ namespace xIds.Providers
///
protected override async Task HandleAuthenticateAsync()
{
+ //
+ // Obtain the endpoint currently being executed
+ var endpoint = Context.GetEndpoint();
+
+ //
+ // Retrieve all AuthorizeAttribute metadata from the endpoint
+ var hasApiKeyScheme = false;
+ var apiKeyScopes = XApiKeyHelper.GetXApiKeyScopes();
+ var apiKeyPolicies = XApiKeyHelper.GetXApiKeyPolicies();
+ var authorizeAttributes = endpoint?.Metadata.GetOrderedMetadata();
+ if (
+ apiKeyPolicies.HasChild() &&
+ authorizeAttributes.HasChild()
+ )
+ {
+ //
+ // Retrieve a List of ApiKey Policies ...
+ hasApiKeyScheme = authorizeAttributes.Any(attr =>
+ apiKeyPolicies.Any(asc => asc.ToNormalString() == attr.Policy.ToNormalString()) ||
+ attr.AuthenticationSchemes.ToNormalString() == XAuthenticationScheme.XApiKey.GetStringValue().ToNormalString()
+ );
+ }
+
+ //
+ // if there is not any Policies for ApiKey Authentication
+ // or Authorization, pass no Result ...
+ if (!hasApiKeyScheme)
+ {
+ return AuthenticateResult.NoResult();
+ }
+
//
var apiKeyHeader = HeaderName;
if (!Request.Headers.ContainsKey(apiKeyHeader))
{
- return AuthenticateResult.NoResult();
+ return AuthenticateResult.Fail("ApiKey not Exists ...");
}
//
var apiKey = Request.Headers[apiKeyHeader].ToString();
if (apiKey.IsNullOrEmpty())
{
- return AuthenticateResult.NoResult();
+ return AuthenticateResult.Fail("ApiKey not Provided ...");
}
// Extract Client IP ...
diff --git a/Providers/XApiKeyAuthorizationFilter.cs b/Providers/XApiKeyAuthorizationFilter.cs
new file mode 100644
index 0000000..1812d93
--- /dev/null
+++ b/Providers/XApiKeyAuthorizationFilter.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Linq;
+using xIds.Helpers;
+using IdentityModel;
+using xIds.Interfaces;
+using xIdentityHelper;
+using xCommons.Constants;
+using xCommons.Extensions;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using xIdentityModels.Constants;
+using xIdentityService.Constants;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Authorization;
+using System.Threading.Tasks;
+
+namespace xIds.Providers
+{
+ [AttributeUsage(
+ AttributeTargets.Class | AttributeTargets.Method,
+ AllowMultiple = false,
+ Inherited = true
+ )]
+ public class XApiKeyAuthorizationFilter : AuthorizeAttribute, IAuthorizationFilter
+ {
+ // ///
+ // /// Optional required scope for this endpoint.
+ // /// Example: [XApiKeyAuthorizationFilter(RequiredScope = "read")]
+ // ///
+ // public string RequiredScope { get; set; }
+
+ // ///
+ // /// If false, a missing header will only result in NoResult (anonymous)
+ // /// instead of a 401. Default is true (must provide a key).
+ // ///
+ // public bool ApiKeyRequired { get; set; } = true;
+
+ // ///
+ // /// Optional allowed scopes. If empty, no scope enforcement.
+ // ///
+ // public string[] AllowedScopes { get; set; }
+
+ public XApiKeyAuthorizationFilter() { }
+
+ ///
+ /// Authorization Filtering ...
+ ///
+ ///
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ Task.FromResult(OnAuthorizationAsync(context));
+ }
+
+ ///
+ /// Handle Authorization Filtering Using Tasks ...
+ ///
+ ///
+ ///
+ private async Task OnAuthorizationAsync(AuthorizationFilterContext context)
+ {
+ //
+ // Access HttpContext ...
+ var http = context.HttpContext;
+
+ //
+ try
+ {
+ //
+ // Obtain the endpoint currently being executed
+ var endpoint = http.GetEndpoint();
+
+ //
+ // Retrieve all AuthorizeAttribute metadata from the endpoint
+ var hasApiKeyScheme = false;
+ var apiKeyScopes = XApiKeyHelper.GetXApiKeyScopes();
+ var apiKeyPolicies = XApiKeyHelper.GetXApiKeyPolicies();
+ var authorizeAttributes = endpoint?.Metadata.GetOrderedMetadata();
+ if (
+ apiKeyPolicies.HasChild() &&
+ authorizeAttributes.HasChild()
+ )
+ {
+ //
+ // Retrieve a List of ApiKey Policies ...
+ hasApiKeyScheme = authorizeAttributes.Any(attr =>
+ apiKeyPolicies.Any(asc => asc.ToNormalString() == attr.Policy.ToNormalString())
+ );
+ }
+
+ //
+ // if there is not any Policies for ApiKey Authentication
+ // or Authorization, pass no Result ...
+ if (!hasApiKeyScheme)
+ {
+ return;
+ }
+
+ //
+ // Retrieve the application provider from DI ...
+ var applicationProvider = http.RequestServices
+ .GetService(typeof(IXApplicationProvider))
+ as IXApplicationProvider;
+ if (applicationProvider.IsNull())
+ {
+ //
+ context.Result = new UnauthorizedObjectResult("Provider Service Not Registered ...");
+ return;
+ }
+
+ //
+ // Checking Header ...
+ var apiKeyHeader = XHeader.ApiKey.GetStringValue();
+ var apiKeyAuthentication = XAuthenticationScheme.XApiKey.GetStringValue();
+ if (!http.Request.Headers.ContainsKey(apiKeyHeader))
+ {
+ //
+ context.Result = new UnauthorizedObjectResult("ApiKey not Exists ...");
+ return;
+ }
+
+ //
+ // Extract ApiKey from Header ...
+ var apiKey = http.Request.Headers[apiKeyHeader].ToString();
+ if (apiKey.IsNullOrEmpty())
+ {
+ //
+ context.Result = new UnauthorizedObjectResult("ApiKey not Provided ...");
+ return;
+ }
+
+ //
+ // Extract Client IP ...
+ var clientIP = http.Request.HttpContext.Connection.RemoteIpAddress?.ToString();
+
+ //
+ // Validating ApiKey ...
+ var validationResult = await applicationProvider
+ .ValidateApiKey(
+ apiKey: apiKey,
+ clientIP: clientIP
+ );
+ if (validationResult.Errors.HasChild())
+ {
+ //
+ var message = validationResult.Errors.ToListString('\n');
+ context.Result = new UnauthorizedObjectResult(message);
+ return;
+ }
+
+ //
+ // Creating Claims ...
+ var claims = new List {
+ //
+ // Owner Identifier of API Key ...
+ new(ClaimTypes.Name, validationResult.OwnerId),
+ new(JwtClaimTypes.Subject, validationResult.OwnerId),
+ //
+ // Application Id ...
+ new(XCustomClaims.ApplicationId, validationResult.ApplicationId.ToString()),
+ //
+ // Authentication Type ...
+ new(XCustomClaims.AuthType, apiKeyAuthentication),
+ };
+
+ //
+ // Add Scopes ...
+ if (validationResult.Scopes != null)
+ {
+ //
+ foreach (var scope in validationResult.Scopes)
+ {
+ claims.Add(new Claim(JwtClaimTypes.Scope, scope));
+ }
+ }
+
+ //
+ // Create Principal and Ticket ...
+ var identity = new ClaimsIdentity(claims, apiKeyAuthentication);
+ var principal = new ClaimsPrincipal(identity);
+
+ //
+ http.User = principal;
+ }
+ catch
+ {
+ context.Result = new UnauthorizedObjectResult("ApiKey authorization failed ...");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Startup.cs b/Startup.cs
index 546448b..000389d 100644
--- a/Startup.cs
+++ b/Startup.cs
@@ -67,10 +67,7 @@ namespace xIds
//
// Register Authentication ...
- services.AddXIdentityServerAuthentication(
- configuration: Configuration,
- addApiKeyAuthentication: true
- );
+ services.AddXIdentityServerAuthentication(Configuration);
//
// Register Authorization ...
diff --git a/Validators/XResourceOwnerPasswordValidator.cs b/Validators/XResourceOwnerPasswordValidator.cs
index b4ece99..e65c179 100644
--- a/Validators/XResourceOwnerPasswordValidator.cs
+++ b/Validators/XResourceOwnerPasswordValidator.cs
@@ -1,14 +1,14 @@
using System;
+using xIds.Interfaces;
+using xCommons.Extensions;
+using xExceptions.Constants;
using System.Threading.Tasks;
using IdentityServer4.Events;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;
-using Microsoft.Extensions.Logging;
-using xCommons.Extensions;
-using xExceptions.Constants;
using xIdentityModels.Navigations;
-using xIds.Interfaces;
+using Microsoft.Extensions.Logging;
using static IdentityModel.OidcConstants;
namespace xIds.Validators