diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..641b49f8
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,93 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "master", "develop" ]
+ pull_request:
+ branches: [ "master", "develop" ]
+ schedule:
+ - cron: '0 0 * * 1' # Weekly schedule at midnight UTC on Mondays
+
+jobs:
+ analyze:
+ name: Analyze (${{ matrix.language }})
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
+ # - https://gh.io/supported-runners-and-hardware-resources
+ # - https://gh.io/using-larger-runners (GitHub.com only)
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
+ timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
+ permissions:
+ # required for all workflows
+ security-events: write
+
+ # required to fetch internal or private CodeQL packs
+ packages: read
+
+ # only required for workflows in private repositories
+ actions: read
+ contents: read
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - language: csharp
+ build-mode: autobuild
+ # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
+ # Use `c-cpp` to analyze code written in C, C++ or both
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ # If the analyze step fails for one of the languages you are analyzing with
+ # "We were unable to automatically build your code", modify the matrix above
+ # to set the build mode to "manual" for that language. Then modify this step
+ # to build your code.
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+ - if: matrix.build-mode == 'manual'
+ shell: bash
+ run: |
+ echo 'If you are using a "manual" build mode for one or more of the' \
+ 'languages you are analyzing, replace this with the commands to build' \
+ 'your code, for example:'
+ echo ' make bootstrap'
+ echo ' make release'
+ exit 1
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj b/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj
index cd1ba096..6beffc4c 100644
--- a/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj
+++ b/Abblix.DependencyInjection/Abblix.DependencyInjection.csproj
@@ -20,6 +20,8 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
+ 1.1.0.0
+ 1.1.0.0
diff --git a/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj b/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj
index f7c1ef1a..ff1a425e 100644
--- a/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj
+++ b/Abblix.Jwt.UnitTests/Abblix.Jwt.UnitTests.csproj
@@ -10,9 +10,9 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs b/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs
index 9bc4302c..fde7d790 100644
--- a/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs
+++ b/Abblix.Jwt.UnitTests/JwtEncryptionTests.cs
@@ -66,8 +66,8 @@ public async Task JwtFullCycleTest()
{
ValidateAudience = aud => Task.FromResult(token.Payload.Audiences.SequenceEqual(aud)),
ValidateIssuer = iss => Task.FromResult(iss == token.Payload.Issuer),
- ResolveTokenDecryptionKeys = _ => new [] { EncryptingKey }.AsAsync(),
- ResolveIssuerSigningKeys = _ => new [] { SigningKey }.AsAsync(),
+ ResolveTokenDecryptionKeys = _ => new [] { EncryptingKey }.ToAsyncEnumerable(),
+ ResolveIssuerSigningKeys = _ => new [] { SigningKey }.ToAsyncEnumerable(),
};
var result = Assert.IsType(await validator.ValidateAsync(jwt, parameters));
diff --git a/Abblix.Jwt/Abblix.Jwt.csproj b/Abblix.Jwt/Abblix.Jwt.csproj
index 0357cc7b..f1fbc345 100644
--- a/Abblix.Jwt/Abblix.Jwt.csproj
+++ b/Abblix.Jwt/Abblix.Jwt.csproj
@@ -20,11 +20,14 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
+ 1.1.0.0
+ 1.1.0.0
-
+
+
diff --git a/Abblix.Jwt/JsonWebKeyFactory.cs b/Abblix.Jwt/JsonWebKeyFactory.cs
index 1ee7b019..2b3e964f 100644
--- a/Abblix.Jwt/JsonWebKeyFactory.cs
+++ b/Abblix.Jwt/JsonWebKeyFactory.cs
@@ -21,6 +21,7 @@
// info@abblix.com
using System.Security.Cryptography;
+using Abblix.Utils;
using Microsoft.IdentityModel.Tokens;
namespace Abblix.Jwt;
@@ -53,12 +54,14 @@ public static class JsonWebKeyFactory
rsa.KeySize = keySize;
var parameters = rsa.ExportParameters(true);
+ var parametersExponent = parameters.Exponent;
var key = new JsonWebKey
{
KeyType = "RSA",
+ KeyId = parameters.ToKeyId(),
Algorithm = algorithm,
Usage = usage,
- RsaExponent = parameters.Exponent,
+ RsaExponent = parametersExponent,
RsaModulus = parameters.Modulus,
PrivateKey = parameters.D,
FirstPrimeFactor = parameters.P,
@@ -70,4 +73,26 @@ public static class JsonWebKeyFactory
return key;
}
+
+ private static string ToKeyId(this RSAParameters parameters)
+ {
+ var keyMaterial = (parameters.Modulus, parameters.Exponent) switch
+ {
+ ({} modulus, {} exponent) => modulus.Concat(exponent),
+ ({} modulus, null) => modulus,
+ (null, {} exponent) => exponent,
+ (null, null) => Array.Empty(),
+ };
+
+ // Compute the SHA-256 hash of the concatenated string
+ return SHA256.HashData(keyMaterial).ToHexString();
+ }
+
+ private static byte[] Concat(this byte[] modulus, byte[] exponent)
+ {
+ var buffer = new byte[modulus.Length + exponent.Length];
+ Array.Copy(modulus, buffer, modulus.Length);
+ Array.Copy(exponent, 0, buffer, modulus.Length, exponent.Length);
+ return buffer;
+ }
}
diff --git a/Abblix.Jwt/JsonWebTokenValidator.cs b/Abblix.Jwt/JsonWebTokenValidator.cs
index 712e4763..c6c9b762 100644
--- a/Abblix.Jwt/JsonWebTokenValidator.cs
+++ b/Abblix.Jwt/JsonWebTokenValidator.cs
@@ -96,9 +96,9 @@ private static JwtValidationResult Validate(string jwt, ValidationParameters par
var signingKeys = resolveIssuerSigningKeys(securityToken.Issuer);
if (keyId.HasValue())
- signingKeys = signingKeys.WhereAsync(key => key.KeyId == keyId);
+ signingKeys = signingKeys.Where(key => key.KeyId == keyId);
- return signingKeys.SelectAsync(key => key.ToSecurityKey()).ToListAsync().Result;
+ return signingKeys.Select(key => key.ToSecurityKey()).ToListAsync().Result;
};
}
@@ -109,9 +109,9 @@ private static JwtValidationResult Validate(string jwt, ValidationParameters par
var decryptionKeys = resolveTokenDecryptionKeys(securityToken.Issuer);
if (keyId.HasValue())
- decryptionKeys = decryptionKeys.WhereAsync(key => key.KeyId == keyId);
+ decryptionKeys = decryptionKeys.Where(key => key.KeyId == keyId);
- return decryptionKeys.SelectAsync(key => key.ToSecurityKey()).ToListAsync().Result;
+ return decryptionKeys.Select(key => key.ToSecurityKey()).ToListAsync().Result;
};
var handler = new JwtSecurityTokenHandler();
diff --git a/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj b/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj
index ba8ae871..ffc9480b 100644
--- a/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj
+++ b/Abblix.Oidc.Server.Mvc/Abblix.Oidc.Server.Mvc.csproj
@@ -22,6 +22,8 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
+ 1.1.0.0
+ 1.1.0.0
@@ -35,7 +37,7 @@
-
+
diff --git a/Abblix.Oidc.Server.Mvc/ActionResults/CookieOptionsExtensions.cs b/Abblix.Oidc.Server.Mvc/ActionResults/CookieOptionsExtensions.cs
index 6e07b6f4..d7c4cb5b 100644
--- a/Abblix.Oidc.Server.Mvc/ActionResults/CookieOptionsExtensions.cs
+++ b/Abblix.Oidc.Server.Mvc/ActionResults/CookieOptionsExtensions.cs
@@ -24,6 +24,7 @@
using Microsoft.AspNetCore.Http;
namespace Abblix.Oidc.Server.Mvc.ActionResults;
+
public static class CookieOptionsExtensions
{
///
diff --git a/Abblix.Oidc.Server.Mvc/Attributes/AbsoluteUriAttribute.cs b/Abblix.Oidc.Server.Mvc/Attributes/AbsoluteUriAttribute.cs
index c5301585..85bca900 100644
--- a/Abblix.Oidc.Server.Mvc/Attributes/AbsoluteUriAttribute.cs
+++ b/Abblix.Oidc.Server.Mvc/Attributes/AbsoluteUriAttribute.cs
@@ -47,9 +47,9 @@ public sealed class AbsoluteUriAttribute : ValidationAttribute
/// Determines whether the specified value of the object is valid.
///
/// The value of the object to validate.
- /// The context information about the object being validated.
+ /// The context information about the object being validated.
/// if the value is a valid absolute URI, otherwise an error .
- protected override ValidationResult? IsValid(object? value, ValidationContext context)
+ protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
=> value switch
{
null => ValidationResult.Success, // absence is OK, apply [Required] if needed
@@ -57,14 +57,14 @@ public sealed class AbsoluteUriAttribute : ValidationAttribute
string str when string.IsNullOrEmpty(str) => ValidationResult.Success,
Uri uri when string.IsNullOrEmpty(uri.OriginalString) => ValidationResult.Success,
- string str when Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out var uri) => IsValid(uri, context),
+ string str when Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out var uri) => IsValid(uri, validationContext),
Uri { IsAbsoluteUri: true, Scheme: var scheme } when RequireScheme.HasValue() && !string.Equals(RequireScheme, scheme, StringComparison.OrdinalIgnoreCase)
- => new ValidationResult($"{context.GetName()} value must use {RequireScheme} scheme."),
+ => new ValidationResult($"{validationContext.GetName()} value must use {RequireScheme} scheme."),
Uri { IsAbsoluteUri: true } => ValidationResult.Success,
- Uri => new ValidationResult($"{context.GetName()} value is not absolute."),
- _ => new ValidationResult($"{context.GetName()} is not Uri, but {value.GetType().Name}."),
+ Uri => new ValidationResult($"{validationContext.GetName()} value is not absolute."),
+ _ => new ValidationResult($"{validationContext.GetName()} is not Uri, but {value.GetType().Name}."),
};
}
diff --git a/Abblix.Oidc.Server.Mvc/Attributes/AllowedValuesAttribute.cs b/Abblix.Oidc.Server.Mvc/Attributes/AllowedValuesAttribute.cs
index 67e3c3d7..1596b981 100644
--- a/Abblix.Oidc.Server.Mvc/Attributes/AllowedValuesAttribute.cs
+++ b/Abblix.Oidc.Server.Mvc/Attributes/AllowedValuesAttribute.cs
@@ -51,23 +51,23 @@ public AllowedValuesAttribute(params string[] allowedValues)
/// Determines whether the specified value of the object is valid.
///
/// The value of the object to validate.
- /// The context information about the object being validated.
+ /// The context information about the object being validated.
/// if the value is among the allowed values,
/// otherwise an error .
///
- protected override ValidationResult? IsValid(object? value, ValidationContext context)
+ protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
return value switch
{
null => ValidationResult.Success,
- string[][] stringValues => IsValid(stringValues.SelectMany(stringValue => stringValue), context),
- string[] stringValues => IsValid(stringValues, context),
+ string[][] stringValues => IsValid(stringValues.SelectMany(stringValue => stringValue)),
+ string[] stringValues => IsValid(stringValues),
string stringValue => IsValid(stringValue),
_ => throw new InvalidOperationException($"The type {value.GetType()} is not supported by {nameof(AllowedValuesAttribute)}"),
};
}
- private ValidationResult? IsValid(IEnumerable values, ValidationContext context)
+ private ValidationResult? IsValid(IEnumerable values)
{
foreach (var value in values)
{
diff --git a/Abblix.Oidc.Server.Mvc/Attributes/ConditionalRequiredAttribute.cs b/Abblix.Oidc.Server.Mvc/Attributes/ConditionalRequiredAttribute.cs
index 2f4ef2e1..30f64e62 100644
--- a/Abblix.Oidc.Server.Mvc/Attributes/ConditionalRequiredAttribute.cs
+++ b/Abblix.Oidc.Server.Mvc/Attributes/ConditionalRequiredAttribute.cs
@@ -38,17 +38,17 @@ public abstract class ConditionalRequiredAttribute : RequiredAttribute
/// Determines whether the specified value of the object is valid based on a custom condition.
///
/// The value of the object to validate.
- /// The context information about the object being validated.
+ /// The context information about the object being validated.
///
/// if the condition is not met or if the value is valid as per the base ;
/// otherwise, an error .
///
- protected override ValidationResult? IsValid(object? value, ValidationContext context)
+ protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
- if (!IsRequired(context.ObjectInstance))
+ if (!IsRequired(validationContext.ObjectInstance))
return ValidationResult.Success;
- return base.IsValid(value, context);
+ return base.IsValid(value, validationContext);
}
///
diff --git a/Abblix.Oidc.Server.Mvc/AutoPostFormatter.cs b/Abblix.Oidc.Server.Mvc/AutoPostFormatter.cs
index 62ecdac2..562e6200 100644
--- a/Abblix.Oidc.Server.Mvc/AutoPostFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/AutoPostFormatter.cs
@@ -57,14 +57,14 @@ public AutoPostFormatter(IParametersProvider parametersProvider, Uri action)
/// This method overrides the base class implementation to write an HTML form with the specified parameters.
///
/// The context for the output formatter.
- /// The encoding to use for the response.
+ /// The encoding to use for the response.
/// A task that represents the asynchronous write operation.
- public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding encoding)
+ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
if (context.Object == null)
return;
- var settings = new XmlWriterSettings { Async = true, Encoding = encoding };
+ var settings = new XmlWriterSettings { Async = true, Encoding = selectedEncoding };
await using var writer = XmlWriter.Create(context.HttpContext.Response.Body, settings);
var parameters = _parametersProvider.GetParameters(context.Object);
diff --git a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
index f252c772..cb1b5f51 100644
--- a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
+++ b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
@@ -34,7 +34,6 @@
using Abblix.Oidc.Server.Features.LogoutNotification;
using Abblix.Oidc.Server.Features.UserInfo;
using Abblix.Oidc.Server.Model;
-using Abblix.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@@ -163,8 +162,8 @@ public async Task> KeysAsync(
if (!options.Value.EnabledEndpoints.HasFlag(OidcEndpoints.Keys))
return NotFound();
- var keys = await serviceKeysProvider.GetSigningKeys().ToListAsync();
- return Json(new JsonWebKeySet(keys.ToArray()));
+ var keys = await serviceKeysProvider.GetSigningKeys().ToArrayAsync();
+ return Json(new JsonWebKeySet(keys));
}
private static JsonResult Json(object response) => new(
diff --git a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs
index 560be8de..16d3a5a1 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationErrorFormatter.cs
@@ -22,6 +22,7 @@
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
+using Abblix.Oidc.Server.Features.Issuer;
using Abblix.Oidc.Server.Model;
using Abblix.Oidc.Server.Mvc.Binders;
using Abblix.Utils;
@@ -37,15 +38,23 @@ namespace Abblix.Oidc.Server.Mvc.Formatters;
public class AuthorizationErrorFormatter
{
///
- /// Initializes a new instance of with the necessary parameter provider.
+ /// Initializes a new instance of with necessary dependencies for
+ /// response parameter handling.
///
- /// The provider for extracting and formatting response parameters.
- public AuthorizationErrorFormatter(IParametersProvider parametersProvider)
+ /// The provider for extracting and formatting response parameters,
+ /// which includes details like state and error descriptions.
+ /// The provider for the issuer URL, ensuring the 'iss' claim is correctly
+ /// included in error responses if applicable.
+ public AuthorizationErrorFormatter(
+ IParametersProvider parametersProvider,
+ IIssuerProvider issuerProvider)
{
_parametersProvider = parametersProvider;
+ _issuerProvider = issuerProvider;
}
private readonly IParametersProvider _parametersProvider;
+ protected readonly IIssuerProvider _issuerProvider;
///
/// Asynchronously formats an authorization error response into an HTTP action result,
@@ -54,12 +63,8 @@ public AuthorizationErrorFormatter(IParametersProvider parametersProvider)
/// The original authorization request that led to the error.
/// The authorization error to be formatted.
/// A task that resolves to the formatted HTTP action result.
- public Task FormatResponseAsync(
- AuthorizationRequest request,
- AuthorizationError error)
- {
- return Task.FromResult(FormatResponse(request, error));
- }
+ public Task FormatResponseAsync(AuthorizationRequest request, AuthorizationError error)
+ => Task.FromResult(FormatResponse(request, error));
///
/// Internally formats the authorization error response based on the request context and the error's properties,
@@ -72,18 +77,18 @@ private ActionResult FormatResponse(AuthorizationRequest request, AuthorizationE
{
switch (error)
{
- case { RedirectUri: not null }:
+ case { RedirectUri: {} redirectUri }:
var response = new AuthorizationResponse
{
+ State = request.State,
+ Issuer = _issuerProvider.GetIssuer(),
Error = error.Error,
ErrorDescription = error.ErrorDescription,
ErrorUri = error.ErrorUri,
-
- State = request.State,
};
- return ToActionResult(response, error.ResponseMode, error.RedirectUri);
+ return ToActionResult(response, error.ResponseMode, redirectUri);
default:
return new BadRequestObjectResult(new ErrorResponse(error.Error, error.ErrorDescription));
diff --git a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs
index bf16322b..dd499c3e 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/AuthorizationResponseFormatter.cs
@@ -24,6 +24,7 @@
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Common.Exceptions;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
+using Abblix.Oidc.Server.Features.Issuer;
using Abblix.Oidc.Server.Features.SessionManagement;
using Abblix.Oidc.Server.Features.Storages;
using Abblix.Oidc.Server.Model;
@@ -59,14 +60,15 @@ internal class AuthorizationResponseFormatter : AuthorizationErrorFormatter, IAu
/// The service responsible for managing user sessions within the authorization process.
///
/// Accessor to obtain the current HTTP context, facilitating access to request and response objects.
-
- public AuthorizationResponseFormatter(
- IOptions options,
+ ///
+ /// Provides issuer information crucial for generating consistent authorization responses.
+ public AuthorizationResponseFormatter(IOptions options,
IAuthorizationRequestStorage authorizationRequestStorage,
IParametersProvider parametersProvider,
ISessionManagementService sessionManagementService,
- IHttpContextAccessor httpContextAccessor)
- : base(parametersProvider)
+ IHttpContextAccessor httpContextAccessor,
+ IIssuerProvider issuerProvider)
+ : base(parametersProvider, issuerProvider)
{
_options = options;
_authorizationRequestStorage = authorizationRequestStorage;
@@ -111,24 +113,21 @@ public async Task FormatResponseAsync(
return await RedirectAsync(
_options.Value.LoginUri.NotNull(nameof(OidcOptions.LoginUri)), response.Model);
- case SuccessfullyAuthenticated { Model.RedirectUri: not null } success:
+ case SuccessfullyAuthenticated { Model.RedirectUri: { } redirectUri } success:
var modelResponse = new AuthorizationResponse
{
State = response.Model.State,
+ Issuer = _issuerProvider.GetIssuer(),
Scope = string.Join(' ', response.Model.Scope),
-
Code = success.Code,
-
TokenType = success.TokenType,
AccessToken = success.AccessToken?.EncodedJwt,
-
IdToken = success.IdToken?.EncodedJwt,
-
SessionState = success.SessionState,
};
- var actionResult = ToActionResult(modelResponse, success.ResponseMode, response.Model.RedirectUri);
+ var actionResult = ToActionResult(modelResponse, success.ResponseMode, redirectUri);
if (_sessionManagementService.Enabled &&
success.SessionId.HasValue() &&
diff --git a/Abblix.Oidc.Server.Mvc/Formatters/PushedAuthorizationResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/PushedAuthorizationResponseFormatter.cs
index e39c8280..9be11227 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/PushedAuthorizationResponseFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/PushedAuthorizationResponseFormatter.cs
@@ -23,6 +23,7 @@
using Abblix.Oidc.Server.Common.Exceptions;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
using Abblix.Oidc.Server.Endpoints.PushedAuthorization.Interfaces;
+using Abblix.Oidc.Server.Features.Issuer;
using Abblix.Oidc.Server.Model;
using Abblix.Oidc.Server.Mvc.Binders;
using Abblix.Oidc.Server.Mvc.Formatters.Interfaces;
@@ -39,11 +40,12 @@ public class PushedAuthorizationResponseFormatter : AuthorizationErrorFormatter,
{
///
/// Initializes a new instance of the class
- /// with the specified parameters provider.
+ /// with the specified parameters' provider.
///
/// Provides access to parameters used in formatting the response.
- public PushedAuthorizationResponseFormatter(IParametersProvider parametersProvider)
- : base(parametersProvider)
+ /// Provides access to the issuer information used in responses.
+ public PushedAuthorizationResponseFormatter(IParametersProvider parametersProvider, IIssuerProvider issuerProvider)
+ : base(parametersProvider, issuerProvider)
{
}
diff --git a/Abblix.Oidc.Server.Mvc/Formatters/TokenResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/TokenResponseFormatter.cs
index 14d2bdae..eee3ac66 100644
--- a/Abblix.Oidc.Server.Mvc/Formatters/TokenResponseFormatter.cs
+++ b/Abblix.Oidc.Server.Mvc/Formatters/TokenResponseFormatter.cs
@@ -72,8 +72,8 @@ private static ActionResult FormatResponse(Endpoints.Token.Interf
IdToken = success.IdToken?.EncodedJwt,
};
- case TokenErrorResponse error:
- return new BadRequestObjectResult(error);
+ case TokenErrorResponse { Error: var error, ErrorDescription: var description }:
+ return new BadRequestObjectResult(new ErrorResponse(error, description));
default:
throw new ArgumentOutOfRangeException(nameof(response));
diff --git a/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs
index eca2b4ed..9fa05202 100644
--- a/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs
@@ -42,7 +42,6 @@ public record AuthorizationRequest
/// The specific scopes requested determine the access privileges granted.
///
[BindProperty(SupportsGet = true, Name = Parameters.Scope)]
- [AllowedValues(Scopes.OpenId, Scopes.Profile, Scopes.Email, Scopes.Phone, Scopes.Address, Scopes.OfflineAccess)]
[ModelBinder(typeof(SpaceSeparatedValuesBinder))]
public string[] Scope { get; init; } = Array.Empty();
@@ -195,7 +194,7 @@ public record AuthorizationRequest
/// resource.
///
[BindProperty(SupportsGet = true, Name = Parameters.Resource)]
- public Uri[]? Resource { get; set; }
+ public Uri[]? Resources { get; set; }
public Core.AuthorizationRequest Map() => new()
{
@@ -219,6 +218,6 @@ public record AuthorizationRequest
CodeChallengeMethod = CodeChallengeMethod,
IdTokenHint = IdTokenHint,
ClaimsLocales = ClaimsLocales,
- Resource = Resource,
+ Resources = Resources,
};
}
diff --git a/Abblix.Oidc.Server.Mvc/Model/AuthorizationResponse.cs b/Abblix.Oidc.Server.Mvc/Model/AuthorizationResponse.cs
index ebd63b23..16dc5c38 100644
--- a/Abblix.Oidc.Server.Mvc/Model/AuthorizationResponse.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/AuthorizationResponse.cs
@@ -26,6 +26,8 @@ namespace Abblix.Oidc.Server.Mvc.Model;
///
/// Represents an authorization response containing the result of an authorization request.
+/// This includes potential error details, codes, tokens, and additional related data used
+/// in both success and error states of OAuth 2.0 and OpenID Connect authorization processes.
///
public record AuthorizationResponse
{
@@ -46,35 +48,39 @@ private static class Parameters
public const string Scope = "scope";
public const string SessionState = "session_state";
+
+ public const string Issuer = "iss";
}
///
- /// The error code if the authorization request has failed.
+ /// The error code if the authorization request has failed, identifying the specific error that occurred.
///
[JsonPropertyName(Parameters.Error)]
public string? Error { init; get; }
///
- /// The human-readable description of the error if the authorization request has failed.
+ /// A human-readable explanation of the error, providing detailed insight into why the authorization request failed.
///
[JsonPropertyName(Parameters.ErrorDescription)]
public string? ErrorDescription { init; get; }
///
- /// The URI for more information about the error if the authorization request has failed.
+ /// A URI to a web resource that provides more information about the error, helping clients understand or
+ /// mitigate the issue.
///
[JsonPropertyName(Parameters.ErrorUri)]
public Uri? ErrorUri { init; get; }
///
- /// The state parameter that was included in the initial authorization request.
+ /// The state parameter originally provided by the client in the authorization request, returned unaltered in
+ /// the response to maintain state between the client and the authorization server.
///
[JsonPropertyName(Parameters.State)]
public string? State { init; get; }
///
- /// The authorization code generated by the authorization server.
- /// This code is used in the authorization code flow of OAuth 2.0 to obtain an access token.
+ /// The authorization code generated by the authorization server. This code must be exchanged for an access token
+ /// at the token endpoint as part of the authorization code flow.
///
[JsonPropertyName(Parameters.Code)]
public string? Code { get; set; }
@@ -114,4 +120,11 @@ private static class Parameters
///
[JsonPropertyName(Parameters.SessionState)]
public string? SessionState { get; set; }
+
+ ///
+ /// The issuer of the response, which is typically the URL of the authorization server from
+ /// which the response originated.
+ ///
+ [JsonPropertyName(Parameters.Issuer)]
+ public string? Issuer { get; set; }
}
diff --git a/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs
index 079fbdc7..351f3b5e 100644
--- a/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs
@@ -289,7 +289,7 @@ public record ClientRegistrationRequest
/// PKCE enhances the security of the OAuth authorization code flow, particularly for public clients.
///
[JsonPropertyName(Parameters.PkceRequired)]
- public bool PkceRequired { get; set; }
+ public bool? PkceRequired { get; set; } = false;
///
/// Indicates whether this client is allowed to request offline access.
@@ -303,7 +303,7 @@ public record ClientRegistrationRequest
/// This is relevant for scenarios where the client needs to be notified when the user logs out.
///
[JsonPropertyName(Parameters.BackChannelLogoutSessionRequired)]
- public bool BackChannelLogoutSessionRequired { get; set; }
+ public bool? BackChannelLogoutSessionRequired { get; set; } = false;
///
/// URI used for back-channel logout. This URI is called by the OpenID Provider to initiate a logout for the client.
@@ -324,7 +324,7 @@ public record ClientRegistrationRequest
/// This is used to manage user sessions in scenarios involving multiple clients.
///
[JsonPropertyName(Parameters.FrontChannelLogoutSessionRequired)]
- public bool FrontChannelLogoutSessionRequired { get; set; }
+ public bool? FrontChannelLogoutSessionRequired { get; set; } = false;
///
/// Array of URIs to which the OP will redirect the user's user agent after logging out.
diff --git a/Abblix.Oidc.Server.Mvc/Model/EndSessionRequest.cs b/Abblix.Oidc.Server.Mvc/Model/EndSessionRequest.cs
index ed66c59b..9b60795e 100644
--- a/Abblix.Oidc.Server.Mvc/Model/EndSessionRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/EndSessionRequest.cs
@@ -83,7 +83,7 @@ public record EndSessionRequest
/// This is typically used to prevent accidental logouts and ensure intentional user actions.
///
[BindProperty(SupportsGet = true, Name = Parameters.Confirmed)]
- public bool Confirmed { get; set; } = false;
+ public bool? Confirmed { get; set; } = false;
///
/// Maps the properties of this end session request to a object.
diff --git a/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs b/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs
index 6fd463b9..d80b1541 100644
--- a/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs
+++ b/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs
@@ -96,14 +96,14 @@ public record TokenRequest
/// The username of the resource owner, used in the password grant type.
/// This represents the credentials of the user for whom the client is requesting the token.
///
- [BindProperty(SupportsGet = true, Name = Parameters.Username)]
+ [BindProperty(Name = Parameters.Username)]
public string? UserName { get; set; }
///
/// The password of the resource owner, used in the password grant type.
/// Along with the username, this forms the user credentials required for the password grant type.
///
- [BindProperty(SupportsGet = true, Name = Parameters.Password)]
+ [BindProperty(Name = Parameters.Password)]
public string? Password { get; set; }
///
@@ -125,7 +125,7 @@ public Core.TokenRequest Map()
GrantType = GrantType,
Code = Code,
Password = Password,
- Resource = Resource,
+ Resources = Resource,
Scope = Scope,
RefreshToken = RefreshToken,
RedirectUri = RedirectUri,
diff --git a/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj b/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj
index 8fd4e6c7..c8b1b580 100644
--- a/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj
+++ b/Abblix.Oidc.Server.Tests/Abblix.Oidc.Server.Tests.csproj
@@ -7,9 +7,9 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Abblix.Oidc.Server.UnitTests/Abblix.Oidc.Server.UnitTests.csproj b/Abblix.Oidc.Server.UnitTests/Abblix.Oidc.Server.UnitTests.csproj
index c632d31a..37e28aad 100644
--- a/Abblix.Oidc.Server.UnitTests/Abblix.Oidc.Server.UnitTests.csproj
+++ b/Abblix.Oidc.Server.UnitTests/Abblix.Oidc.Server.UnitTests.csproj
@@ -6,9 +6,9 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Abblix.Oidc.Server/Abblix.Oidc.Server.csproj b/Abblix.Oidc.Server/Abblix.Oidc.Server.csproj
index cf29dcef..e783e19e 100644
--- a/Abblix.Oidc.Server/Abblix.Oidc.Server.csproj
+++ b/Abblix.Oidc.Server/Abblix.Oidc.Server.csproj
@@ -19,6 +19,8 @@
For detailed release notes, visit: https://github.com/Abblix/Oidc.Server/releases
Abblix.png
true
+ 1.1.0.0
+ 1.1.0.0
diff --git a/Abblix.Oidc.Server/Common/AuthorizationContext.cs b/Abblix.Oidc.Server/Common/AuthorizationContext.cs
index b32962b2..d83519ff 100644
--- a/Abblix.Oidc.Server/Common/AuthorizationContext.cs
+++ b/Abblix.Oidc.Server/Common/AuthorizationContext.cs
@@ -83,4 +83,11 @@ public record AuthorizationContext(string ClientId, string[] Scope, RequestedCla
/// enhancing the security of PKCE by allowing the authorization server to verify the code exchange authenticity.
///
public string? CodeChallengeMethod { get; init; }
+
+ ///
+ /// The resources for which the authorization is granted.
+ /// These resources are typically URIs that identify specific services or data that the client is authorized
+ /// to access.
+ ///
+ public Uri[]? Resources { get; init; }
}
diff --git a/Abblix.Oidc.Server/Common/AuthorizationContextExtensions.cs b/Abblix.Oidc.Server/Common/AuthorizationContextExtensions.cs
index c6444901..db8f401e 100644
--- a/Abblix.Oidc.Server/Common/AuthorizationContextExtensions.cs
+++ b/Abblix.Oidc.Server/Common/AuthorizationContextExtensions.cs
@@ -54,6 +54,9 @@ public static void ApplyTo(this AuthorizationContext context, JsonWebTokenPayloa
payload.ClientId = context.ClientId;
payload.Scope = context.Scope;
payload.Nonce = context.Nonce;
+ payload.Audiences = context.Resources is { Length: > 0 }
+ ? Array.ConvertAll(context.Resources, res => res.OriginalString)
+ : new[] { context.ClientId };
payload[JwtClaimTypes.RequestedClaims] = JsonSerializer.SerializeToNode(context.RequestedClaims, JsonSerializerOptions);
}
@@ -70,9 +73,21 @@ public static void ApplyTo(this AuthorizationContext context, JsonWebTokenPayloa
///
public static AuthorizationContext ToAuthorizationContext(this JsonWebTokenPayload payload)
{
+ var resources =
+ payload.Audiences.Count() == 1 && payload.Audiences.Single() == payload.ClientId
+ ? null
+ : payload.Audiences
+ .Select(aud => Uri.TryCreate(aud, UriKind.Absolute, out var uri) ? uri : null)
+ .OfType()
+ .ToArray();
+
return new AuthorizationContext(
payload.ClientId.NotNull(nameof(payload.ClientId)),
payload.Scope.NotNull(nameof(payload.Scope)).ToArray(),
- payload[JwtClaimTypes.RequestedClaims].Deserialize(JsonSerializerOptions));
+ payload[JwtClaimTypes.RequestedClaims].Deserialize(JsonSerializerOptions))
+ {
+ Nonce = payload.Nonce,
+ Resources = resources,
+ };
}
}
diff --git a/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs b/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs
index f26b13a2..7922178d 100644
--- a/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs
+++ b/Abblix.Oidc.Server/Common/Configuration/OidcOptions.cs
@@ -21,6 +21,7 @@
// info@abblix.com
using Abblix.Jwt;
+using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Model;
@@ -41,7 +42,7 @@ public record OidcOptions
///
/// Represents the unique identifier of the OIDC server.
- /// It is recommended to use a URL that is controlled by the entity operating the OIDC server, and it should be
+ /// It is recommended to use a URL controlled by the entity operating the OIDC server, and it should be
/// consistent across different environments to maintain trust with client applications.
///
public string? Issuer { get; set; }
@@ -100,7 +101,7 @@ public record OidcOptions
///
/// The collection of JSON Web Keys (JWK) used for signing tokens issued by the OIDC server.
/// Signing tokens is a critical security measure that ensures the integrity and authenticity of the tokens.
- /// These keys are used to digitally sign ID tokens, access tokens, and other JWTs issued by the server,
+ /// These keys are used to digitally sign ID tokens, access tokens, and other JWT tokens issued by the server,
/// allowing clients to verify that the tokens have not been tampered with and were indeed issued by this server.
/// It is recommended to rotate these keys periodically to maintain the security of the token signing process.
///
@@ -129,9 +130,9 @@ public record OidcOptions
///
/// The collection of JSON Web Keys (JWK) used for encrypting tokens or sensitive information sent to the clients.
/// Encryption is essential for protecting sensitive data within tokens, especially when tokens are passed through
- /// less secure channels or when storing tokens at the client side. These keys are utilized to encrypt ID tokens and,
- /// optionally, access tokens when the OIDC server sends them to clients. Clients use the corresponding public keys
- /// to decrypt the tokens and access the contained claims.
+ /// less secure channels or when storing tokens on the client side.
+ /// These keys are used to encrypt ID tokens and, optionally, access tokens when the OIDC server sends them to clients.
+ /// Clients use the corresponding public keys to decrypt the tokens and access the contained claims.
///
public IReadOnlyCollection EncryptionKeys { get; set; } = Array.Empty();
@@ -146,12 +147,33 @@ public record OidcOptions
///
/// A JWT used for licensing and configuration validation of the OIDC service. This token contains claims that the
/// OIDC service uses to validate its configuration, features, and licensing status, ensuring the service operates
- /// within its licensed capabilities. Proper validation of this token is crucial for the service's legal and functional
- /// compliance.
+ /// within its licensed capabilities. Proper validation of this token is crucial for the service's legal and
+ /// functional compliance.
///
public string? LicenseJwt { get; set; }
+ ///
+ /// The standard length of the authorization code generated by the server.
+ ///
public int AuthorizationCodeLength { get; set; } = 64;
+ ///
+ /// The standard length of the request URI generated by the server for Pushed Authorization Requests (PAR).
+ ///
public int RequestUriLength { get; set; } = 64;
+
+ ///
+ /// The supported scopes and their respective claim types, which outline the access permissions and associated data
+ /// that clients can request.
+ /// This setting determines what information and operations are available to different clients based on the scopes
+ /// they request during authorization.
+ ///
+ public ScopeDefinition[]? Scopes { get; set; }
+
+ ///
+ /// The resource definitions supported by the OIDC server. This setting outlines the resources that clients
+ /// can request access to during authorization, ensuring the OIDC server can enforce access control policies
+ /// and permissions based on these definitions.
+ ///
+ public ResourceDefinition[]? Resources { get; set; }
}
diff --git a/Abblix.Oidc.Server/Common/Configuration/RefreshTokenOptions.cs b/Abblix.Oidc.Server/Common/Configuration/RefreshTokenOptions.cs
index 6b5ed9ad..c383344c 100644
--- a/Abblix.Oidc.Server/Common/Configuration/RefreshTokenOptions.cs
+++ b/Abblix.Oidc.Server/Common/Configuration/RefreshTokenOptions.cs
@@ -30,12 +30,12 @@ public record struct RefreshTokenOptions()
///
/// Sets the absolute period of expiration for refresh tokens.
///
- public TimeSpan AbsoluteExpiresIn { get; init; } = TimeSpan.FromDays(30);
+ public TimeSpan AbsoluteExpiresIn { get; init; } = TimeSpan.FromHours(8);
///
/// Sets the sliding (call-to-call relative) period of expiration for refresh tokens.
///
- public TimeSpan? SlidingExpiresIn { get; init; } = TimeSpan.FromDays(15);
+ public TimeSpan? SlidingExpiresIn { get; init; } = TimeSpan.FromHours(1);
///
/// Allows to reuse refresh tokens after the first usage.
diff --git a/Abblix.Oidc.Server/Common/Constants/ResourceDefinition.cs b/Abblix.Oidc.Server/Common/Constants/ResourceDefinition.cs
new file mode 100644
index 00000000..64038210
--- /dev/null
+++ b/Abblix.Oidc.Server/Common/Constants/ResourceDefinition.cs
@@ -0,0 +1,33 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+namespace Abblix.Oidc.Server.Common.Constants;
+
+///
+/// Represents a resource with associated scopes, defining the permissions and access levels within an application.
+/// This record is typically used to configure and enforce authorization policies based on resource identifiers
+/// and their corresponding scopes.
+///
+/// The identifier for the resource, often a unique name or URL representing the resource.
+/// A variable number of scope definitions associated with the resource. Each scope definition
+/// specifies a scope and its related claims, detailing the access levels and permissions granted.
+public record ResourceDefinition(Uri Resource, params ScopeDefinition[] Scopes);
diff --git a/Abblix.Oidc.Server/Common/Implementation/OidcOptionsKeysProvider.cs b/Abblix.Oidc.Server/Common/Implementation/OidcOptionsKeysProvider.cs
index b4ce2621..5e646f18 100644
--- a/Abblix.Oidc.Server/Common/Implementation/OidcOptionsKeysProvider.cs
+++ b/Abblix.Oidc.Server/Common/Implementation/OidcOptionsKeysProvider.cs
@@ -23,7 +23,6 @@
using Abblix.Jwt;
using Abblix.Oidc.Server.Common.Configuration;
using Abblix.Oidc.Server.Common.Interfaces;
-using Abblix.Utils;
using Microsoft.Extensions.Options;
@@ -57,7 +56,7 @@ public IAsyncEnumerable GetEncryptionKeys(bool includePrivateKeys)
from jwk in _options.Value.EncryptionKeys
select jwk.Sanitize(includePrivateKeys);
- return jsonWebKeys.AsAsync();
+ return jsonWebKeys.ToAsyncEnumerable();
}
///
@@ -71,6 +70,6 @@ public IAsyncEnumerable GetSigningKeys(bool includePrivateKeys)
from jwk in _options.Value.SigningKeys
select jwk.Sanitize(includePrivateKeys);
- return jsonWebKeys.AsAsync();
+ return jsonWebKeys.ToAsyncEnumerable();
}
}
diff --git a/Abblix.Oidc.Server/Common/JsonWebKeyExtensions.cs b/Abblix.Oidc.Server/Common/JsonWebKeyExtensions.cs
index 379c8978..ce7d36ac 100644
--- a/Abblix.Oidc.Server/Common/JsonWebKeyExtensions.cs
+++ b/Abblix.Oidc.Server/Common/JsonWebKeyExtensions.cs
@@ -37,14 +37,14 @@ public static class JsonWebKeyExtensions
/// The algorithm to match. Returns null if is provided.
/// The first with the specified algorithm or null if not found.
- public static Task FirstByAlgorithmAsync(this IAsyncEnumerable credentials, string? alg)
+ public static async Task FirstByAlgorithmAsync(this IAsyncEnumerable credentials, string? alg)
{
if (alg == SigningAlgorithms.None)
- return Task.FromResult(null);
+ return null;
if (alg.HasValue())
- credentials = credentials.WhereAsync(key => key.Algorithm == alg);
+ credentials = credentials.Where(key => key.Algorithm == alg);
- return credentials.FirstOrDefaultAsync();
+ return await credentials.FirstOrDefaultAsync();
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs
index aa84dda4..15414c9f 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/AuthorizationRequestProcessor.cs
@@ -51,21 +51,21 @@ public class AuthorizationRequestProcessor : IAuthorizationRequestProcessor
/// and identity token services, and time-related functionality.
///
/// Service for handling user authentication.
- /// Service for managing user consent.
+ /// Service for managing user consent.
/// Service for generating and managing authorization codes.
/// Service for creating access tokens.
/// Service for generating identity tokens.
/// Service for managing time-related operations.
public AuthorizationRequestProcessor(
IAuthSessionService authSessionService,
- IConsentService consentService,
+ IUserConsentsProvider consentsProvider,
IAuthorizationCodeService authorizationCodeService,
IAccessTokenService accessTokenService,
IIdentityTokenService identityTokenService,
TimeProvider clock)
{
_authSessionService = authSessionService;
- _consentService = consentService;
+ _consentsProvider = consentsProvider;
_authorizationCodeService = authorizationCodeService;
_accessTokenService = accessTokenService;
_identityTokenService = identityTokenService;
@@ -75,7 +75,7 @@ public AuthorizationRequestProcessor(
private readonly IAccessTokenService _accessTokenService;
private readonly IAuthorizationCodeService _authorizationCodeService;
private readonly IAuthSessionService _authSessionService;
- private readonly IConsentService _consentService;
+ private readonly IUserConsentsProvider _consentsProvider;
private readonly IIdentityTokenService _identityTokenService;
private readonly TimeProvider _clock;
@@ -131,7 +131,8 @@ public async Task ProcessAsync(ValidAuthorizationRequest
var authSession = authSessions.Single();
- if (model.Prompt == Prompts.Consent || await _consentService.IsConsentRequired(request, authSession))
+ var userConsents = await _consentsProvider.GetUserConsentsAsync(request, authSession);
+ if (userConsents.Pending is { Scopes.Length: > 0 } or { Resources.Length: > 0 })
{
if (model.Prompt == Prompts.None)
{
@@ -143,16 +144,24 @@ public async Task ProcessAsync(ValidAuthorizationRequest
model.RedirectUri);
}
- return new ConsentRequired(model, authSession);
+ return new ConsentRequired(model, authSession, userConsents.Pending);
}
var clientId = request.ClientInfo.ClientId;
- var authContext = new AuthorizationContext(clientId, model.Scope, model.Claims)
+ var grantedConsents = userConsents.Granted;
+ var scopes = grantedConsents.Scopes
+ .Concat(grantedConsents.Resources.SelectMany(rd => rd.Scopes))
+ .Select(sd => sd.Scope)
+ .Distinct()
+ .ToArray();
+ var resources = Array.ConvertAll(grantedConsents.Resources, resource => resource.Resource);
+ var authContext = new AuthorizationContext(clientId, scopes, model.Claims)
{
RedirectUri = model.RedirectUri,
Nonce = model.Nonce,
CodeChallenge = model.CodeChallenge,
CodeChallengeMethod = model.CodeChallengeMethod,
+ Resources = resources,
};
if (!authSession.AffectedClientIds.Contains(clientId))
@@ -179,33 +188,28 @@ public async Task ProcessAsync(ValidAuthorizationRequest
if (tokenRequired)
{
result.TokenType = TokenTypes.Bearer;
-
- var accessToken = await _accessTokenService.CreateAccessTokenAsync(
+ result.AccessToken = await _accessTokenService.CreateAccessTokenAsync(
authSession,
authContext,
request.ClientInfo);
-
- result.AccessToken = accessToken;
}
var idTokenRequired = request.Model.ResponseType.HasFlag(ResponseTypes.IdToken);
if (idTokenRequired)
{
- var idToken = await _identityTokenService.CreateIdentityTokenAsync(
+ result.IdToken = await _identityTokenService.CreateIdentityTokenAsync(
authSession,
authContext,
request.ClientInfo,
!codeRequired && !tokenRequired,
result.Code,
result.AccessToken?.EncodedJwt);
-
- result.IdToken = idToken;
}
return result;
}
- private Task> GetAvailableAuthSessionsAsync(AuthorizationRequest model)
+ private ValueTask> GetAvailableAuthSessionsAsync(AuthorizationRequest model)
{
var authSessions = _authSessionService.GetAvailableAuthSessions();
@@ -214,14 +218,14 @@ private Task> GetAvailableAuthSessionsAsync(AuthorizationReque
// skip all sessions older than max_age value
var minAuthenticationTime = _clock.GetUtcNow() - model.MaxAge;
authSessions = authSessions
- .WhereAsync(session => minAuthenticationTime < session.AuthenticationTime);
+ .Where(session => minAuthenticationTime < session.AuthenticationTime);
}
var acrValues = model.AcrValues;
if (acrValues is { Length: > 0 })
{
authSessions = authSessions
- .WhereAsync(session => session.AuthContextClassRef.HasValue() &&
+ .Where(session => session.AuthContextClassRef.HasValue() &&
acrValues.Contains(session.AuthContextClassRef));
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ConsentRequired.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ConsentRequired.cs
index 6b7331dd..d2d2cd50 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ConsentRequired.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ConsentRequired.cs
@@ -20,6 +20,8 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Features.Consents;
using Abblix.Oidc.Server.Features.UserAuthentication;
using Abblix.Oidc.Server.Model;
@@ -27,7 +29,24 @@
namespace Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
///
-/// Means that a user is logged and it is to ask for his consent for authorization.
+/// Represents a state where the user is authenticated but requires consent for further authorization.
+/// This record is used to encapsulate the details needed to prompt the user for consent.
///
-public record ConsentRequired(AuthorizationRequest Model, AuthSession AuthSession)
- : AuthorizationResponse(Model);
+/// The model of the authorization request prompting the need for user consent.
+/// The authentication session associated with the user, detailing their authenticated state.
+///
+///
+/// Defines the consents that are pending and require user approval.
+/// This includes the specific scopes and resources that need user consent before proceeding with the authorization
+/// process.
+public record ConsentRequired(AuthorizationRequest Model, AuthSession AuthSession, ConsentDefinition RequiredUserConsents)
+ : AuthorizationResponse(Model)
+{
+ [Obsolete("Use constructor with RequiredUserConsents parameter instead")]
+ public ConsentRequired(AuthorizationRequest Model, AuthSession AuthSession)
+ : this(Model, AuthSession, new ConsentDefinition(
+ Array.Empty(),
+ Array.Empty()))
+ {
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/SuccessfullyAuthenticated.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/SuccessfullyAuthenticated.cs
index a0f98231..a84ef07f 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/SuccessfullyAuthenticated.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/SuccessfullyAuthenticated.cs
@@ -34,7 +34,11 @@ namespace Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
/// An optional session identifier that may be used for session management.
/// Identifiers of the clients that are affected by or related to this authentication
/// process.
-public record SuccessfullyAuthenticated(AuthorizationRequest Model, string ResponseMode, string? SessionId, ICollection AffectedClientIds)
+public record SuccessfullyAuthenticated(
+ AuthorizationRequest Model,
+ string ResponseMode,
+ string? SessionId,
+ ICollection AffectedClientIds)
: AuthorizationResponse(Model)
{
///
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ValidAuthorizationRequest.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ValidAuthorizationRequest.cs
index a4e7ff40..01cff731 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ValidAuthorizationRequest.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/ValidAuthorizationRequest.cs
@@ -20,6 +20,7 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Authorization.Validation;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Model;
@@ -39,8 +40,9 @@ public ValidAuthorizationRequest(AuthorizationValidationContext context)
{
Model = context.Request;
ClientInfo = context.ClientInfo;
+ Scope = context.Scope;
+ Resources = context.Resources;
}
-
///
/// The original or recovered request model that was validated.
///
@@ -50,4 +52,15 @@ public ValidAuthorizationRequest(AuthorizationValidationContext context)
/// Information about the client making the request, as determined during validation.
///
public ClientInfo ClientInfo { get; init; }
+
+ ///
+ /// The scope associated with the authorization request, indicating the permissions requested by the client.
+ ///
+ public ScopeDefinition[] Scope { get; set; }
+
+ ///
+ /// The resources associated with the authorization request, detailing the specific resources the client
+ /// is requesting access to.
+ ///
+ public ResourceDefinition[] Resources { get; set; }
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/AuthorizationValidationContext.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/AuthorizationValidationContext.cs
index 59123716..f70ca7fa 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/AuthorizationValidationContext.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/AuthorizationValidationContext.cs
@@ -28,43 +28,55 @@
namespace Abblix.Oidc.Server.Endpoints.Authorization.Validation;
///
-/// Represents a validation context containing information about the client, response mode, and flow type
-/// that is used during the authorization request validation process.
+/// Encapsulates the context necessary for validating an authorization request, including client details,
+/// response modes, and the OAuth 2.0 flow type.
///
public record AuthorizationValidationContext(AuthorizationRequest Request)
{
///
- /// The request object to validate.
+ /// The authorization request to be validated. This includes all the details provided by the client
+ /// for the authorization process.
///
public AuthorizationRequest Request { get; set; } = Request;
private ClientInfo? _clientInfo;
///
- /// The ClientInfo object containing information about the client. It is a result of identifying the client
- /// making the authorization request.
+ /// Provides details about the client making the authorization request. This includes identifying information
+ /// such as client ID and any other relevant data that has been registered with the authorization server.
///
- /// Thrown when attempting to get a null value.
+ /// Thrown when trying to access this property before it is set.
+ ///
public ClientInfo ClientInfo { get => _clientInfo.NotNull(nameof(ClientInfo)); set => _clientInfo = value; }
///
- /// The response mode associated with the authorization request, determining how the authorization response
- /// should be delivered to the client.
+ /// Specifies how the authorization response should be delivered to the client, e.g., via a direct query or fragment.
///
public string ResponseMode = ResponseModes.Query;
private FlowTypes? _flowType;
///
- /// The flow type associated with the authorization request, indicating the OAuth 2.0 flow being utilized
- /// (e.g., Authorization Code, Implicit).
+ /// Identifies the OAuth 2.0 flow used in the authorization request, such as Authorization Code or Implicit.
///
- /// Thrown when attempting to get a null value.
+ /// Thrown when trying to access this property before it is set.
+ ///
public FlowTypes FlowType { get => _flowType.NotNull(nameof(FlowType)); set => _flowType = value; }
///
- /// The validated and approved redirect URI for the authorization response.
- /// This URI must match one of the URIs registered by the client.
+ /// The redirect URI where the response to the authorization request should be sent. This URI must be one of the
+ /// registered URIs for the client to ensure security.
///
public Uri? ValidRedirectUri { get; set; }
+
+ ///
+ /// A collection of scope definitions applicable to the authorization request, determining the permissions granted.
+ ///
+ public ScopeDefinition[] Scope { get; set; } = Array.Empty();
+
+ ///
+ /// A collection of resource definitions that may be requested as part of the authorization process,
+ /// providing additional control over the accessible resources.
+ ///
+ public ResourceDefinition[] Resources { get; set; } = Array.Empty();
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ClientValidator.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ClientValidator.cs
index 85ae0af4..1df8d2d9 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ClientValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ClientValidator.cs
@@ -73,7 +73,7 @@ public ClientValidator(
var clientInfo = await _clientInfoProvider.TryFindClientAsync(clientId.NotNull(nameof(clientId))).WithLicenseCheck();
if (clientInfo == null)
{
- _logger.LogWarning("The client with id {ClientId} was not found", clientId);
+ _logger.LogWarning("The client with id {ClientId} was not found", new Sanitized(clientId));
return context.InvalidRequest("The client is not authorized");
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs
index 96a73711..865e1a64 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ErrorFactory.cs
@@ -1,22 +1,22 @@
// Abblix OIDC Server Library
// Copyright (c) Abblix LLP. All rights reserved.
-//
+//
// DISCLAIMER: This software is provided 'as-is', without any express or implied
// warranty. Use at your own risk. Abblix LLP is not liable for any damages
// arising from the use of this software.
-//
+//
// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
// in any form outside of the official GitHub repository at:
// https://github.com/Abblix/OIDC.Server. All development and modifications
// must occur within the official repository and are managed solely by Abblix LLP.
-//
+//
// Unauthorized use, modification, or distribution of this software is strictly
// prohibited and may be subject to legal action.
-//
+//
// For full licensing terms, please visit:
-//
+//
// https://oidc.abblix.com/license
-//
+//
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
@@ -119,4 +119,18 @@ public static AuthorizationRequestValidationError Error(
description,
context.ValidRedirectUri,
context.ResponseMode);
+
+ ///
+ /// Creates an indicating an invalid scope error.
+ /// This error type is used when the scopes requested by the client are not supported or are inappropriate
+ /// for the requested operation.
+ ///
+ /// The validation context associated with the request, providing additional context for
+ /// the error response.
+ /// A human-readable description of why the requested scopes are invalid.
+ /// An with details about the scope-related issue.
+ public static AuthorizationRequestValidationError InvalidScope(
+ this AuthorizationValidationContext context,
+ string description)
+ => context.Error(ErrorCodes.InvalidScope, description);
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/PkceValidator.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/PkceValidator.cs
index 03224c39..a4218893 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/PkceValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/PkceValidator.cs
@@ -29,7 +29,7 @@ namespace Abblix.Oidc.Server.Endpoints.Authorization.Validation;
///
/// Validates the PKCE (Proof Key for Code Exchange) parameters in an authorization request.
-/// PKCE adds an additional layer of security for the OAuth 2.0 authorization code flow,
+/// PKCE adds another layer of security for the OAuth 2.0 authorization code flow,
/// particularly in public clients. It ensures that the authorization request conforms to
/// the standards defined in RFC 7636 (specifically, see Section 4.3 for client validation requirements).
///
@@ -55,7 +55,7 @@ public class PkceValidator : SyncAuthorizationContextValidatorBase
return context.InvalidRequest("The client is not allowed PKCE plain method");
}
}
- else if (context.ClientInfo.PkceRequired)
+ else if (context.ClientInfo.PkceRequired ?? true)
{
return context.InvalidRequest("The client requires PKCE code challenge");
}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ResourceValidator.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ResourceValidator.cs
new file mode 100644
index 00000000..98fe15af
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ResourceValidator.cs
@@ -0,0 +1,84 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
+using Abblix.Oidc.Server.Features.ResourceIndicators;
+
+namespace Abblix.Oidc.Server.Endpoints.Authorization.Validation;
+
+///
+/// Validates resources specified in authorization requests to ensure they conform to registered definitions and policies.
+/// This validator checks whether the resources requested in the authorization process are recognized by the system
+/// and permitted for the requesting client, extending the base functionality of resource validation by incorporating
+/// integration with the authorization context.
+///
+public class ResourceValidator: SyncAuthorizationContextValidatorBase
+{
+ ///
+ /// Initializes a new instance of the class, setting up the resource manager
+ /// responsible for maintaining and validating the definitions of resources.
+ ///
+ /// The manager responsible for retrieving and validating resource information.
+ ///
+ public ResourceValidator(IResourceManager resourceManager)
+ {
+ _resourceManager = resourceManager;
+ }
+
+ private readonly IResourceManager _resourceManager;
+
+ ///
+ /// Performs the validation of resource identifiers specified in the authorization request against the allowed
+ /// resource definitions managed by the . This method ensures that the resources
+ /// requested are known to the system and align with security and access policies.
+ ///
+ /// The context containing the authorization request, which includes the resources to be
+ /// validated.
+ ///
+ /// An containing error details if validation fails,
+ /// or null if the validation is successful, indicating that all requested resources are recognized and permissible.
+ ///
+ protected override AuthorizationRequestValidationError? Validate(AuthorizationValidationContext context)
+ {
+ var request = context.Request;
+
+ // Proceed with validation only if there are resources specified in the request.
+ if (request.Resources is { Length: > 0 })
+ {
+ // Validate the requested resources using the resource manager.
+ if (!_resourceManager.Validate(
+ request.Resources,
+ request.Scope,
+ out var resources,
+ out var errorDescription))
+ {
+ return context.Error(ErrorCodes.InvalidTarget, errorDescription);
+ }
+
+ context.Resources = resources;
+ }
+
+ // Return null indicating successful validation if there are no errors.
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ScopeValidator.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ScopeValidator.cs
index 8c7acb39..0a132501 100644
--- a/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ScopeValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Authorization/Validation/ScopeValidator.cs
@@ -20,11 +20,9 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using Abblix.Oidc.Server.Common;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
-
-
+using Abblix.Oidc.Server.Features.ScopeManagement;
namespace Abblix.Oidc.Server.Endpoints.Authorization.Validation;
@@ -36,28 +34,44 @@ namespace Abblix.Oidc.Server.Endpoints.Authorization.Validation;
///
public class ScopeValidator : SyncAuthorizationContextValidatorBase
{
+ public ScopeValidator(IScopeManager scopeManager)
+ {
+ _scopeManager = scopeManager;
+ }
+
+ private readonly IScopeManager _scopeManager;
+
///
- /// Validates the scopes specified in the authorization request.
- /// It checks the compatibility of requested scopes with the client's allowed scopes
- /// and the OAuth flow type. For instance, it validates if offline access is requested
- /// appropriately and if the client is authorized for such access.
+ /// Validates the scopes specified in the authorization request. It checks the compatibility of requested scopes
+ /// with the client's allowed scopes and the OAuth flow type. For instance, it validates if offline access is
+ /// requested appropriately and if the client is authorized for such access.
///
/// The validation context containing client information and request details.
///
- /// An if the scope validation fails,
+ /// An if the scope validation fails,
/// or null if the scopes in the request are valid.
///
- protected override AuthorizationRequestValidationError? Validate(AuthorizationValidationContext context)
+ protected override AuthorizationRequestValidationError? Validate(AuthorizationValidationContext context)
{
- if (context.Request.Scope.HasFlag(Scopes.OfflineAccess))
- {
- if (context.FlowType == FlowTypes.Implicit)
- return context.InvalidRequest("It is not allowed to request for offline access in implicit flow");
+ if (context.Request.Scope.Contains(Scopes.OfflineAccess))
+ {
+ if (context.FlowType == FlowTypes.Implicit)
+ return context.InvalidScope("It is not allowed to request for offline access in implicit flow");
+
+ if (context.ClientInfo.OfflineAccessAllowed != true)
+ return context.InvalidScope("This client is not allowed to request for offline access");
+ }
- if (!context.ClientInfo.OfflineAccessAllowed)
- return context.InvalidRequest("This client is not allowed to request for offline access");
+ if (!_scopeManager.Validate(
+ context.Request.Scope,
+ context.Resources,
+ out var scopeDefinitions,
+ out var errorDescription))
+ {
+ return context.InvalidScope(errorDescription);
}
- return null;
- }
+ context.Scope = scopeDefinitions;
+ return null;
+ }
}
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs
index 7c702f2a..6228834f 100644
--- a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/RegisterClientRequestProcessor.cs
@@ -186,14 +186,14 @@ private ClientInfo ToClientInfo(
{
clientInfo.BackChannelLogout = new BackChannelLogoutOptions(
model.BackChannelLogoutUri,
- model.BackChannelLogoutSessionRequired);
+ model.BackChannelLogoutSessionRequired ?? false);
}
if (model.FrontChannelLogoutUri != null)
{
clientInfo.FrontChannelLogout = new FrontChannelLogoutOptions(
model.FrontChannelLogoutUri,
- model.FrontChannelLogoutSessionRequired);
+ model.FrontChannelLogoutSessionRequired ?? false);
}
return clientInfo;
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/ClientIdValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/ClientIdValidator.cs
index 2eaced85..967b10bb 100644
--- a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/ClientIdValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/ClientIdValidator.cs
@@ -62,7 +62,7 @@ public ClientIdValidator(
var clientInfo = await _clientInfoProvider.TryFindClientAsync(clientId).WithLicenseCheck();
if (clientInfo != null)
{
- _logger.LogWarning("The client with id {ClientId} is already registered", clientId);
+ _logger.LogWarning("The client with id {ClientId} is already registered", new Sanitized(clientId));
return ErrorFactory.InvalidClientMetadata($"The client with id={clientId} is already registered");
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SubjectTypeValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SubjectTypeValidator.cs
index 9b50ec03..e93bd68b 100644
--- a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SubjectTypeValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SubjectTypeValidator.cs
@@ -23,6 +23,7 @@
using System.Net.Http.Json;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Interfaces;
+using Abblix.Utils;
using Microsoft.Extensions.Logging;
using static Abblix.Oidc.Server.Model.ClientRegistrationRequest;
@@ -88,7 +89,7 @@ public SubjectTypeValidator(
catch (Exception ex)
{
_logger.LogWarning(ex, "Unable to receive content of {SectorIdentifierUri}",
- sectorIdentifierUri);
+ new Sanitized(sectorIdentifierUri));
return ErrorFactory.InvalidClientMetadata(
$"Unable to receive content of {Parameters.SectorIdentifierUri}");
}
@@ -109,7 +110,7 @@ public SubjectTypeValidator(
if (missingUris.Length > 0)
{
_logger.LogWarning("The following URIs are present in the {SectorIdentifierUri}, but missing from the Redirect URIs: {@MissingUris}",
- sectorIdentifierUri,
+ new Sanitized(sectorIdentifierUri),
missingUris);
return ErrorFactory.InvalidClientMetadata(
diff --git a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ClientValidator.cs b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ClientValidator.cs
index 6a0b411f..0ab8f964 100644
--- a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ClientValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ClientValidator.cs
@@ -67,7 +67,7 @@ public ClientValidator(
var clientInfo = await _clientInfoProvider.TryFindClientAsync(context.ClientId).WithLicenseCheck();
if (clientInfo == null)
{
- _logger.LogWarning("The client with id {ClientId} was not found", context.ClientId);
+ _logger.LogWarning("The client with id {ClientId} was not found", new Sanitized(context.ClientId));
return new EndSessionRequestValidationError(
ErrorCodes.UnauthorizedClient,
"The client is not authorized");
diff --git a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ConfirmationValidator.cs b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ConfirmationValidator.cs
index 84e47975..aa8f76b0 100644
--- a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ConfirmationValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/ConfirmationValidator.cs
@@ -44,7 +44,7 @@ public class ConfirmationValidator: IEndSessionContextValidator
{
var request = context.Request;
- if (!request.Confirmed && !request.IdTokenHint.HasValue())
+ if (request.Confirmed != true && !request.IdTokenHint.HasValue())
{
return new EndSessionRequestValidationError(
ErrorCodes.ConfirmationRequired,
diff --git a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/PostLogoutRedirectUrisValidator.cs b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/PostLogoutRedirectUrisValidator.cs
index 017b9d83..99c33e20 100644
--- a/Abblix.Oidc.Server/Endpoints/EndSession/Validation/PostLogoutRedirectUrisValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/EndSession/Validation/PostLogoutRedirectUrisValidator.cs
@@ -23,6 +23,7 @@
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.EndSession.Interfaces;
using Abblix.Oidc.Server.Features.UriValidation;
+using Abblix.Utils;
using Microsoft.Extensions.Logging;
using static Abblix.Oidc.Server.Model.EndSessionRequest;
@@ -75,7 +76,7 @@ public PostLogoutRedirectUrisValidator(ILogger
return null;
_logger.LogWarning("The post-logout redirect URI {RedirectUri} is invalid for client with id {ClientId}",
- redirectUri,
+ new Sanitized(redirectUri),
context.ClientInfo.ClientId);
return new EndSessionRequestValidationError(
diff --git a/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs
index f578f45d..87acaa64 100644
--- a/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs
+++ b/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs
@@ -47,11 +47,12 @@
using Abblix.Oidc.Server.Endpoints.Token;
using Abblix.Oidc.Server.Endpoints.Token.Grants;
using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+using Abblix.Oidc.Server.Endpoints.Token.Validation;
using Abblix.Oidc.Server.Endpoints.UserInfo;
using Abblix.Oidc.Server.Endpoints.UserInfo.Interfaces;
using Microsoft.Extensions.DependencyInjection;
-using ClientValidator = Abblix.Oidc.Server.Endpoints.Authorization.Validation.ClientValidator;
-using PostLogoutRedirectUrisValidator = Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation.PostLogoutRedirectUrisValidator;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
namespace Abblix.Oidc.Server.Endpoints;
@@ -76,16 +77,30 @@ public static IServiceCollection AddAuthorizationEndpoint(this IServiceCollectio
.AddScoped();
}
+ ///
+ /// Registers authorization request fetchers and related services into the provided IServiceCollection.
+ /// This method adds implementations for various authorization request fetchers as singletons, ensuring
+ /// that they are efficiently reused throughout the application. It also composes these fetchers into a
+ /// composite fetcher to handle different types of authorization requests seamlessly.
+ ///
+ /// The IServiceCollection to which the services will be added.
+ /// The updated IServiceCollection with the added authorization request fetchers.
public static IServiceCollection AddAuthorizationRequestFetchers(this IServiceCollection services)
{
return services
+ // Add a JSON object binder as a singleton
+ .AddSingleton()
+
+ // Add individual authorization request fetchers as singletons
.AddSingleton()
.AddSingleton()
- .AddSingleton()
.AddSingleton()
+
+ // Compose the individual fetchers into a composite fetcher
.Compose();
}
+
///
/// Adds a series of validators for authorization context as a composite service to ensure comprehensive validation
/// of authorization requests.
@@ -102,12 +117,13 @@ public static IServiceCollection AddAuthorizationContextValidators(this IService
{
return services
// compose AuthorizationContext validation as a pipeline of several IAuthorizationContextValidator
- .AddSingleton()
+ .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
.AddSingleton()
.Compose();
}
@@ -137,13 +153,38 @@ public static IServiceCollection AddPushedAuthorizationEndpoint(this IServiceCol
/// The configured .
public static IServiceCollection AddTokenEndpoint(this IServiceCollection services)
{
- return services
+ services
.AddAuthorizationGrants()
+ .AddTokenContextValidators();
+
+ services.TryAddScoped();
+
+ services.TryAddScoped();
+ services.TryAddScoped();
+ services.TryAddScoped();
+ services.Decorate();
+
+ return services;
+ }
- .AddScoped()
- .AddScoped()
- .AddScoped()
- .Decorate();
+ ///
+ /// Configures and registers a composite of token context validators into the service collection.
+ /// This method sets up a sequence of validators that perform various checks on token requests,
+ /// ensuring they comply with the necessary criteria before a token can be issued.
+ ///
+ /// The service collection to which the token context validators will be added.
+ /// The modified service collection with the registered token context validators.
+ public static IServiceCollection AddTokenContextValidators(this IServiceCollection services)
+ {
+ return services
+ // Register individual validators that will participate in a composite pattern.
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ // Combine all registered ITokenContextValidator into a single composite validator.
+ // This composite approach allows the application to apply multiple validation checks sequentially.
+ .Compose();
}
///
@@ -290,7 +331,7 @@ private static IServiceCollection AddClientRegistrationContextValidators(this IS
// compose ClientRegistrationContext validation as a pipeline of several IClientRegistrationContextValidator
.AddSingleton()
.AddSingleton()
- .AddSingleton()
+ .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
@@ -317,8 +358,8 @@ public static IServiceCollection AddEndSessionContextValidators(this IServiceCol
{
return services
.AddSingleton()
- .AddSingleton()
- .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
.AddSingleton()
.Compose();
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/AuthorizationCodeReusePreventingDecorator.cs b/Abblix.Oidc.Server/Endpoints/Token/AuthorizationCodeReusePreventingDecorator.cs
index a33f6de7..0c4662b8 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/AuthorizationCodeReusePreventingDecorator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/AuthorizationCodeReusePreventingDecorator.cs
@@ -99,13 +99,10 @@ public async Task ProcessAsync(ValidTokenRequest request)
var response = await _processor.ProcessAsync(request);
// Register issued tokens as part of the authorization code grant
- if (response is TokenIssuedResponse
- {
- AccessToken: var accessToken,
- RefreshToken: var refreshToken
- })
+ if (response is TokenIssuedResponse { AccessToken: var accessToken, RefreshToken: var refreshToken })
{
var issuedTokensList = new List();
+
void TryRegisterToken(JsonWebToken? token)
{
if (token is { Payload: { JwtId: { } jwtId, ExpiresAt: { } expiresAt }})
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs
index 281f16d6..3fa12fa8 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/RefreshTokenGrantHandler.cs
@@ -28,7 +28,6 @@
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Features.Storages;
using Abblix.Oidc.Server.Features.Tokens;
-using Abblix.Oidc.Server.Features.Tokens.Revocation;
using Abblix.Oidc.Server.Features.Tokens.Validation;
using Abblix.Oidc.Server.Model;
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs
index a403a04a..427c3bce 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/AuthorizedGrant.cs
@@ -28,12 +28,19 @@ namespace Abblix.Oidc.Server.Endpoints.Token.Interfaces;
///
/// Represents the successful result of an authorized grant operation,
-/// including authentication session and authorization context.
+/// encapsulating the details of the authentication session and the authorization context.
///
-/// The authentication session associated with the grant.
-/// The context of the authorization process.
+/// The authentication session associated with the grant, detailing the user's authenticated
+/// state.
+/// The context of the authorization process, providing specific details such as the client ID,
+/// requested scopes, and any other relevant authorization parameters.
public record AuthorizedGrant(AuthSession AuthSession, AuthorizationContext Context)
: GrantAuthorizationResult
{
+ ///
+ /// An array of tokens that have been issued as part of this grant.
+ /// This may include access tokens, refresh tokens, or other types of tokens
+ /// depending on the authorization flow and client request.
+ ///
public TokenInfo[]? IssuedTokens { get; init; }
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/ITokenAuthorizationContextEvaluator.cs b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/ITokenAuthorizationContextEvaluator.cs
new file mode 100644
index 00000000..8fb1f364
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/ITokenAuthorizationContextEvaluator.cs
@@ -0,0 +1,41 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+
+///
+/// Defines an evaluator for determining the based on token requests.
+///
+public interface ITokenAuthorizationContextEvaluator
+{
+ ///
+ /// Evaluates and constructs a new by refining and reconciling the scopes and resources
+ /// from the original authorization request based on the current token request.
+ ///
+ /// The valid token request that contains the original authorization grant and any additional
+ /// token-specific requests.
+ /// An updated that reflects the actual scopes and resources that
+ /// should be considered during the token issuance process.
+ AuthorizationContext EvaluateAuthorizationContext(ValidTokenRequest request);
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/TokenIssuedResponse.cs b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/TokenIssuedResponse.cs
index 4ad1c25b..b0bccb1c 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/TokenIssuedResponse.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/TokenIssuedResponse.cs
@@ -43,4 +43,9 @@ public record TokenIssuedResponse(EncodedJsonWebToken AccessToken, string TokenT
/// An ID token that provides identity information about the user.
///
public EncodedJsonWebToken? IdToken { get; set; }
+
+ ///
+ /// The scopes associated with the access token issued. Scopes indicate the permissions granted to the access token.
+ ///
+ public IEnumerable Scope => AccessToken.Token.Payload.Scope;
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/ValidTokenRequest.cs b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/ValidTokenRequest.cs
index 3ea6a8bb..c8e6fd6d 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/Interfaces/ValidTokenRequest.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/Interfaces/ValidTokenRequest.cs
@@ -20,6 +20,8 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.Token.Validation;
using Abblix.Oidc.Server.Features.ClientInformation;
using Abblix.Oidc.Server.Model;
@@ -35,7 +37,25 @@ namespace Abblix.Oidc.Server.Endpoints.Token.Interfaces;
/// process.
/// Information about the client making the token request, including client credentials and
/// metadata.
+/// /// The scopes associated with the token request, indicating the permissions
+/// requested by the client.
+/// The resources associated with the token request,
+/// detailing the specific resources the client is requesting access to.
public record ValidTokenRequest(
TokenRequest Model,
AuthorizedGrant AuthorizedGrant,
- ClientInfo ClientInfo) : TokenRequestValidationResult;
+ ClientInfo ClientInfo,
+ ScopeDefinition[] Scope,
+ ResourceDefinition[] Resources)
+ : TokenRequestValidationResult
+{
+ public ValidTokenRequest(TokenValidationContext context)
+ : this(
+ context.Request,
+ context.AuthorizedGrant,
+ context.ClientInfo,
+ context.Scope,
+ context.Resources)
+ {
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/TokenAuthorizationContextEvaluator.cs b/Abblix.Oidc.Server/Endpoints/Token/TokenAuthorizationContextEvaluator.cs
new file mode 100644
index 00000000..6e5c0a48
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/TokenAuthorizationContextEvaluator.cs
@@ -0,0 +1,70 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common;
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+
+namespace Abblix.Oidc.Server.Endpoints.Token;
+
+///
+/// Evaluates instances based on token requests.
+///
+public class TokenAuthorizationContextEvaluator : ITokenAuthorizationContextEvaluator
+{
+ ///
+ /// Evaluates and constructs a new by refining and reconciling the scopes
+ /// and resources from the original authorization request based on the current token request.
+ ///
+ /// The valid token request that contains the original authorization grant and any additional
+ /// token-specific requests.
+ /// An updated that reflects the actual scopes and resources that
+ /// should be considered during the token issuance process.
+ public AuthorizationContext EvaluateAuthorizationContext(ValidTokenRequest request)
+ {
+ var authContext = request.AuthorizedGrant.Context;
+
+ // Determine the effective scopes for the token request.
+ var scope = authContext.Scope;
+ if (scope is { Length: > 0 } && request.Scope is { Length: > 0 })
+ {
+ scope = scope
+ .Intersect(from sd in request.Scope select sd.Scope, StringComparer.Ordinal)
+ .ToArray();
+ }
+
+ // Determine the effective resources for the token request.
+ var resources = authContext.Resources;
+ if (resources is { Length: > 0 } && request.Resources is { Length: > 0 })
+ {
+ resources = resources
+ .Intersect(from rd in request.Resources select rd.Resource)
+ .ToArray();
+ }
+
+ // Return a new authorization context updated with the determined scopes and resources.
+ return authContext with
+ {
+ Scope = scope,
+ Resources = resources,
+ };
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs
index cc929639..0ce7d8b6 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/TokenRequestProcessor.cs
@@ -20,7 +20,6 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using Abblix.Jwt;
using Abblix.Oidc.Server.Common;
using Abblix.Oidc.Server.Common.Constants;
using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
@@ -31,83 +30,85 @@
namespace Abblix.Oidc.Server.Endpoints.Token;
///
-/// Processes token requests in compliance with OAuth 2.0 and OpenID Connect standards.
-/// This processor is responsible for handling various types of token requests (e.g., authorization code, refresh token)
-/// and generating the appropriate token responses, including access tokens, refresh tokens and ID tokens.
+/// Processes token requests in compliance with OAuth 2.0 and OpenID Connect standards,
+/// handling various types of token requests such as authorization code and refresh token.
+/// Generates the appropriate token responses including access tokens, refresh tokens, and ID tokens.
///
public class TokenRequestProcessor : ITokenRequestProcessor
{
///
- /// Initializes a new instance of the class with services for token generation
- /// and management.
+ /// Initializes a new instance of the class, equipped with services
+ /// for token generation and management.
///
/// Service for creating and managing access tokens.
/// Service for creating and managing refresh tokens.
/// Service for creating and managing ID tokens in OpenID Connect flows.
+ /// Service for building the authorization context from a token request.
public TokenRequestProcessor(
IAccessTokenService accessTokenService,
IRefreshTokenService refreshTokenService,
- IIdentityTokenService identityTokenService)
+ IIdentityTokenService identityTokenService,
+ ITokenAuthorizationContextEvaluator tokenContextEvaluator)
{
_accessTokenService = accessTokenService;
_refreshTokenService = refreshTokenService;
_identityTokenService = identityTokenService;
+ _tokenContextEvaluator = tokenContextEvaluator;
}
private readonly IAccessTokenService _accessTokenService;
private readonly IRefreshTokenService _refreshTokenService;
private readonly IIdentityTokenService _identityTokenService;
+ private readonly ITokenAuthorizationContextEvaluator _tokenContextEvaluator;
///
/// Asynchronously processes a valid token request, determining the necessary tokens to generate based on
- /// the request's scope and grant type. Generates an access token for every request and, depending on the scope,
+ /// the request's scope and grant type. It generates an access token for every request and, depending on the scope,
/// may also generate a refresh token and an ID token for OpenID Connect authentication.
///
- ///
- /// The validated token request containing client and authorization session information.
+ /// The validated token request containing client and authorization session information.
+ ///
/// A task representing the asynchronous operation, yielding a containing
/// the generated tokens.
///
- /// Access tokens are generated for client authorization in resource access.
- /// Refresh tokens are issued for long-lived sessions, allowing clients to obtain new access tokens without
- /// re-authentication. ID tokens provide identity information about the user and are used in OpenID Connect
- /// authentication flows. This method ensures the secure and compliant generation of these tokens as per OAuth 2.0
- /// and OpenID Connect standards.
+ /// Access tokens authorize clients for resource access; refresh tokens enable long-lived sessions by allowing
+ /// new access tokens to be obtained without re-authentication; ID tokens provide identity information about
+ /// the user, crucial for OpenID Connect authentication flows. This method ensures secure and compliant token
+ /// generation.
///
public async Task ProcessAsync(ValidTokenRequest request)
{
- request.ClientInfo.CheckClient();
+ var clientInfo = request.ClientInfo;
+ clientInfo.CheckClient();
+
+ var authContext = _tokenContextEvaluator.EvaluateAuthorizationContext(request);
var accessToken = await _accessTokenService.CreateAccessTokenAsync(
request.AuthorizedGrant.AuthSession,
- request.AuthorizedGrant.Context,
- request.ClientInfo);
+ authContext,
+ clientInfo);
var response = new TokenIssuedResponse(
accessToken,
TokenTypes.Bearer,
- request.ClientInfo.AccessTokenExpiresIn,
+ clientInfo.AccessTokenExpiresIn,
TokenTypeIdentifiers.AccessToken);
- if (request.AuthorizedGrant.Context.Scope.HasFlag(Scopes.OfflineAccess))
+ if (authContext.Scope.HasFlag(Scopes.OfflineAccess))
{
response.RefreshToken = await _refreshTokenService.CreateRefreshTokenAsync(
request.AuthorizedGrant.AuthSession,
request.AuthorizedGrant.Context,
- request.ClientInfo,
- request.AuthorizedGrant switch
- {
- RefreshTokenAuthorizedGrant grant => grant.RefreshToken,
- _ => null,
- });
+ clientInfo,
+ request.AuthorizedGrant is RefreshTokenAuthorizedGrant { RefreshToken: var refreshToken } ? refreshToken : null);
}
- if (request.AuthorizedGrant.Context.Scope.HasFlag(Scopes.OpenId))
+ if (authContext.Scope.HasFlag(Scopes.OpenId))
{
response.IdToken = await _identityTokenService.CreateIdentityTokenAsync(
request.AuthorizedGrant.AuthSession,
- request.AuthorizedGrant.Context,
- request.ClientInfo,
+ authContext,
+ clientInfo,
false,
null,
accessToken.EncodedJwt);
diff --git a/Abblix.Oidc.Server/Endpoints/Token/TokenRequestValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/TokenRequestValidator.cs
index 06612fcf..e5a32962 100644
--- a/Abblix.Oidc.Server/Endpoints/Token/TokenRequestValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/Token/TokenRequestValidator.cs
@@ -20,41 +20,27 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using Abblix.Oidc.Server.Common.Constants;
-using Abblix.Oidc.Server.Common.Exceptions;
-using Abblix.Oidc.Server.Endpoints.Token.Grants;
using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
-using Abblix.Oidc.Server.Features.ClientAuthentication;
+using Abblix.Oidc.Server.Endpoints.Token.Validation;
using Abblix.Oidc.Server.Model;
-using Abblix.Utils;
namespace Abblix.Oidc.Server.Endpoints.Token;
///
/// Validates token requests against OAuth 2.0 specifications, ensuring that requests are properly formed and authorized.
-/// This class plays a critical role in the OAuth 2.0 authentication and authorization process by verifying the integrity and
-/// authenticity of token requests according to the framework defined in RFC 6749.
+/// This class plays a critical role in the OAuth 2.0 authentication and authorization process by verifying the integrity
+/// and authenticity of token requests, according to the framework defined in RFC 6749.
///
public class TokenRequestValidator : ITokenRequestValidator
{
- ///
- /// Initializes a new instance of the class.
- /// Sets up the validator with the necessary components for client authentication and handling different
- /// authorization grant types. Client authentication follows the process described in RFC 6749, Section 2.3.
- ///
- /// The authenticator used for validating client requests.
- /// A grant handler responsible for different types of authorization grants.
- public TokenRequestValidator(
- IClientAuthenticator clientAuthenticator,
- IAuthorizationGrantHandler grantHandler)
+ public TokenRequestValidator(ITokenContextValidator validator)
{
- _clientAuthenticator = clientAuthenticator;
- _grantHandler = grantHandler;
+ _validator = validator;
}
- private readonly IClientAuthenticator _clientAuthenticator;
- private readonly IAuthorizationGrantHandler _grantHandler;
+ private readonly ITokenContextValidator _validator;
+
///
/// Asynchronously validates a token request against the OAuth 2.0 specifications. It checks for proper authorization
@@ -69,46 +55,10 @@ public TokenRequestValidator(
/// or contain error information specifying why the request was invalid.
public async Task ValidateAsync(TokenRequest tokenRequest, ClientRequest clientRequest)
{
- if (tokenRequest.Resource != null)
- {
- foreach (var resource in tokenRequest.Resource)
- {
- if (!resource.IsAbsoluteUri)
- {
- return new TokenRequestError(
- ErrorCodes.InvalidTarget,
- "The resource must be absolute URI");
- }
- if (resource.Fragment.HasValue())
- {
- return new TokenRequestError(
- ErrorCodes.InvalidTarget,
- "The resource must not contain fragment");
- }
- }
- }
-
- var clientInfo = await _clientAuthenticator.TryAuthenticateClientAsync(clientRequest);
- if (clientInfo == null)
- {
- return new TokenRequestError(ErrorCodes.InvalidClient, "The client is not authorized");
- }
-
- var result = await _grantHandler.AuthorizeAsync(tokenRequest, clientInfo);
- return result switch
- {
- InvalidGrantResult { Error: var error, ErrorDescription: var description }
- => new TokenRequestError(error, description),
-
- AuthorizedGrant { Context.RedirectUri: var redirectUri } when redirectUri != tokenRequest.RedirectUri
- => new TokenRequestError(
- ErrorCodes.InvalidGrant,
- "The redirect Uri value does not match to the value used before"),
-
- AuthorizedGrant grant
- => new ValidTokenRequest(tokenRequest, grant, clientInfo),
+ // Context creation is a critical step in the validation process, encapsulating all necessary data.
+ var context = new TokenValidationContext(tokenRequest, clientRequest);
- _ => throw new UnexpectedTypeException(nameof(result), result.GetType()),
- };
+ // Delegating the validation to the assigned validator and handling null error response as valid request.
+ return await _validator.ValidateAsync(context) ?? (TokenRequestValidationResult)new ValidTokenRequest(context);
}
}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/AuthorizationGrantValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/AuthorizationGrantValidator.cs
new file mode 100644
index 00000000..78710c25
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/AuthorizationGrantValidator.cs
@@ -0,0 +1,61 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Common.Exceptions;
+using Abblix.Oidc.Server.Endpoints.Token.Grants;
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+public class AuthorizationGrantValidator: ITokenContextValidator
+{
+ public AuthorizationGrantValidator(IAuthorizationGrantHandler grantHandler)
+ {
+ _grantHandler = grantHandler;
+ }
+
+ private readonly IAuthorizationGrantHandler _grantHandler;
+
+ public async Task ValidateAsync(TokenValidationContext context)
+ {
+ var result = await _grantHandler.AuthorizeAsync(context.Request, context.ClientInfo);
+ switch (result)
+ {
+ case InvalidGrantResult { Error: var error, ErrorDescription: var description }:
+ return new TokenRequestError(error, description);
+
+ case AuthorizedGrant { Context.RedirectUri: var redirectUri }
+ when redirectUri != context.Request.RedirectUri:
+ return new TokenRequestError(
+ ErrorCodes.InvalidGrant,
+ "The redirect Uri value does not match to the value used before");
+
+ case AuthorizedGrant grant:
+ context.AuthorizedGrant = grant;
+ return null;
+
+ default:
+ throw new UnexpectedTypeException(nameof(result), result.GetType());
+ }
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/ClientValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/ClientValidator.cs
new file mode 100644
index 00000000..4ae3f43d
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/ClientValidator.cs
@@ -0,0 +1,50 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+using Abblix.Oidc.Server.Features.ClientAuthentication;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+public class ClientValidator: ITokenContextValidator
+{
+ public ClientValidator(IClientAuthenticator clientAuthenticator)
+ {
+ _clientAuthenticator = clientAuthenticator;
+ }
+
+ private readonly IClientAuthenticator _clientAuthenticator;
+
+ public async Task ValidateAsync(TokenValidationContext context)
+ {
+ var clientRequest = context.ClientRequest;
+ var clientInfo = await _clientAuthenticator.TryAuthenticateClientAsync(clientRequest);
+ if (clientInfo == null)
+ {
+ return new TokenRequestError(ErrorCodes.InvalidClient, "The client is not authorized");
+ }
+
+ context.ClientInfo = clientInfo;
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/ITokenContextValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/ITokenContextValidator.cs
new file mode 100644
index 00000000..c12032cd
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/ITokenContextValidator.cs
@@ -0,0 +1,30 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+public interface ITokenContextValidator
+{
+ Task ValidateAsync(TokenValidationContext context);
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/ResourceValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/ResourceValidator.cs
new file mode 100644
index 00000000..d3927a1e
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/ResourceValidator.cs
@@ -0,0 +1,78 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+using Abblix.Oidc.Server.Features.ResourceIndicators;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+///
+/// Provides validation for resource-related data within token requests, ensuring that all requested resources are
+/// recognized and appropriately scoped according to OAuth 2.0 and OpenID Connect standards.
+///
+public class ResourceValidator: SyncTokenContextValidatorBase
+{
+ ///
+ /// Initializes a new instance of the class with a specific resource manager.
+ ///
+ /// The manager responsible for validating and managing resource definitions.
+ public ResourceValidator(IResourceManager resourceManager)
+ {
+ _resourceManager = resourceManager;
+ }
+
+ private readonly IResourceManager _resourceManager;
+
+ ///
+ /// Validates the resources specified in a token request against known resource definitions.
+ /// This validation ensures that only registered and approved resources are accessed by the client.
+ ///
+ /// The context of the token validation including the request and client information.
+ ///
+ /// A if the validation fails, indicating the nature of the error and providing
+ /// an error message; otherwise, null if the resource validation passes successfully.
+ ///
+ protected override TokenRequestError? Validate(TokenValidationContext context)
+ {
+ var request = context.Request;
+
+ // Proceed with validation only if there are resources specified in the request.
+ if (request.Resources is { Length: > 0 })
+ {
+ // Validate the requested resources using the resource manager.
+ if (!_resourceManager.Validate(
+ request.Resources,
+ request.Scope,
+ out var resources,
+ out var errorDescription))
+ {
+ return new TokenRequestError(ErrorCodes.InvalidTarget, errorDescription);
+ }
+
+ context.Resources = resources;
+ }
+
+ // Return null indicating successful validation if there are no errors.
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/ScopeValidator.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/ScopeValidator.cs
new file mode 100644
index 00000000..a421f365
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/ScopeValidator.cs
@@ -0,0 +1,73 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+using Abblix.Oidc.Server.Features.ScopeManagement;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+///
+/// Validates the scopes specified in token requests using a scope manager to ensure their validity and availability.
+/// This validator checks whether each requested scope is recognized and authorized for use, ensuring that clients
+/// only receive permissions appropriate to their needs and in compliance with server policies.
+///
+public class ScopeValidator: SyncTokenContextValidatorBase
+{
+ ///
+ /// Initializes a new instance of the class, equipping it with a scope manager
+ /// to validate requested scopes.
+ ///
+ /// The manager responsible for maintaining and validating scope definitions.
+ public ScopeValidator(IScopeManager scopeManager)
+ {
+ _scopeManager = scopeManager;
+ }
+
+ private readonly IScopeManager _scopeManager;
+
+ ///
+ /// Validates the scopes specified in the token request context. This method ensures that all requested scopes
+ /// are recognized by the scope manager and are permissible for the requesting client.
+ ///
+ /// The context containing the token request information,
+ /// including the scopes to be validated.
+ ///
+ /// A if any of the requested scopes are invalid or not permitted,
+ /// including an error code and a message describing the issue;
+ /// otherwise, returns null indicating that all requested scopes are valid.
+ ///
+ protected override TokenRequestError? Validate(TokenValidationContext context)
+ {
+ if (!_scopeManager.Validate(
+ context.Request.Scope,
+ context.Resources,
+ out var scopeDefinitions,
+ out var errorDescription))
+ {
+ return new TokenRequestError(ErrorCodes.InvalidScope, errorDescription);
+ }
+
+ context.Scope = scopeDefinitions;
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/SyncTokenContextValidatorBase.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/SyncTokenContextValidatorBase.cs
new file mode 100644
index 00000000..1ac60369
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/SyncTokenContextValidatorBase.cs
@@ -0,0 +1,33 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+public abstract class SyncTokenContextValidatorBase : ITokenContextValidator
+{
+ public Task ValidateAsync(TokenValidationContext context)
+ => Task.FromResult(Validate(context));
+
+ protected abstract TokenRequestError? Validate(TokenValidationContext context);
+}
\ No newline at end of file
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenContextValidatorComposite.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenContextValidatorComposite.cs
new file mode 100644
index 00000000..bacd252e
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenContextValidatorComposite.cs
@@ -0,0 +1,50 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+public class TokenContextValidatorComposite : ITokenContextValidator
+{
+ public TokenContextValidatorComposite(ITokenContextValidator[] validators)
+ {
+ _validators = validators;
+ }
+
+ ///
+ /// The array of validators representing the steps in the validation process.
+ ///
+ private readonly ITokenContextValidator[] _validators;
+
+ public async Task ValidateAsync(TokenValidationContext context)
+ {
+ foreach (var validator in _validators)
+ {
+ var error = await validator.ValidateAsync(context);
+ if (error != null)
+ return error;
+ }
+
+ return null;
+ }
+}
diff --git a/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenValidationContext.cs b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenValidationContext.cs
new file mode 100644
index 00000000..a488a71f
--- /dev/null
+++ b/Abblix.Oidc.Server/Endpoints/Token/Validation/TokenValidationContext.cs
@@ -0,0 +1,59 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.Token.Interfaces;
+using Abblix.Oidc.Server.Features.ClientInformation;
+using Abblix.Oidc.Server.Model;
+
+namespace Abblix.Oidc.Server.Endpoints.Token.Validation;
+
+///
+/// Encapsulates the context required for validating token requests, including client and authorization grant details.
+///
+public record TokenValidationContext(TokenRequest Request, ClientRequest ClientRequest)
+{
+ ///
+ /// Information about the client making the request, derived from the client authentication process.
+ ///
+ public ClientInfo ClientInfo { get; set; }
+
+ ///
+ /// Represents the result of an authorized grant, containing both the session and context of the authorization.
+ /// This object is essential for ensuring that the grant is valid and for extracting any additional information
+ /// needed for token generation.
+ ///
+ public AuthorizedGrant AuthorizedGrant { get; set; }
+
+ ///
+ /// Defines the scope of access requested or authorized. This array of scope definitions helps in determining
+ /// the extent of access granted to the client and any constraints or conditions applied to the token.
+ ///
+ public ScopeDefinition[] Scope { get; set; } = Array.Empty();
+
+ ///
+ /// Specifies additional resources that the client has requested or that have been included in the authorization.
+ /// These definitions provide context on the resources that are accessible with the issued token, enhancing
+ /// the token's utility for fine-grained access control.
+ ///
+ public ResourceDefinition[] Resources { get; set; } = Array.Empty();
+}
diff --git a/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestValidator.cs b/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestValidator.cs
index 37065ca7..f081c798 100644
--- a/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestValidator.cs
+++ b/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestValidator.cs
@@ -71,7 +71,9 @@ public UserInfoRequestValidator(
/// Additional client request information for contextual validation.
/// A representing the asynchronous operation,
/// which upon completion will yield a .
- public async Task ValidateAsync(UserInfoRequest userInfoRequest, ClientRequest clientRequest)
+ public async Task ValidateAsync(
+ UserInfoRequest userInfoRequest,
+ ClientRequest clientRequest)
{
string jwtAccessToken;
var authorizationHeader = clientRequest.AuthorizationHeader;
@@ -79,7 +81,9 @@ public async Task ValidateAsync(UserInfoRequest
{
if (authorizationHeader.Scheme != TokenTypes.Bearer)
{
- return new UserInfoRequestError(ErrorCodes.InvalidGrant, $"The scheme name '{authorizationHeader.Scheme}' is not supported");
+ return new UserInfoRequestError(
+ ErrorCodes.InvalidGrant,
+ $"The scheme name '{authorizationHeader.Scheme}' is not supported");
}
if (userInfoRequest.AccessToken != null)
@@ -118,10 +122,12 @@ public async Task ValidateAsync(UserInfoRequest
switch (result)
{
- case ValidJsonWebToken { Token: { Header.Type: var tokenType } token }:
- if (tokenType != JwtTypes.AccessToken)
- return new UserInfoRequestError(ErrorCodes.InvalidGrant, $"Invalid token type: {tokenType}");
+ case ValidJsonWebToken { Token.Header.Type: var tokenType } when tokenType != JwtTypes.AccessToken:
+ return new UserInfoRequestError(
+ ErrorCodes.InvalidGrant,
+ $"Invalid token type: {tokenType}");
+ case ValidJsonWebToken { Token: var token }:
(authSession, authContext) = await _accessTokenService.AuthenticateByAccessTokenAsync(token);
break;
@@ -134,7 +140,11 @@ public async Task ValidateAsync(UserInfoRequest
var clientInfo = await _clientInfoProvider.TryFindClientAsync(authContext.ClientId).WithLicenseCheck();
if (clientInfo == null)
- return new UserInfoRequestError(ErrorCodes.InvalidGrant, $"The client '{authContext.ClientId}' is not found");
+ {
+ return new UserInfoRequestError(
+ ErrorCodes.InvalidGrant,
+ $"The client '{authContext.ClientId}' is not found");
+ }
return new ValidUserInfoRequest(userInfoRequest, authSession, authContext, clientInfo);
}
diff --git a/Abblix.Oidc.Server/Features/ClientAuthentication/IClientAuthenticator.cs b/Abblix.Oidc.Server/Features/ClientAuthentication/IClientAuthenticator.cs
index c9346e49..e2536f0a 100644
--- a/Abblix.Oidc.Server/Features/ClientAuthentication/IClientAuthenticator.cs
+++ b/Abblix.Oidc.Server/Features/ClientAuthentication/IClientAuthenticator.cs
@@ -32,7 +32,7 @@ public interface IClientAuthenticator
{
///
/// Specifies the authentication methods supported by this authenticator.
- /// This property should return a value that identify the authentication scheme
+ /// This property should return a value that identifies the authentication scheme
/// (e.g., "client_secret_basic", "private_key_jwt") supported by the implementer.
///
IEnumerable ClientAuthenticationMethodsSupported { get; }
diff --git a/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs b/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs
index fc54f297..525f84a9 100644
--- a/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs
+++ b/Abblix.Oidc.Server/Features/ClientInformation/ClientInfo.cs
@@ -73,7 +73,7 @@ public record ClientInfo(string ClientId)
/// Indicates whether the client is to use Proof Key for Code Exchange (PKCE) in the authorization code flow,
/// enhancing security for public clients.
///
- public bool PkceRequired { get; set; } = false;
+ public bool? PkceRequired { get; set; } = true;
///
/// Indicates if the client is allowed to use the "plain" method for PKCE.
@@ -85,13 +85,13 @@ public record ClientInfo(string ClientId)
/// The validity period of an authorization code issued to this client.
/// Shorter durations are recommended for higher security.
///
- public TimeSpan AuthorizationCodeExpiresIn { get; set; } = TimeSpan.FromMinutes(5);
+ public TimeSpan AuthorizationCodeExpiresIn { get; set; } = TimeSpan.FromMinutes(1);
///
/// Specifies the lifetime of access tokens issued to this client.
/// Shorter access token lifetimes reduce the risk of token leakage.
///
- public TimeSpan AccessTokenExpiresIn { get; set; } = TimeSpan.FromHours(1);
+ public TimeSpan AccessTokenExpiresIn { get; set; } = TimeSpan.FromMinutes(10);
///
/// Configures the behavior and properties of refresh tokens issued to this client,
@@ -131,7 +131,7 @@ public record ClientInfo(string ClientId)
///
/// Allows the client to request tokens that enable access to the user's resources while they are offline.
///
- public bool OfflineAccessAllowed { get; set; } = false;
+ public bool? OfflineAccessAllowed { get; set; } = false;
///
/// The set of JSON Web Keys used by the client, typically for signing request objects and decrypting
diff --git a/Abblix.Oidc.Server/Features/ClientInformation/ClientKeysProvider.cs b/Abblix.Oidc.Server/Features/ClientInformation/ClientKeysProvider.cs
index ef3385af..dd8374cc 100644
--- a/Abblix.Oidc.Server/Features/ClientInformation/ClientKeysProvider.cs
+++ b/Abblix.Oidc.Server/Features/ClientInformation/ClientKeysProvider.cs
@@ -22,7 +22,6 @@
using System.Net.Http.Json;
using Abblix.Jwt;
-using Abblix.Utils;
using Microsoft.Extensions.Logging;
namespace Abblix.Oidc.Server.Features.ClientInformation;
@@ -56,7 +55,7 @@ public ClientKeysProvider(
/// A collection of encryption keys as an asynchronous enumerable.
public IAsyncEnumerable GetEncryptionKeys(ClientInfo clientInfo)
{
- return GetKeys(clientInfo).WhereAsync(key => key.Usage == PublicKeyUsages.Encryption);
+ return GetKeys(clientInfo).Where(key => key.Usage == PublicKeyUsages.Encryption);
}
///
@@ -66,7 +65,7 @@ public IAsyncEnumerable GetEncryptionKeys(ClientInfo clientInfo)
/// A collection of signing keys as an asynchronous enumerable.
public IAsyncEnumerable GetSigningKeys(ClientInfo clientInfo)
{
- return GetKeys(clientInfo).WhereAsync(key => key.Usage == PublicKeyUsages.Signature);
+ return GetKeys(clientInfo).Where(key => key.Usage == PublicKeyUsages.Signature);
}
///
diff --git a/Abblix.Oidc.Server/Features/Consents/ConsentDefinition.cs b/Abblix.Oidc.Server/Features/Consents/ConsentDefinition.cs
new file mode 100644
index 00000000..3a04b635
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/Consents/ConsentDefinition.cs
@@ -0,0 +1,15 @@
+using Abblix.Oidc.Server.Common.Constants;
+
+namespace Abblix.Oidc.Server.Features.Consents;
+
+///
+/// Defines the details of user consents required for specific scopes and resources.
+/// This record is used to manage and validate user consent for accessing specific scopes and resources,
+/// ensuring that consent is explicitly granted according to the requirements of the application and compliance
+/// standards.
+///
+/// An array of that represents the scopes for which user consent
+/// is needed.
+/// An array of that represents the resources for which
+/// user consent is needed.
+public record ConsentDefinition(ScopeDefinition[] Scopes, ResourceDefinition[] Resources);
diff --git a/Abblix.Oidc.Server/Features/Consents/IConsentService.cs b/Abblix.Oidc.Server/Features/Consents/IConsentService.cs
index ee23fd83..e69431f4 100644
--- a/Abblix.Oidc.Server/Features/Consents/IConsentService.cs
+++ b/Abblix.Oidc.Server/Features/Consents/IConsentService.cs
@@ -28,6 +28,7 @@ namespace Abblix.Oidc.Server.Features.Consents;
///
/// Provides methods to determine whether user consent is to proceed with authentication.
///
+[Obsolete("Use IConsentProvider instead")]
public interface IConsentService
{
///
diff --git a/Abblix.Oidc.Server/Features/Consents/IUserConsentsProvider.cs b/Abblix.Oidc.Server/Features/Consents/IUserConsentsProvider.cs
new file mode 100644
index 00000000..f2cec746
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/Consents/IUserConsentsProvider.cs
@@ -0,0 +1,25 @@
+using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
+using Abblix.Oidc.Server.Features.UserAuthentication;
+
+namespace Abblix.Oidc.Server.Features.Consents;
+
+///
+/// Defines an interface for a service that provides user consents. This service is responsible for retrieving
+/// and managing user consent decisions related to authorization requests. It ensures that the application adheres
+/// to user preferences and legal requirements concerning data access and processing.
+///
+public interface IUserConsentsProvider
+{
+ ///
+ /// Asynchronously retrieves the user consents for a given authorization request and authentication session.
+ /// This method is essential for determining which scopes and resources the user has consented to, enabling
+ /// the application to respect user permissions and comply with data protection regulations.
+ ///
+ /// The validated authorization request containing the scopes and resources for which
+ /// consent may be required.
+ /// The current authentication session that provides context about the authenticated user,
+ /// potentially influencing consent retrieval based on the user's settings or previous consent decisions.
+ /// A task that resolves to an instance of , containing detailed information
+ /// about the consents granted or denied by the user.
+ Task GetUserConsentsAsync(ValidAuthorizationRequest request, AuthSession authSession);
+}
diff --git a/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs b/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs
index 4d242d4b..383fbc70 100644
--- a/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs
+++ b/Abblix.Oidc.Server/Features/Consents/NullConsentService.cs
@@ -28,11 +28,37 @@ namespace Abblix.Oidc.Server.Features.Consents;
///
/// Implements the very basic consent service not requiring any consents.
///
-///
-/// Replace it with your own implementation in case you need to require a consent from a user.
+/// ///
+/// This implementation assumes that no consents are necessary for any operations, effectively bypassing
+/// consent-related checks and workflows. If your application requires user consent for accessing specific scopes
+/// or resources, replace this service with a custom implementation that appropriately handles such requirements.
///
-public class NullConsentService : IConsentService
+public class NullConsentService : IUserConsentsProvider, IConsentService
{
- public Task IsConsentRequired(ValidAuthorizationRequest request, AuthSession authSession)
- => Task.FromResult(false);
+ ///
+ /// Retrieves the user consents for a given authorization request and authentication session.
+ ///
+ /// The validated authorization request for which to retrieve consents.
+ /// The authentication session associated with the request.
+ /// A task that resolves to an instance of , containing details about
+ /// the consents granted by the user. This implementation automatically assumes all consents are granted.
+ public Task GetUserConsentsAsync(ValidAuthorizationRequest request, AuthSession authSession)
+ {
+ var userConsents = new UserConsents { Granted = new(request.Scope, request.Resources) };
+ return Task.FromResult(userConsents);
+ }
+
+ ///
+ /// Determines whether consent is required for a given authorization request and authentication session.
+ ///
+ /// The validated authorization request that might require consent.
+ /// The authentication session associated with the request,
+ /// containing user-specific data.
+ /// A task that resolves to a boolean indicating whether user consent is needed. This implementation
+ /// always returns false, suggesting consent is never required.
+ public async Task IsConsentRequired(ValidAuthorizationRequest request, AuthSession authSession)
+ {
+ var userConsents = await GetUserConsentsAsync(request, authSession);
+ return userConsents.Pending is { Scopes.Length: > 0 } or { Resources.Length: > 0 };
+ }
}
diff --git a/Abblix.Oidc.Server/Features/Consents/PromptConsentDecorator.cs b/Abblix.Oidc.Server/Features/Consents/PromptConsentDecorator.cs
new file mode 100644
index 00000000..5ba04959
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/Consents/PromptConsentDecorator.cs
@@ -0,0 +1,66 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces;
+using Abblix.Oidc.Server.Features.UserAuthentication;
+
+namespace Abblix.Oidc.Server.Features.Consents;
+
+///
+/// A decorator for the that enforces the prompt for consent when required.
+/// This class intercepts the consent retrieval process to inject mandatory consent prompts based on the authorization
+/// request details.
+///
+public class PromptConsentDecorator: IUserConsentsProvider
+{
+ ///
+ /// Initializes a new instance of the class, wrapping an existing consents'
+ /// provider.
+ ///
+ /// The inner-to - delegate calls to when no explicit
+ /// prompting is needed.
+ public PromptConsentDecorator(IUserConsentsProvider inner)
+ {
+ _inner = inner;
+ }
+
+ private readonly IUserConsentsProvider _inner;
+
+ ///
+ /// Retrieves user consents, injecting a mandatory prompt for consent if specified by the authorization request.
+ ///
+ /// The validated authorization request containing details that may require user interaction
+ /// for consent.
+ /// The current authentication session that might affect how consents are handled.
+ /// A task that resolves to an instance of , which will include any consents
+ /// that are pending based on the authorization request parameters.
+ public async Task GetUserConsentsAsync(ValidAuthorizationRequest request, AuthSession authSession)
+ => request.Model.Prompt switch
+ {
+ // If the 'consent' prompt is explicitly requested, force all scopes and resources to be pending consent.
+ Prompts.Consent => new UserConsents { Pending = new(request.Scope, request.Resources) },
+
+ // Otherwise, defer to the inner consents' provider.
+ _ => await _inner.GetUserConsentsAsync(request, authSession),
+ };
+}
diff --git a/Abblix.Oidc.Server/Features/Consents/UserConsents.cs b/Abblix.Oidc.Server/Features/Consents/UserConsents.cs
new file mode 100644
index 00000000..cd4d9b78
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/Consents/UserConsents.cs
@@ -0,0 +1,25 @@
+using Abblix.Oidc.Server.Common.Constants;
+
+namespace Abblix.Oidc.Server.Features.Consents;
+
+///
+/// Represents the state of user consents in an authorization flow, categorizing them into granted, denied, and pending.
+///
+public record UserConsents
+{
+ ///
+ /// The consents that have been explicitly granted by the user.
+ /// These consents cover scopes and resources the user has agreed to provide access to.
+ ///
+ public ConsentDefinition Granted { get; set; } = new(
+ Array.Empty(),
+ Array.Empty());
+
+ ///
+ /// The consents that are still pending a decision by the user.
+ /// These include scopes and resources that have been requested but not yet explicitly approved or denied.
+ ///
+ public ConsentDefinition Pending { get; set; } = new(
+ Array.Empty(),
+ Array.Empty());
+};
diff --git a/Abblix.Oidc.Server/Features/Licensing/OptionsLicenseJwtProvider.cs b/Abblix.Oidc.Server/Features/Licensing/OptionsLicenseJwtProvider.cs
index 0152bb41..e706097d 100644
--- a/Abblix.Oidc.Server/Features/Licensing/OptionsLicenseJwtProvider.cs
+++ b/Abblix.Oidc.Server/Features/Licensing/OptionsLicenseJwtProvider.cs
@@ -21,7 +21,6 @@
// info@abblix.com
using Abblix.Oidc.Server.Common.Configuration;
-using Abblix.Utils;
using Microsoft.Extensions.Options;
namespace Abblix.Oidc.Server.Features.Licensing;
@@ -47,6 +46,6 @@ public OptionsLicenseJwtProvider(IOptions options)
public IAsyncEnumerable? GetLicenseJwtAsync()
{
var licenseJwt = _options.Value.LicenseJwt;
- return licenseJwt != null ? new[] { licenseJwt }.AsAsync() : null;
+ return licenseJwt != null ? new[] { licenseJwt }.ToAsyncEnumerable() : null;
}
}
diff --git a/Abblix.Oidc.Server/Features/Licensing/StaticLicenseJwtProvider.cs b/Abblix.Oidc.Server/Features/Licensing/StaticLicenseJwtProvider.cs
index 3f2311b7..abf2ec1a 100644
--- a/Abblix.Oidc.Server/Features/Licensing/StaticLicenseJwtProvider.cs
+++ b/Abblix.Oidc.Server/Features/Licensing/StaticLicenseJwtProvider.cs
@@ -20,8 +20,6 @@
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com
-using Abblix.Utils;
-
namespace Abblix.Oidc.Server.Features.Licensing;
///
@@ -53,6 +51,6 @@ public StaticLicenseJwtProvider(string licenseJwt)
/// A task that represents the asynchronous operation, resulting in the license JWT string.
public IAsyncEnumerable? GetLicenseJwtAsync()
{
- return new[] { _licenseJwt }.AsAsync();
+ return new[] { _licenseJwt }.ToAsyncEnumerable();
}
}
diff --git a/Abblix.Oidc.Server/Features/ResourceIndicators/IResourceManager.cs b/Abblix.Oidc.Server/Features/ResourceIndicators/IResourceManager.cs
new file mode 100644
index 00000000..877cf60c
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/ResourceIndicators/IResourceManager.cs
@@ -0,0 +1,43 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using System.Diagnostics.CodeAnalysis;
+using Abblix.Oidc.Server.Common.Constants;
+
+namespace Abblix.Oidc.Server.Features.ResourceIndicators;
+
+///
+/// Provides an interface for managing and retrieving resource definitions. This interface is essential for
+/// ensuring that requests for resources are validated against registered and recognized definitions, supporting
+/// the enforcement of policies related to resource access and permissions.
+///
+public interface IResourceManager
+{
+ ///
+ /// Attempts to retrieve the resource definition associated with the specified URI.
+ ///
+ /// The URI identifying the resource for which the definition is requested.
+ /// When this method returns, contains the resource definition associated with
+ /// the specified URI, if the resource is found; otherwise, null. This parameter is passed uninitialized.
+ /// true if the resource definition is found; otherwise, false.
+ bool TryGet(Uri resource, [MaybeNullWhen(false)] out ResourceDefinition definition);
+}
diff --git a/Abblix.Oidc.Server/Features/ResourceIndicators/ResourceManager.cs b/Abblix.Oidc.Server/Features/ResourceIndicators/ResourceManager.cs
new file mode 100644
index 00000000..d1e7678d
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/ResourceIndicators/ResourceManager.cs
@@ -0,0 +1,63 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using System.Diagnostics.CodeAnalysis;
+using Abblix.Oidc.Server.Common.Configuration;
+using Abblix.Oidc.Server.Common.Constants;
+using Microsoft.Extensions.Options;
+
+namespace Abblix.Oidc.Server.Features.ResourceIndicators;
+
+///
+/// Manages resource definitions, ensuring they are registered and retrievable based on their URIs.
+///
+public class ResourceManager : IResourceManager
+{
+ ///
+ /// Initializes a new instance of the class,
+ /// registering resources defined in the OIDC options.
+ ///
+ /// The OIDC options containing resource definitions to be registered.
+ public ResourceManager(IOptions options)
+ {
+ if (options.Value.Resources != null)
+ Array.ForEach(options.Value.Resources, AddResource);
+ }
+
+ private readonly Dictionary _resources = new();
+
+ ///
+ /// Adds a resource definition to the internal collection.
+ ///
+ /// The resource definition to be added.
+ private void AddResource(ResourceDefinition resource) => _resources.Add(resource.Resource, resource);
+
+ ///
+ /// Attempts to retrieve the resource definition associated with the specified URI.
+ ///
+ /// The URI identifying the resource for which the definition is requested.
+ /// When this method returns, contains the resource definition associated with
+ /// the specified URI, if the resource is found; otherwise, null. This parameter is passed uninitialized.
+ /// true if the resource definition is found; otherwise, false.
+ public bool TryGet(Uri resource, [MaybeNullWhen(false)] out ResourceDefinition definition)
+ => _resources.TryGetValue(resource, out definition);
+}
diff --git a/Abblix.Oidc.Server/Features/ResourceIndicators/ResourceManagerExtensions.cs b/Abblix.Oidc.Server/Features/ResourceIndicators/ResourceManagerExtensions.cs
new file mode 100644
index 00000000..a732b965
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/ResourceIndicators/ResourceManagerExtensions.cs
@@ -0,0 +1,93 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using System.Diagnostics.CodeAnalysis;
+using Abblix.Oidc.Server.Common.Constants;
+using Abblix.Utils;
+
+namespace Abblix.Oidc.Server.Features.ResourceIndicators;
+
+///
+/// Provides extension methods for resource validation, leveraging a resource manager to ensure the validity and
+/// permissibility of requested resources.
+///
+public static class ResourceManagerExtensions
+{
+ ///
+ /// Validates requested resources against registered resource definitions to confirm their validity and
+ /// authorization.
+ /// This method ensures that resources and the requested scopes within those resources are registered and allowed.
+ ///
+ /// The resource manager that maintains the definitions of resources.
+ /// A collection of URIs representing the resources being requested.
+ /// A collection of scope identifiers associated with the request.
+ /// Outputs an array of objects if
+ /// the validation is successful, otherwise null.
+ /// Outputs a string describing the reason for validation failure,
+ /// otherwise null if the validation is successful.
+ /// True if all requested resources and their corresponding scopes are valid and permissible,
+ /// false otherwise.
+ public static bool Validate(
+ this IResourceManager resourceManager,
+ IEnumerable resources,
+ IEnumerable scopes,
+ [MaybeNullWhen(false)] out ResourceDefinition[] resourceDefinitions,
+ [MaybeNullWhen(true)] out string errorDescription)
+ {
+ resourceDefinitions = null;
+ errorDescription = null;
+
+ var resourceList = new List();
+ var scopeSet = scopes.ToHashSet(StringComparer.Ordinal);
+
+ foreach (var resource in resources)
+ {
+ if (!resource.IsAbsoluteUri)
+ {
+ errorDescription = "The resource must be absolute URI";
+ return false;
+ }
+
+ if (resource.Fragment.HasValue())
+ {
+ errorDescription = "The requested resource must not contain fragment part";
+ return false;
+ }
+
+ if (!resourceManager.TryGet(resource, out var definition))
+ {
+ errorDescription = "The requested resource is unknown";
+ return false;
+ }
+
+ // Filter the scopes of the resource to include only those that are requested
+ var requestedScopes = definition.Scopes
+ .Where(def => scopeSet.Contains(def.Scope))
+ .ToArray();
+
+ resourceList.Add(definition with { Scopes = requestedScopes });
+ }
+
+ resourceDefinitions = resourceList.ToArray();
+ return true;
+ }
+}
diff --git a/Abblix.Oidc.Server/Features/ScopeManagement/IScopeManager.cs b/Abblix.Oidc.Server/Features/ScopeManagement/IScopeManager.cs
new file mode 100644
index 00000000..5edd5730
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/ScopeManagement/IScopeManager.cs
@@ -0,0 +1,40 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using System.Diagnostics.CodeAnalysis;
+using Abblix.Oidc.Server.Common.Constants;
+
+namespace Abblix.Oidc.Server.Features.ScopeManagement;
+
+///
+/// Defines a contract for managing and retrieving scope definitions.
+///
+public interface IScopeManager: IEnumerable
+{
+ ///
+ /// Attempts to retrieve the definition of a specified scope.
+ ///
+ /// The scope identifier to retrieve the definition for.
+ /// Outputs the if the scope exists, otherwise null.
+ /// True if the scope exists and the definition is retrieved, false otherwise.
+ bool TryGet(string scope, [MaybeNullWhen(false)] out ScopeDefinition definition);
+}
diff --git a/Abblix.Oidc.Server/Features/ScopeManagement/ScopeManager.cs b/Abblix.Oidc.Server/Features/ScopeManagement/ScopeManager.cs
new file mode 100644
index 00000000..d965a216
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/ScopeManagement/ScopeManager.cs
@@ -0,0 +1,75 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+using Abblix.Oidc.Server.Common.Configuration;
+using Abblix.Oidc.Server.Common.Constants;
+using Microsoft.Extensions.Options;
+
+namespace Abblix.Oidc.Server.Features.ScopeManagement;
+
+///
+/// Manages the registration and retrieval of scope definitions, providing a mechanism to validate requested scopes
+/// against predefined or configured scopes.
+///
+public class ScopeManager : IScopeManager
+{
+ ///
+ /// Initializes a new instance of the class with default standard scopes and additional
+ /// custom scopes provided through configuration.
+ ///
+ /// The options containing OIDC configuration, including additional custom scopes.
+ public ScopeManager(IOptions options)
+ {
+ Add(StandardScopes.OpenId);
+ Add(StandardScopes.Profile);
+ Add(StandardScopes.Email);
+ Add(StandardScopes.Address);
+ Add(StandardScopes.Phone);
+ Add(StandardScopes.OfflineAccess);
+
+ if (options.Value.Scopes != null)
+ Array.ForEach(options.Value.Scopes, Add);
+ }
+
+ private readonly Dictionary _scopes = new(StringComparer.Ordinal);
+
+ ///
+ /// Adds a new scope definition to the manager.
+ ///
+ /// The scope definition to add.
+ private void Add(ScopeDefinition scope) => _scopes.TryAdd(scope.Scope, scope);
+
+ ///
+ /// Attempts to retrieve the definition of a specified scope.
+ ///
+ /// The scope identifier to retrieve the definition for.
+ /// Outputs the if the scope exists, otherwise null.
+ /// True if the scope exists and the definition is retrieved, false otherwise.
+ public bool TryGet(string scope, [MaybeNullWhen(false)] out ScopeDefinition definition)
+ => _scopes.TryGetValue(scope, out definition);
+
+ public IEnumerator GetEnumerator() => _scopes.Values.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
diff --git a/Abblix.Oidc.Server/Features/ScopeManagement/ScopeManagerExtensions.cs b/Abblix.Oidc.Server/Features/ScopeManagement/ScopeManagerExtensions.cs
new file mode 100644
index 00000000..f16c5e0b
--- /dev/null
+++ b/Abblix.Oidc.Server/Features/ScopeManagement/ScopeManagerExtensions.cs
@@ -0,0 +1,90 @@
+// Abblix OIDC Server Library
+// Copyright (c) Abblix LLP. All rights reserved.
+//
+// DISCLAIMER: This software is provided 'as-is', without any express or implied
+// warranty. Use at your own risk. Abblix LLP is not liable for any damages
+// arising from the use of this software.
+//
+// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
+// in any form outside of the official GitHub repository at:
+// https://github.com/Abblix/OIDC.Server. All development and modifications
+// must occur within the official repository and are managed solely by Abblix LLP.
+//
+// Unauthorized use, modification, or distribution of this software is strictly
+// prohibited and may be subject to legal action.
+//
+// For full licensing terms, please visit:
+//
+// https://oidc.abblix.com/license
+//
+// CONTACT: For license inquiries or permissions, contact Abblix LLP at
+// info@abblix.com
+
+using System.Diagnostics.CodeAnalysis;
+using Abblix.Oidc.Server.Common.Constants;
+
+namespace Abblix.Oidc.Server.Features.ScopeManagement;
+
+///
+/// Provides extension methods for scope validation, leveraging a scope manager and resource definitions
+/// to ensure the validity and permissibility of requested scopes.
+///
+public static class ScopeManagerExtensions
+{
+ ///
+ /// Validates the requested scopes against registered scope definitions and resource definitions to confirm
+ /// their validity and authorization.
+ /// This method ensures that scopes are either recognized by the scope manager or included in the resource
+ /// definitions.
+ ///
+ /// The scope manager that maintains the definitions of scopes.
+ /// A collection of scope identifiers to be validated.
+ /// An optional array of objects to validate scopes
+ /// against.
+ /// Outputs an array of objects if
+ /// the validation is successful, otherwise null.
+ /// Outputs a string describing the reason for validation failure,
+ /// otherwise null if the validation is successful.
+ /// True if all requested scopes are valid and permissible, false otherwise.
+ public static bool Validate(
+ this IScopeManager scopeManager,
+ IEnumerable scopes,
+ ResourceDefinition[]? resources,
+ [MaybeNullWhen(false)] out ScopeDefinition[] scopeDefinitions,
+ [MaybeNullWhen(true)] out string errorDescription)
+ {
+ scopeDefinitions = null;
+ errorDescription = null;
+
+ var scopeList = new List();
+
+ // Create a hash set of resource scopes if resources are provided and not empty
+ var resourceScopes = resources is { Length: > 0 }
+ ? resources
+ .SelectMany(rd => rd.Scopes, (_, sd) => sd.Scope)
+ .ToHashSet(StringComparer.Ordinal)
+ : null;
+
+ foreach (var scope in scopes)
+ {
+ // Check if the scope is recognized by the scope manager
+ if (scopeManager.TryGet(scope, out var scopeDefinition))
+ {
+ scopeList.Add(scopeDefinition);
+ }
+ // Check if the scope is part of the resource scopes
+ else if (resourceScopes != null && resourceScopes.Contains(scope))
+ {
+ // skip it
+ }
+ else
+ {
+ errorDescription = "The scope is not available";
+ return false;
+ }
+ }
+
+ scopeDefinitions = scopeList.ToArray();
+ return true;
+ }
+}
diff --git a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs
index 8ce57d2a..08a1a791 100644
--- a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs
+++ b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs
@@ -34,6 +34,8 @@
using Abblix.Oidc.Server.Features.Licensing;
using Abblix.Oidc.Server.Features.LogoutNotification;
using Abblix.Oidc.Server.Features.RandomGenerators;
+using Abblix.Oidc.Server.Features.ResourceIndicators;
+using Abblix.Oidc.Server.Features.ScopeManagement;
using Abblix.Oidc.Server.Features.SessionManagement;
using Abblix.Oidc.Server.Features.Storages;
using Abblix.Oidc.Server.Features.Tokens;
@@ -95,6 +97,10 @@ public static IServiceCollection AddClientInformation(this IServiceCollection se
public static IServiceCollection AddCommonServices(this IServiceCollection services)
{
services.TryAddSingleton();
+
+ services.TryAddSingleton();
+ services.Decorate();
+
services.TryAddSingleton(TimeProvider.System);
services.TryAddSingleton();
services.TryAddSingleton();
@@ -376,6 +382,8 @@ public static IServiceCollection AddUserInfo(this IServiceCollection services)
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
return services;
}
}
diff --git a/Abblix.Oidc.Server/Features/SessionManagement/Resources/checkSession.html b/Abblix.Oidc.Server/Features/SessionManagement/Resources/checkSession.html
index 84f323c6..119583a1 100644
--- a/Abblix.Oidc.Server/Features/SessionManagement/Resources/checkSession.html
+++ b/Abblix.Oidc.Server/Features/SessionManagement/Resources/checkSession.html
@@ -1,3 +1,4 @@
+